From aee0a433e8419fe48af064f8deaadfe08a8e215f Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Wed, 22 Feb 2017 21:35:14 +0100 Subject: [PATCH] Added RBDL to 3rdparty, added empty rig model, used better coding style in CharacterModule --- 3rdparty/rbdl/.editorconfig | 10 + 3rdparty/rbdl/.hg_archival.txt | 6 + 3rdparty/rbdl/.hgignore | 38 + 3rdparty/rbdl/.hgtags | 14 + 3rdparty/rbdl/CMake/FindCython.cmake | 44 + 3rdparty/rbdl/CMake/FindEigen3.cmake | 80 + 3rdparty/rbdl/CMake/FindUnitTest++.cmake | 40 + .../CMake/ReplicatePythonSourceTree.cmake | 4 + 3rdparty/rbdl/CMake/UseCython.cmake | 295 +++ 3rdparty/rbdl/CMakeLists.txt | 214 ++ 3rdparty/rbdl/CONTRIBUTING.md | 209 ++ 3rdparty/rbdl/Doxyfile | 1837 ++++++++++++++ 3rdparty/rbdl/LICENSE | 23 + 3rdparty/rbdl/README.md | 171 ++ 3rdparty/rbdl/addons/benchmark/CMakeLists.txt | 60 + .../rbdl/addons/benchmark/Human36Model.cc | 158 ++ 3rdparty/rbdl/addons/benchmark/Human36Model.h | 11 + 3rdparty/rbdl/addons/benchmark/SampleData.h | 85 + 3rdparty/rbdl/addons/benchmark/Timer.h | 29 + 3rdparty/rbdl/addons/benchmark/benchmark.cc | 879 +++++++ .../rbdl/addons/benchmark/model_generator.cc | 54 + .../rbdl/addons/benchmark/model_generator.h | 11 + 3rdparty/rbdl/addons/geometry/CMakeLists.txt | 80 + 3rdparty/rbdl/addons/geometry/Function.h | 525 ++++ 3rdparty/rbdl/addons/geometry/LICENSE | 23 + .../addons/geometry/LICENSE_APACHE-2.0.txt | 202 ++ 3rdparty/rbdl/addons/geometry/NOTICE | 24 + 3rdparty/rbdl/addons/geometry/README.md | 60 + .../geometry/SegmentedQuinticBezierToolkit.cc | 1285 ++++++++++ .../geometry/SegmentedQuinticBezierToolkit.h | 836 +++++++ .../geometry/SmoothSegmentedFunction.cc | 844 +++++++ .../addons/geometry/SmoothSegmentedFunction.h | 589 +++++ 3rdparty/rbdl/addons/geometry/geometry.h | 16 + .../rbdl/addons/geometry/tests/CMakeLists.txt | 67 + .../geometry/tests/numericalTestFunctions.cc | 534 ++++ .../geometry/tests/numericalTestFunctions.h | 289 +++ .../tests/testSmoothSegmentedFunction.cc | 483 ++++ 3rdparty/rbdl/addons/luamodel/CMakeLists.txt | 76 + 3rdparty/rbdl/addons/luamodel/README | 51 + 3rdparty/rbdl/addons/luamodel/luamodel.cc | 508 ++++ 3rdparty/rbdl/addons/luamodel/luamodel.h | 309 +++ 3rdparty/rbdl/addons/luamodel/luatables.cc | 932 +++++++ 3rdparty/rbdl/addons/luamodel/luatables.h | 261 ++ .../addons/luamodel/rbdl_luamodel_util.cc | 100 + .../luamodel/sampleconstrainedmodel.lua | 267 ++ 3rdparty/rbdl/addons/luamodel/samplemodel.lua | 87 + 3rdparty/rbdl/addons/muscle/CMakeLists.txt | 89 + 3rdparty/rbdl/addons/muscle/LICENSE | 23 + .../rbdl/addons/muscle/LICENSE_APACHE-2.0.txt | 202 ++ .../addons/muscle/Millard2016TorqueMuscle.cc | 1445 +++++++++++ .../addons/muscle/Millard2016TorqueMuscle.h | 1102 +++++++++ .../addons/muscle/MuscleFunctionFactory.cc | 927 +++++++ .../addons/muscle/MuscleFunctionFactory.h | 832 +++++++ 3rdparty/rbdl/addons/muscle/NOTICE | 22 + 3rdparty/rbdl/addons/muscle/README.md | 55 + .../muscle/TorqueMuscleFunctionFactory.cc | 1434 +++++++++++ .../muscle/TorqueMuscleFunctionFactory.h | 663 +++++ 3rdparty/rbdl/addons/muscle/csvtools.cc | 160 ++ 3rdparty/rbdl/addons/muscle/csvtools.h | 55 + 3rdparty/rbdl/addons/muscle/muscle.h | 12 + .../rbdl/addons/muscle/tests/CMakeLists.txt | 78 + .../tests/testMillard2016TorqueMuscle.cc | 725 ++++++ .../muscle/tests/testMuscleFunctionFactory.cc | 692 ++++++ .../tests/testTorqueMuscleFunctionFactory.cc | 779 ++++++ .../addons/urdfreader/CMake/FindURDF.cmake | 88 + .../addons/urdfreader/CMake/pkg-config.cmake | 369 +++ .../rbdl/addons/urdfreader/CMake/ros.cmake | 92 + .../urdfreader/CMake/shared-library.cmake | 28 + .../rbdl/addons/urdfreader/CMakeLists.txt | 116 + 3rdparty/rbdl/addons/urdfreader/README.md | 50 + .../addons/urdfreader/rbdl_urdfreader_util.cc | 69 + .../addons/urdfreader/thirdparty/README.md | 5 + .../urdfreader/thirdparty/tinyxml/changes.txt | 299 +++ .../urdfreader/thirdparty/tinyxml/readme.txt | 530 ++++ .../urdfreader/thirdparty/tinyxml/tinystr.cpp | 111 + .../urdfreader/thirdparty/tinyxml/tinystr.h | 305 +++ .../urdfreader/thirdparty/tinyxml/tinyxml.cpp | 1886 +++++++++++++++ .../urdfreader/thirdparty/tinyxml/tinyxml.h | 1805 ++++++++++++++ .../urdfreader/thirdparty/tinyxml/tinyxml.sln | 38 + .../thirdparty/tinyxml/tinyxmlerror.cpp | 52 + .../thirdparty/tinyxml/tinyxmlparser.cpp | 1638 +++++++++++++ .../thirdparty/tinyxml/utf8test.xml | 11 + .../thirdparty/tinyxml/utf8testverify.xml | 11 + .../urdf/boost_replacement/lexical_cast.h | 27 + .../urdf/boost_replacement/printf_console.cpp | 28 + .../urdf/boost_replacement/printf_console.h | 13 + .../urdf/boost_replacement/shared_ptr.h | 210 ++ .../urdf/boost_replacement/string_split.cpp | 253 ++ .../urdf/boost_replacement/string_split.h | 31 + .../thirdparty/urdf/urdfdom/LICENSE | 15 + .../thirdparty/urdf/urdfdom/README.txt | 7 + .../include/urdf_parser/urdf_parser.h | 63 + .../urdfdom/urdf_parser/src/check_urdf.cpp | 137 ++ .../urdf/urdfdom/urdf_parser/src/joint.cpp | 579 +++++ .../urdf/urdfdom/urdf_parser/src/link.cpp | 505 ++++ .../urdf/urdfdom/urdf_parser/src/model.cpp | 240 ++ .../urdf/urdfdom/urdf_parser/src/pose.cpp | 91 + .../urdf/urdfdom/urdf_parser/src/twist.cpp | 85 + .../urdf_parser/src/urdf_model_state.cpp | 154 ++ .../urdfdom/urdf_parser/src/urdf_sensor.cpp | 364 +++ .../urdf/urdfdom/urdf_parser/src/world.cpp | 71 + .../urdf/urdfdom/urdf_parser/test/memtest.cpp | 20 + .../thirdparty/urdf/urdfdom_headers/LICENSE | 15 + .../urdf/urdfdom_headers/README.txt | 6 + .../include/urdf_exception/exception.h | 53 + .../urdf_model/include/urdf_model/color.h | 101 + .../urdf_model/include/urdf_model/joint.h | 234 ++ .../urdf_model/include/urdf_model/link.h | 262 ++ .../urdf_model/include/urdf_model/model.h | 220 ++ .../urdf_model/include/urdf_model/pose.h | 265 ++ .../urdf_model/include/urdf_model/twist.h | 68 + .../include/urdf_model_state/model_state.h | 141 ++ .../include/urdf_model_state/twist.h | 42 + .../urdf_sensor/include/urdf_sensor/sensor.h | 176 ++ .../urdf_world/include/urdf_world/world.h | 114 + 3rdparty/rbdl/addons/urdfreader/urdfreader.cc | 315 +++ 3rdparty/rbdl/addons/urdfreader/urdfreader.h | 18 + 3rdparty/rbdl/doc/Mainpage.h | 170 ++ 3rdparty/rbdl/doc/api_changes.txt | 179 ++ 3rdparty/rbdl/doc/example.h | 25 + ...ig_GeometryAddon_quinticCornerSections.png | Bin 0 -> 80063 bytes ...scleAddon_Anderson2007AllPositiveSigns.png | Bin 0 -> 179699 bytes .../fig_MuscleAddon_Gymnast_ElbowForearm.png | Bin 0 -> 59041 bytes .../fig_MuscleAddon_Gymnast_HipKneeAnkle.png | Bin 0 -> 263104 bytes .../images/fig_MuscleAddon_Gymnast_Lumbar.png | Bin 0 -> 56979 bytes .../fig_MuscleAddon_Gymnast_Shoulder3Dof.png | Bin 0 -> 189496 bytes .../fig_MuscleAddon_Gymnast_Wrist3Dof.png | Bin 0 -> 203518 bytes ...leAddon_MuscleFunctionFactory_falCurve.png | Bin 0 -> 43114 bytes ...on_MuscleFunctionFactory_fcCosPhiCurve.png | Bin 0 -> 30956 bytes ...on_MuscleFunctionFactory_fcLengthCurve.png | Bin 0 -> 20917 bytes ...Addon_MuscleFunctionFactory_fcphiCurve.png | Bin 0 -> 31408 bytes ...leAddon_MuscleFunctionFactory_fpeCurve.png | Bin 0 -> 25651 bytes ...leAddon_MuscleFunctionFactory_fseCurve.png | Bin 0 -> 31856 bytes ...cleAddon_MuscleFunctionFactory_fvCurve.png | Bin 0 -> 38513 bytes ...Addon_MuscleFunctionFactory_fvInvCurve.png | Bin 0 -> 25558 bytes ...Factory_GaussianActiveTorqueAngleCurve.png | Bin 0 -> 45777 bytes ...y_GaussianActiveTorqueAngleCurveSimple.png | Bin 0 -> 34635 bytes ...unctionFactory_PassiveTorqueAngleCurve.png | Bin 0 -> 55923 bytes ...nFactory_PassiveTorqueAngleCurveSimple.png | Bin 0 -> 37055 bytes ...FunctionFactory_TendonTorqueAngleCurve.png | Bin 0 -> 59181 bytes ...onFactory_TendonTorqueAngleCurveSimple.png | Bin 0 -> 38669 bytes ...cleFunctionFactory_TorqueVelocityCurve.png | Bin 0 -> 85489 bytes ...ctionFactory_TorqueVelocityCurveSimple.png | Bin 0 -> 63138 bytes 3rdparty/rbdl/doc/logo/rbdl_logo.png | Bin 0 -> 4731 bytes 3rdparty/rbdl/doc/logo/rbdl_logo.svg | 104 + 3rdparty/rbdl/doc/logo/rbdl_logo_16x16.png | Bin 0 -> 256 bytes 3rdparty/rbdl/doc/logo/rbdl_logo_32x32.png | Bin 0 -> 707 bytes 3rdparty/rbdl/doc/logo/rbdl_logo_64x64.png | Bin 0 -> 1350 bytes 3rdparty/rbdl/doc/luamodel_example.h | 43 + 3rdparty/rbdl/doc/notes/Makefile | 2 + .../doc/notes/acceleration_visualization.pdf | Bin 0 -> 35231 bytes .../doc/notes/acceleration_visualization.svg | 2003 +++++++++++++++ .../doc/notes/point_velocity_acceleration.tex | 157 ++ 3rdparty/rbdl/doc/python_example.h | 32 + .../rbdl/examples/luamodel/CMakeLists.txt | 27 + .../rbdl/examples/luamodel/FindEigen3.cmake | 80 + .../rbdl/examples/luamodel/FindRBDL.cmake | 126 + .../examples/luamodel/example_luamodel.cc | 47 + .../luamodel/sampleconstrainedmodel.lua | 247 ++ .../rbdl/examples/luamodel/samplemodel.lua | 122 + 3rdparty/rbdl/examples/python/example.py | 55 + 3rdparty/rbdl/examples/simple/CMakeLists.txt | 24 + .../rbdl/examples/simple/FindEigen3.cmake | 80 + 3rdparty/rbdl/examples/simple/FindRBDL.cmake | 126 + 3rdparty/rbdl/examples/simple/example.cc | 65 + .../rbdl/examples/urdfreader/CMakeLists.txt | 25 + .../rbdl/examples/urdfreader/FindEigen3.cmake | 80 + .../rbdl/examples/urdfreader/FindRBDL.cmake | 126 + .../examples/urdfreader/example_urdfreader.cc | 45 + 3rdparty/rbdl/include/rbdl/Body.h | 218 ++ 3rdparty/rbdl/include/rbdl/Constraints.h | 936 +++++++ 3rdparty/rbdl/include/rbdl/Dynamics.h | 182 ++ 3rdparty/rbdl/include/rbdl/Joint.h | 694 ++++++ 3rdparty/rbdl/include/rbdl/Kinematics.h | 415 ++++ 3rdparty/rbdl/include/rbdl/Logging.h | 74 + 3rdparty/rbdl/include/rbdl/Model.h | 519 ++++ 3rdparty/rbdl/include/rbdl/Quaternion.h | 211 ++ .../include/rbdl/SimpleMath/.hg_archival.txt | 5 + .../rbdl/include/rbdl/SimpleMath/.hgignore | 15 + 3rdparty/rbdl/include/rbdl/SimpleMath/README | 10 + .../rbdl/include/rbdl/SimpleMath/SimpleMath.h | 28 + .../include/rbdl/SimpleMath/SimpleMathBlock.h | 194 ++ .../rbdl/SimpleMath/SimpleMathCholesky.h | 94 + .../SimpleMath/SimpleMathCommaInitializer.h | 69 + .../rbdl/SimpleMath/SimpleMathDynamic.h | 667 +++++ .../include/rbdl/SimpleMath/SimpleMathFixed.h | 881 +++++++ .../include/rbdl/SimpleMath/SimpleMathGL.h | 253 ++ .../include/rbdl/SimpleMath/SimpleMathMap.h | 22 + .../include/rbdl/SimpleMath/SimpleMathMixed.h | 138 ++ .../include/rbdl/SimpleMath/SimpleMathQR.h | 324 +++ .../include/rbdl/SimpleMath/compileassert.h | 39 + .../include/rbdl/SpatialAlgebraOperators.h | 452 ++++ 3rdparty/rbdl/include/rbdl/compileassert.h | 46 + 3rdparty/rbdl/include/rbdl/rbdl.h | 69 + .../rbdl/include/rbdl/rbdl_config.h.cmake | 74 + 3rdparty/rbdl/include/rbdl/rbdl_eigenmath.h | 236 ++ 3rdparty/rbdl/include/rbdl/rbdl_math.h | 82 + 3rdparty/rbdl/include/rbdl/rbdl_mathutils.h | 265 ++ 3rdparty/rbdl/include/rbdl/rbdl_utils.h | 54 + 3rdparty/rbdl/python/CMakeLists.txt | 66 + 3rdparty/rbdl/python/README.md | 53 + 3rdparty/rbdl/python/crbdl.pxd | 412 ++++ 3rdparty/rbdl/python/rbdl-wrapper.pyx | 1393 +++++++++++ 3rdparty/rbdl/python/rbdl.pyx | 2119 ++++++++++++++++ 3rdparty/rbdl/python/rbdl_loadmodel.cc | 42 + 3rdparty/rbdl/python/rbdl_ptr_functions.h | 637 +++++ 3rdparty/rbdl/python/setup.py.cmake | 57 + 3rdparty/rbdl/python/test_wrapper.py | 326 +++ 3rdparty/rbdl/python/wrappergen.py | 137 ++ 3rdparty/rbdl/rbdl.pc.cmake | 13 + 3rdparty/rbdl/src/Constraints.cc | 1537 ++++++++++++ 3rdparty/rbdl/src/Dynamics.cc | 863 +++++++ 3rdparty/rbdl/src/Joint.cc | 436 ++++ 3rdparty/rbdl/src/Kinematics.cc | 918 +++++++ 3rdparty/rbdl/src/Logging.cc | 14 + 3rdparty/rbdl/src/Model.cc | 494 ++++ 3rdparty/rbdl/src/rbdl_mathutils.cc | 319 +++ 3rdparty/rbdl/src/rbdl_utils.cc | 229 ++ 3rdparty/rbdl/src/rbdl_version.cc | 91 + 3rdparty/rbdl/tests/BodyTests.cc | 244 ++ 3rdparty/rbdl/tests/CMakeLists.txt | 70 + 3rdparty/rbdl/tests/CalcAccelerationsTests.cc | 235 ++ 3rdparty/rbdl/tests/CalcVelocitiesTests.cc | 215 ++ .../rbdl/tests/CompositeRigidBodyTests.cc | 261 ++ 3rdparty/rbdl/tests/ContactsTests.cc | 724 ++++++ .../rbdl/tests/CustomJointMultiBodyTests.cc | 1044 ++++++++ .../rbdl/tests/CustomJointSingleBodyTests.cc | 868 +++++++ 3rdparty/rbdl/tests/CustomJointTests.cc | 137 ++ 3rdparty/rbdl/tests/DynamicsTests.cc | 715 ++++++ 3rdparty/rbdl/tests/Fixtures.h | 698 ++++++ 3rdparty/rbdl/tests/FloatingBaseTests.cc | 550 +++++ 3rdparty/rbdl/tests/Human36Fixture.h | 461 ++++ 3rdparty/rbdl/tests/ImpulsesTests.cc | 279 +++ 3rdparty/rbdl/tests/InverseDynamicsTests.cc | 76 + 3rdparty/rbdl/tests/InverseKinematicsTests.cc | 272 +++ 3rdparty/rbdl/tests/KinematicsTests.cc | 680 ++++++ 3rdparty/rbdl/tests/LoopConstraintsTests.cc | 2146 +++++++++++++++++ 3rdparty/rbdl/tests/MathTests.cc | 109 + 3rdparty/rbdl/tests/ModelTests.cc | 604 +++++ 3rdparty/rbdl/tests/MultiDofTests.cc | 1055 ++++++++ 3rdparty/rbdl/tests/ScrewJointTests.cc | 115 + .../rbdl/tests/SparseFactorizationTests.cc | 268 ++ 3rdparty/rbdl/tests/SpatialAlgebraTests.cc | 586 +++++ 3rdparty/rbdl/tests/TwolegModelTests.cc | 404 ++++ 3rdparty/rbdl/tests/UtilsTests.cc | 156 ++ 3rdparty/rbdl/tests/main.cc | 19 + 3rdparty/rbdl/utils/matlab/FrameTranslation.m | 30 + .../rbdl/utils/matlab/VectorCrossMatrix.m | 5 + 3rdparty/rbdl/utils/matlab/ZYXEulerToMatrix.m | 56 + CMakeLists.txt | 15 +- src/modules/CMakeLists.txt | 3 + src/modules/CharacterModule.cc | 57 +- src/modules/CharacterModule.h | 28 +- src/modules/TestModule.cc | 18 +- 254 files changed, 69010 insertions(+), 46 deletions(-) create mode 100644 3rdparty/rbdl/.editorconfig create mode 100644 3rdparty/rbdl/.hg_archival.txt create mode 100644 3rdparty/rbdl/.hgignore create mode 100644 3rdparty/rbdl/.hgtags create mode 100644 3rdparty/rbdl/CMake/FindCython.cmake create mode 100644 3rdparty/rbdl/CMake/FindEigen3.cmake create mode 100644 3rdparty/rbdl/CMake/FindUnitTest++.cmake create mode 100644 3rdparty/rbdl/CMake/ReplicatePythonSourceTree.cmake create mode 100644 3rdparty/rbdl/CMake/UseCython.cmake create mode 100644 3rdparty/rbdl/CMakeLists.txt create mode 100644 3rdparty/rbdl/CONTRIBUTING.md create mode 100644 3rdparty/rbdl/Doxyfile create mode 100644 3rdparty/rbdl/LICENSE create mode 100644 3rdparty/rbdl/README.md create mode 100644 3rdparty/rbdl/addons/benchmark/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/benchmark/Human36Model.cc create mode 100644 3rdparty/rbdl/addons/benchmark/Human36Model.h create mode 100644 3rdparty/rbdl/addons/benchmark/SampleData.h create mode 100644 3rdparty/rbdl/addons/benchmark/Timer.h create mode 100644 3rdparty/rbdl/addons/benchmark/benchmark.cc create mode 100644 3rdparty/rbdl/addons/benchmark/model_generator.cc create mode 100644 3rdparty/rbdl/addons/benchmark/model_generator.h create mode 100644 3rdparty/rbdl/addons/geometry/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/geometry/Function.h create mode 100644 3rdparty/rbdl/addons/geometry/LICENSE create mode 100644 3rdparty/rbdl/addons/geometry/LICENSE_APACHE-2.0.txt create mode 100644 3rdparty/rbdl/addons/geometry/NOTICE create mode 100644 3rdparty/rbdl/addons/geometry/README.md create mode 100644 3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.cc create mode 100644 3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.h create mode 100644 3rdparty/rbdl/addons/geometry/SmoothSegmentedFunction.cc create mode 100644 3rdparty/rbdl/addons/geometry/SmoothSegmentedFunction.h create mode 100644 3rdparty/rbdl/addons/geometry/geometry.h create mode 100644 3rdparty/rbdl/addons/geometry/tests/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/geometry/tests/numericalTestFunctions.cc create mode 100644 3rdparty/rbdl/addons/geometry/tests/numericalTestFunctions.h create mode 100644 3rdparty/rbdl/addons/geometry/tests/testSmoothSegmentedFunction.cc create mode 100644 3rdparty/rbdl/addons/luamodel/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/luamodel/README create mode 100644 3rdparty/rbdl/addons/luamodel/luamodel.cc create mode 100644 3rdparty/rbdl/addons/luamodel/luamodel.h create mode 100644 3rdparty/rbdl/addons/luamodel/luatables.cc create mode 100644 3rdparty/rbdl/addons/luamodel/luatables.h create mode 100644 3rdparty/rbdl/addons/luamodel/rbdl_luamodel_util.cc create mode 100644 3rdparty/rbdl/addons/luamodel/sampleconstrainedmodel.lua create mode 100644 3rdparty/rbdl/addons/luamodel/samplemodel.lua create mode 100644 3rdparty/rbdl/addons/muscle/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/muscle/LICENSE create mode 100644 3rdparty/rbdl/addons/muscle/LICENSE_APACHE-2.0.txt create mode 100644 3rdparty/rbdl/addons/muscle/Millard2016TorqueMuscle.cc create mode 100644 3rdparty/rbdl/addons/muscle/Millard2016TorqueMuscle.h create mode 100644 3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.cc create mode 100644 3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.h create mode 100644 3rdparty/rbdl/addons/muscle/NOTICE create mode 100644 3rdparty/rbdl/addons/muscle/README.md create mode 100644 3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.cc create mode 100644 3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.h create mode 100644 3rdparty/rbdl/addons/muscle/csvtools.cc create mode 100644 3rdparty/rbdl/addons/muscle/csvtools.h create mode 100644 3rdparty/rbdl/addons/muscle/muscle.h create mode 100644 3rdparty/rbdl/addons/muscle/tests/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/muscle/tests/testMillard2016TorqueMuscle.cc create mode 100644 3rdparty/rbdl/addons/muscle/tests/testMuscleFunctionFactory.cc create mode 100644 3rdparty/rbdl/addons/muscle/tests/testTorqueMuscleFunctionFactory.cc create mode 100644 3rdparty/rbdl/addons/urdfreader/CMake/FindURDF.cmake create mode 100644 3rdparty/rbdl/addons/urdfreader/CMake/pkg-config.cmake create mode 100644 3rdparty/rbdl/addons/urdfreader/CMake/ros.cmake create mode 100644 3rdparty/rbdl/addons/urdfreader/CMake/shared-library.cmake create mode 100644 3rdparty/rbdl/addons/urdfreader/CMakeLists.txt create mode 100644 3rdparty/rbdl/addons/urdfreader/README.md create mode 100644 3rdparty/rbdl/addons/urdfreader/rbdl_urdfreader_util.cc create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/README.md create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/changes.txt create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/readme.txt create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.sln create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxmlerror.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxmlparser.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/utf8test.xml create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/utf8testverify.xml create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/lexical_cast.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/shared_ptr.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/string_split.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/string_split.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/LICENSE create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/README.txt create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/check_urdf.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/joint.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/link.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/model.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/pose.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/twist.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_model_state.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_sensor.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/world.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/test/memtest.cpp create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/LICENSE create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/README.txt create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_exception/include/urdf_exception/exception.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/color.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/joint.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/link.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/model.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/pose.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/twist.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/model_state.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/twist.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_sensor/include/urdf_sensor/sensor.h create mode 100644 3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_world/include/urdf_world/world.h create mode 100644 3rdparty/rbdl/addons/urdfreader/urdfreader.cc create mode 100644 3rdparty/rbdl/addons/urdfreader/urdfreader.h create mode 100644 3rdparty/rbdl/doc/Mainpage.h create mode 100644 3rdparty/rbdl/doc/api_changes.txt create mode 100644 3rdparty/rbdl/doc/example.h create mode 100644 3rdparty/rbdl/doc/images/fig_GeometryAddon_quinticCornerSections.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_Anderson2007AllPositiveSigns.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_ElbowForearm.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_HipKneeAnkle.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Lumbar.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Shoulder3Dof.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Wrist3Dof.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_falCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcCosPhiCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcLengthCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcphiCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fpeCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fseCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fvCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fvInvCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_GaussianActiveTorqueAngleCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_GaussianActiveTorqueAngleCurveSimple.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_PassiveTorqueAngleCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_PassiveTorqueAngleCurveSimple.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurveSimple.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TorqueVelocityCurve.png create mode 100644 3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TorqueVelocityCurveSimple.png create mode 100644 3rdparty/rbdl/doc/logo/rbdl_logo.png create mode 100644 3rdparty/rbdl/doc/logo/rbdl_logo.svg create mode 100644 3rdparty/rbdl/doc/logo/rbdl_logo_16x16.png create mode 100644 3rdparty/rbdl/doc/logo/rbdl_logo_32x32.png create mode 100644 3rdparty/rbdl/doc/logo/rbdl_logo_64x64.png create mode 100644 3rdparty/rbdl/doc/luamodel_example.h create mode 100644 3rdparty/rbdl/doc/notes/Makefile create mode 100644 3rdparty/rbdl/doc/notes/acceleration_visualization.pdf create mode 100644 3rdparty/rbdl/doc/notes/acceleration_visualization.svg create mode 100644 3rdparty/rbdl/doc/notes/point_velocity_acceleration.tex create mode 100644 3rdparty/rbdl/doc/python_example.h create mode 100644 3rdparty/rbdl/examples/luamodel/CMakeLists.txt create mode 100644 3rdparty/rbdl/examples/luamodel/FindEigen3.cmake create mode 100644 3rdparty/rbdl/examples/luamodel/FindRBDL.cmake create mode 100644 3rdparty/rbdl/examples/luamodel/example_luamodel.cc create mode 100644 3rdparty/rbdl/examples/luamodel/sampleconstrainedmodel.lua create mode 100644 3rdparty/rbdl/examples/luamodel/samplemodel.lua create mode 100644 3rdparty/rbdl/examples/python/example.py create mode 100644 3rdparty/rbdl/examples/simple/CMakeLists.txt create mode 100644 3rdparty/rbdl/examples/simple/FindEigen3.cmake create mode 100644 3rdparty/rbdl/examples/simple/FindRBDL.cmake create mode 100644 3rdparty/rbdl/examples/simple/example.cc create mode 100644 3rdparty/rbdl/examples/urdfreader/CMakeLists.txt create mode 100644 3rdparty/rbdl/examples/urdfreader/FindEigen3.cmake create mode 100644 3rdparty/rbdl/examples/urdfreader/FindRBDL.cmake create mode 100644 3rdparty/rbdl/examples/urdfreader/example_urdfreader.cc create mode 100644 3rdparty/rbdl/include/rbdl/Body.h create mode 100644 3rdparty/rbdl/include/rbdl/Constraints.h create mode 100644 3rdparty/rbdl/include/rbdl/Dynamics.h create mode 100644 3rdparty/rbdl/include/rbdl/Joint.h create mode 100644 3rdparty/rbdl/include/rbdl/Kinematics.h create mode 100644 3rdparty/rbdl/include/rbdl/Logging.h create mode 100644 3rdparty/rbdl/include/rbdl/Model.h create mode 100644 3rdparty/rbdl/include/rbdl/Quaternion.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/.hg_archival.txt create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/.hgignore create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/README create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMath.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathBlock.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCholesky.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCommaInitializer.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathDynamic.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathFixed.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathGL.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMap.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMixed.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathQR.h create mode 100644 3rdparty/rbdl/include/rbdl/SimpleMath/compileassert.h create mode 100644 3rdparty/rbdl/include/rbdl/SpatialAlgebraOperators.h create mode 100644 3rdparty/rbdl/include/rbdl/compileassert.h create mode 100644 3rdparty/rbdl/include/rbdl/rbdl.h create mode 100644 3rdparty/rbdl/include/rbdl/rbdl_config.h.cmake create mode 100644 3rdparty/rbdl/include/rbdl/rbdl_eigenmath.h create mode 100644 3rdparty/rbdl/include/rbdl/rbdl_math.h create mode 100644 3rdparty/rbdl/include/rbdl/rbdl_mathutils.h create mode 100644 3rdparty/rbdl/include/rbdl/rbdl_utils.h create mode 100644 3rdparty/rbdl/python/CMakeLists.txt create mode 100644 3rdparty/rbdl/python/README.md create mode 100644 3rdparty/rbdl/python/crbdl.pxd create mode 100644 3rdparty/rbdl/python/rbdl-wrapper.pyx create mode 100644 3rdparty/rbdl/python/rbdl.pyx create mode 100644 3rdparty/rbdl/python/rbdl_loadmodel.cc create mode 100644 3rdparty/rbdl/python/rbdl_ptr_functions.h create mode 100755 3rdparty/rbdl/python/setup.py.cmake create mode 100755 3rdparty/rbdl/python/test_wrapper.py create mode 100755 3rdparty/rbdl/python/wrappergen.py create mode 100644 3rdparty/rbdl/rbdl.pc.cmake create mode 100644 3rdparty/rbdl/src/Constraints.cc create mode 100644 3rdparty/rbdl/src/Dynamics.cc create mode 100644 3rdparty/rbdl/src/Joint.cc create mode 100644 3rdparty/rbdl/src/Kinematics.cc create mode 100644 3rdparty/rbdl/src/Logging.cc create mode 100644 3rdparty/rbdl/src/Model.cc create mode 100644 3rdparty/rbdl/src/rbdl_mathutils.cc create mode 100644 3rdparty/rbdl/src/rbdl_utils.cc create mode 100644 3rdparty/rbdl/src/rbdl_version.cc create mode 100644 3rdparty/rbdl/tests/BodyTests.cc create mode 100644 3rdparty/rbdl/tests/CMakeLists.txt create mode 100644 3rdparty/rbdl/tests/CalcAccelerationsTests.cc create mode 100644 3rdparty/rbdl/tests/CalcVelocitiesTests.cc create mode 100644 3rdparty/rbdl/tests/CompositeRigidBodyTests.cc create mode 100644 3rdparty/rbdl/tests/ContactsTests.cc create mode 100644 3rdparty/rbdl/tests/CustomJointMultiBodyTests.cc create mode 100644 3rdparty/rbdl/tests/CustomJointSingleBodyTests.cc create mode 100644 3rdparty/rbdl/tests/CustomJointTests.cc create mode 100644 3rdparty/rbdl/tests/DynamicsTests.cc create mode 100644 3rdparty/rbdl/tests/Fixtures.h create mode 100644 3rdparty/rbdl/tests/FloatingBaseTests.cc create mode 100644 3rdparty/rbdl/tests/Human36Fixture.h create mode 100644 3rdparty/rbdl/tests/ImpulsesTests.cc create mode 100644 3rdparty/rbdl/tests/InverseDynamicsTests.cc create mode 100644 3rdparty/rbdl/tests/InverseKinematicsTests.cc create mode 100644 3rdparty/rbdl/tests/KinematicsTests.cc create mode 100644 3rdparty/rbdl/tests/LoopConstraintsTests.cc create mode 100644 3rdparty/rbdl/tests/MathTests.cc create mode 100644 3rdparty/rbdl/tests/ModelTests.cc create mode 100644 3rdparty/rbdl/tests/MultiDofTests.cc create mode 100644 3rdparty/rbdl/tests/ScrewJointTests.cc create mode 100644 3rdparty/rbdl/tests/SparseFactorizationTests.cc create mode 100644 3rdparty/rbdl/tests/SpatialAlgebraTests.cc create mode 100644 3rdparty/rbdl/tests/TwolegModelTests.cc create mode 100644 3rdparty/rbdl/tests/UtilsTests.cc create mode 100644 3rdparty/rbdl/tests/main.cc create mode 100755 3rdparty/rbdl/utils/matlab/FrameTranslation.m create mode 100644 3rdparty/rbdl/utils/matlab/VectorCrossMatrix.m create mode 100644 3rdparty/rbdl/utils/matlab/ZYXEulerToMatrix.m diff --git a/3rdparty/rbdl/.editorconfig b/3rdparty/rbdl/.editorconfig new file mode 100644 index 0000000..195d343 --- /dev/null +++ b/3rdparty/rbdl/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 2 +charset = utf-8 + +[Makefile] +indent_style = tab diff --git a/3rdparty/rbdl/.hg_archival.txt b/3rdparty/rbdl/.hg_archival.txt new file mode 100644 index 0000000..f0a6d1a --- /dev/null +++ b/3rdparty/rbdl/.hg_archival.txt @@ -0,0 +1,6 @@ +repo: d536c26f2a238e943cf08a038d1134c782b1e66c +node: ee446c22d4fbfb51b29d2d708472a958e2eb38b6 +branch: dev +latesttag: v2.5.0 +latesttagdistance: 43 +changessincelatesttag: 103 diff --git a/3rdparty/rbdl/.hgignore b/3rdparty/rbdl/.hgignore new file mode 100644 index 0000000..2535234 --- /dev/null +++ b/3rdparty/rbdl/.hgignore @@ -0,0 +1,38 @@ +syntax: glob + +doc/html/* +doc/notes/*.aux +doc/notes/*.pdf + +*Debug/* +*Release/* +*RelWithDebInfo/* +*build/* + +.*.swp +CMakeFiles/* +cmake_install.cmake +CMakeCache.txt +Makefile +tags +*.log + +*.user + +src/rbdl_config.h + +# autogenerated by python/wrappergen.py +python/rbdl.pyx + +examples/simple/CMakeCache.txt +examples/simple/example +examples/simple/librbdl.so + +examples/luamodel/example_luamodel +examples/luamodel/CMakeCache.txt + +*.vcxproj +*.vcxproj.filters +*.suo +*.db +*.sln diff --git a/3rdparty/rbdl/.hgtags b/3rdparty/rbdl/.hgtags new file mode 100644 index 0000000..5e6787a --- /dev/null +++ b/3rdparty/rbdl/.hgtags @@ -0,0 +1,14 @@ +7a77f7a994d813d91742f89563c274d6fd04c061 v1.0.0 +e753e34715b107aad90b8aa7dd985cca2ff01d53 v1.1.0 +e0f64ac8491140fdc9e4499b55d7eadbc093b229 v2.0.0 +7742057b61fb0094c3e437294cd48c596913ad4d v2.0.1 +70d7265f4d36fa5aba907879f939ba658fe58bd3 v2.1.0 +0f2b7a0fd90a5391d9bb5a2399b1a4fe44893018 v2.2.0 +57ec58a73e0948727b96092cb1ea948fdcae4bd0 v2.2.1 +39707193a22c11ea671ce1b981b8dc9b4da406b9 v2.2.2 +2da137bfd1fd7f2e14294aff6e3feeffebfac952 v2.3.1 +8c9139fa7142b8370c0c004abb86ce13d0984aa3 v2.3.2 +7cfc6e93e10135c7fe8e89736160f0322932d440 v2.3.3 +48c0872b39d6fd9d085c1c5c6ff46f7125a17f9d v2.4.0 +9d527cef3bb1dcb8b57b861251fc14ab99a93372 v2.4.1 +37918477608938f4c1e90eedf1a525c69533d843 v2.5.0 diff --git a/3rdparty/rbdl/CMake/FindCython.cmake b/3rdparty/rbdl/CMake/FindCython.cmake new file mode 100644 index 0000000..04aed1f --- /dev/null +++ b/3rdparty/rbdl/CMake/FindCython.cmake @@ -0,0 +1,44 @@ +# Find the Cython compiler. +# +# This code sets the following variables: +# +# CYTHON_EXECUTABLE +# +# See also UseCython.cmake + +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +# Use the Cython executable that lives next to the Python executable +# if it is a local installation. +find_package( PythonInterp ) +if( PYTHONINTERP_FOUND ) + get_filename_component( _python_path ${PYTHON_EXECUTABLE} PATH ) + find_program( CYTHON_EXECUTABLE + NAMES cython cython.bat cython3 + HINTS ${_python_path} + ) +else() + find_program( CYTHON_EXECUTABLE + NAMES cython cython.bat cython3 + ) +endif() + + +include( FindPackageHandleStandardArgs ) +FIND_PACKAGE_HANDLE_STANDARD_ARGS( Cython REQUIRED_VARS CYTHON_EXECUTABLE ) + +mark_as_advanced( CYTHON_EXECUTABLE ) diff --git a/3rdparty/rbdl/CMake/FindEigen3.cmake b/3rdparty/rbdl/CMake/FindEigen3.cmake new file mode 100644 index 0000000..66ffe8e --- /dev/null +++ b/3rdparty/rbdl/CMake/FindEigen3.cmake @@ -0,0 +1,80 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif(NOT Eigen3_FIND_VERSION_MAJOR) + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif(NOT Eigen3_FIND_VERSION_MINOR) + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif(NOT Eigen3_FIND_VERSION_PATCH) + + set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif(NOT Eigen3_FIND_VERSION) + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK TRUE) + endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif(NOT EIGEN3_VERSION_OK) +endmacro(_eigen3_check_version) + +if (EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + +else (EIGEN3_INCLUDE_DIR) + + find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library + PATHS + ${CMAKE_INSTALL_PREFIX}/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen + ) + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif(EIGEN3_INCLUDE_DIR) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif(EIGEN3_INCLUDE_DIR) diff --git a/3rdparty/rbdl/CMake/FindUnitTest++.cmake b/3rdparty/rbdl/CMake/FindUnitTest++.cmake new file mode 100644 index 0000000..dac8410 --- /dev/null +++ b/3rdparty/rbdl/CMake/FindUnitTest++.cmake @@ -0,0 +1,40 @@ +# - Try to find UnitTest++ +# +# + +SET (UNITTEST++_FOUND FALSE) + +FIND_PATH (UNITTEST++_INCLUDE_DIR UnitTest++.h + /usr/include/unittest++ + /usr/local/include/unittest++ + /opt/local/include/unittest++ + $ENV{UNITTESTXX_PATH}/src + $ENV{UNITTESTXX_INCLUDE_PATH} + ) + +FIND_LIBRARY (UNITTEST++_LIBRARY NAMES UnitTest++ PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + $ENV{UNITTESTXX_PATH} + ENV{UNITTESTXX_LIBRARY_PATH} + ) + +IF (UNITTEST++_INCLUDE_DIR AND UNITTEST++_LIBRARY) + SET (UNITTEST++_FOUND TRUE) +ENDIF (UNITTEST++_INCLUDE_DIR AND UNITTEST++_LIBRARY) + +IF (UNITTEST++_FOUND) + IF (NOT UnitTest++_FIND_QUIETLY) + MESSAGE(STATUS "Found UnitTest++: ${UNITTEST++_LIBRARY}") + ENDIF (NOT UnitTest++_FIND_QUIETLY) +ELSE (UNITTEST++_FOUND) + IF (UnitTest++_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find UnitTest++") + ENDIF (UnitTest++_FIND_REQUIRED) +ENDIF (UNITTEST++_FOUND) + +MARK_AS_ADVANCED ( + UNITTEST++_INCLUDE_DIR + UNITTEST++_LIBRARY + ) diff --git a/3rdparty/rbdl/CMake/ReplicatePythonSourceTree.cmake b/3rdparty/rbdl/CMake/ReplicatePythonSourceTree.cmake new file mode 100644 index 0000000..69e638d --- /dev/null +++ b/3rdparty/rbdl/CMake/ReplicatePythonSourceTree.cmake @@ -0,0 +1,4 @@ +# Note: when executed in the build dir, then CMAKE_CURRENT_SOURCE_DIR is the +# build dir. +file( COPY src test bin DESTINATION "${CMAKE_ARGV3}" + FILES_MATCHING PATTERN "*.py" ) diff --git a/3rdparty/rbdl/CMake/UseCython.cmake b/3rdparty/rbdl/CMake/UseCython.cmake new file mode 100644 index 0000000..7eaa770 --- /dev/null +++ b/3rdparty/rbdl/CMake/UseCython.cmake @@ -0,0 +1,295 @@ +# Define a function to create Cython modules. +# +# For more information on the Cython project, see http://cython.org/. +# "Cython is a language that makes writing C extensions for the Python language +# as easy as Python itself." +# +# This file defines a CMake function to build a Cython Python module. +# To use it, first include this file. +# +# include( UseCython ) +# +# Then call cython_add_module to create a module. +# +# cython_add_module( ... ) +# +# To create a standalone executable, the function +# +# cython_add_standalone_executable( [MAIN_MODULE src1] ... ) +# +# To avoid dependence on Python, set the PYTHON_LIBRARY cache variable to point +# to a static library. If a MAIN_MODULE source is specified, +# the "if __name__ == '__main__':" from that module is used as the C main() method +# for the executable. If MAIN_MODULE, the source with the same basename as +# is assumed to be the MAIN_MODULE. +# +# Where is the name of the resulting Python module and +# ... are source files to be compiled into the module, e.g. *.pyx, +# *.py, *.c, *.cxx, etc. A CMake target is created with name . This can +# be used for target_link_libraries(), etc. +# +# The sample paths set with the CMake include_directories() command will be used +# for include directories to search for *.pxd when running the Cython complire. +# +# Cache variables that effect the behavior include: +# +# CYTHON_ANNOTATE +# CYTHON_NO_DOCSTRINGS +# CYTHON_FLAGS +# +# Source file properties that effect the build process are +# +# CYTHON_IS_CXX +# +# If this is set of a *.pyx file with CMake set_source_files_properties() +# command, the file will be compiled as a C++ file. +# +# See also FindCython.cmake + +#============================================================================= +# Copyright 2011 Kitware, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#============================================================================= + +# Configuration options. +set( CYTHON_ANNOTATE OFF + CACHE BOOL "Create an annotated .html file when compiling *.pyx." ) +set( CYTHON_NO_DOCSTRINGS OFF + CACHE BOOL "Strip docstrings from the compiled module." ) +set( CYTHON_FLAGS "" CACHE STRING + "Extra flags to the cython compiler." ) +mark_as_advanced( CYTHON_ANNOTATE CYTHON_NO_DOCSTRINGS CYTHON_FLAGS ) + +find_package( Cython REQUIRED ) +find_package( PythonLibs REQUIRED ) + +set( CYTHON_CXX_EXTENSION "cxx" ) +set( CYTHON_C_EXTENSION "c" ) + +# Create a *.c or *.cxx file from a *.pyx file. +# Input the generated file basename. The generate file will put into the variable +# placed in the "generated_file" argument. Finally all the *.py and *.pyx files. +function( compile_pyx _name generated_file ) + # Default to assuming all files are C. + set( cxx_arg "" ) + set( extension ${CYTHON_C_EXTENSION} ) + set( pyx_lang "C" ) + set( comment "Compiling Cython C source for ${_name}..." ) + + set( cython_include_directories "" ) + set( pxd_dependencies "" ) + set( c_header_dependencies "" ) + set( pyx_locations "" ) + + foreach( pyx_file ${ARGN} ) + get_filename_component( pyx_file_basename "${pyx_file}" NAME_WE ) + + # Determine if it is a C or C++ file. + get_source_file_property( property_is_cxx ${pyx_file} CYTHON_IS_CXX ) + if( ${property_is_cxx} STREQUAL "TRUE" ) + set( cxx_arg "--cplus" ) + set( extension ${CYTHON_CXX_EXTENSION} ) + set( pyx_lang "CXX" ) + set( comment "Compiling Cython CXX source for ${_name}..." ) + endif() + + # Get the include directories. + get_source_file_property( pyx_location ${pyx_file} LOCATION ) + get_filename_component( pyx_path ${pyx_location} PATH ) + get_directory_property( cmake_include_directories DIRECTORY ${pyx_path} INCLUDE_DIRECTORIES ) + list( APPEND cython_include_directories ${cmake_include_directories} ) + list( APPEND pyx_locations "${pyx_location}" ) + + # Determine dependencies. + # Add the pxd file will the same name as the given pyx file. + unset( corresponding_pxd_file CACHE ) + find_file( corresponding_pxd_file ${pyx_file_basename}.pxd + PATHS "${pyx_path}" ${cmake_include_directories} + NO_DEFAULT_PATH ) + if( corresponding_pxd_file ) + list( APPEND pxd_dependencies "${corresponding_pxd_file}" ) + endif() + + # pxd files to check for additional dependencies. + set( pxds_to_check "${pyx_file}" "${pxd_dependencies}" ) + set( pxds_checked "" ) + set( number_pxds_to_check 1 ) + while( ${number_pxds_to_check} GREATER 0 ) + foreach( pxd ${pxds_to_check} ) + list( APPEND pxds_checked "${pxd}" ) + list( REMOVE_ITEM pxds_to_check "${pxd}" ) + + # check for C header dependencies + file( STRINGS "${pxd}" extern_from_statements + REGEX "cdef[ ]+extern[ ]+from.*$" ) + foreach( statement ${extern_from_statements} ) + # Had trouble getting the quote in the regex + string( REGEX REPLACE "cdef[ ]+extern[ ]+from[ ]+[\"]([^\"]+)[\"].*" "\\1" header "${statement}" ) + unset( header_location CACHE ) + find_file( header_location ${header} PATHS ${cmake_include_directories} ) + if( header_location ) + list( FIND c_header_dependencies "${header_location}" header_idx ) + if( ${header_idx} LESS 0 ) + list( APPEND c_header_dependencies "${header_location}" ) + endif() + endif() + endforeach() + + # check for pxd dependencies + + # Look for cimport statements. + set( module_dependencies "" ) + file( STRINGS "${pxd}" cimport_statements REGEX cimport ) + foreach( statement ${cimport_statements} ) + if( ${statement} MATCHES from ) + string( REGEX REPLACE "from[ ]+([^ ]+).*" "\\1" module "${statement}" ) + else() + string( REGEX REPLACE "cimport[ ]+([^ ]+).*" "\\1" module "${statement}" ) + endif() + list( APPEND module_dependencies ${module} ) + endforeach() + list( REMOVE_DUPLICATES module_dependencies ) + # Add the module to the files to check, if appropriate. + foreach( module ${module_dependencies} ) + unset( pxd_location CACHE ) + find_file( pxd_location ${module}.pxd + PATHS "${pyx_path}" ${cmake_include_directories} NO_DEFAULT_PATH ) + if( pxd_location ) + list( FIND pxds_checked ${pxd_location} pxd_idx ) + if( ${pxd_idx} LESS 0 ) + list( FIND pxds_to_check ${pxd_location} pxd_idx ) + if( ${pxd_idx} LESS 0 ) + list( APPEND pxds_to_check ${pxd_location} ) + list( APPEND pxd_dependencies ${pxd_location} ) + endif() # if it is not already going to be checked + endif() # if it has not already been checked + endif() # if pxd file can be found + endforeach() # for each module dependency discovered + endforeach() # for each pxd file to check + list( LENGTH pxds_to_check number_pxds_to_check ) + endwhile() + endforeach() # pyx_file + + # Set additional flags. + if( CYTHON_ANNOTATE ) + set( annotate_arg "--annotate" ) + endif() + + if( CYTHON_NO_DOCSTRINGS ) + set( no_docstrings_arg "--no-docstrings" ) + endif() + + if( "${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR + "${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo" ) + set( cython_debug_arg "--gdb" ) + endif() + + if( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^2." ) + set( version_arg "-2" ) + elseif( "${PYTHONLIBS_VERSION_STRING}" MATCHES "^3." ) + set( version_arg "-3" ) + else() + set( version_arg ) + endif() + + # Include directory arguments. + list( REMOVE_DUPLICATES cython_include_directories ) + set( include_directory_arg "" ) + foreach( _include_dir ${cython_include_directories} ) + set( include_directory_arg ${include_directory_arg} "-I" "${_include_dir}" ) + endforeach() + + # Determining generated file name. + set( _generated_file "${CMAKE_CURRENT_BINARY_DIR}/${_name}.${extension}" ) + set_source_files_properties( ${_generated_file} PROPERTIES GENERATED TRUE ) + set( ${generated_file} ${_generated_file} PARENT_SCOPE ) + + list( REMOVE_DUPLICATES pxd_dependencies ) + list( REMOVE_DUPLICATES c_header_dependencies ) + + # Add the command to run the compiler. + add_custom_command( OUTPUT ${_generated_file} + COMMAND ${CYTHON_EXECUTABLE} + ARGS ${cxx_arg} ${include_directory_arg} ${version_arg} + ${annotate_arg} ${no_docstrings_arg} ${cython_debug_arg} ${CYTHON_FLAGS} + --output-file ${_generated_file} ${pyx_locations} + DEPENDS ${pyx_locations} ${pxd_dependencies} + IMPLICIT_DEPENDS ${pyx_lang} ${c_header_dependencies} + COMMENT ${comment} + ) + + # Remove their visibility to the user. + set( corresponding_pxd_file "" CACHE INTERNAL "" ) + set( header_location "" CACHE INTERNAL "" ) + set( pxd_location "" CACHE INTERNAL "" ) +endfunction() + +# cython_add_module( src1 src2 ... srcN ) +# Build the Cython Python module. +function( cython_add_module _name ) + set( pyx_module_sources "" ) + set( other_module_sources "" ) + foreach( _file ${ARGN} ) + if( ${_file} MATCHES ".*\\.py[x]?$" ) + list( APPEND pyx_module_sources ${_file} ) + else() + list( APPEND other_module_sources ${_file} ) + endif() + endforeach() + compile_pyx( ${_name} generated_file ${pyx_module_sources} ) + include_directories( ${PYTHON_INCLUDE_DIRS} ) + python_add_module( ${_name} ${generated_file} ${other_module_sources} ) + if( APPLE ) + set_target_properties( ${_name} PROPERTIES LINK_FLAGS "-undefined dynamic_lookup" ) + else() + target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ) + endif() +endfunction() + +include( CMakeParseArguments ) +# cython_add_standalone_executable( _name [MAIN_MODULE src3.py] src1 src2 ... srcN ) +# Creates a standalone executable the given sources. +function( cython_add_standalone_executable _name ) + set( pyx_module_sources "" ) + set( other_module_sources "" ) + set( main_module "" ) + cmake_parse_arguments( cython_arguments "" "MAIN_MODULE" "" ${ARGN} ) + include_directories( ${PYTHON_INCLUDE_DIRS} ) + foreach( _file ${cython_arguments_UNPARSED_ARGUMENTS} ) + if( ${_file} MATCHES ".*\\.py[x]?$" ) + get_filename_component( _file_we ${_file} NAME_WE ) + if( "${_file_we}" STREQUAL "${_name}" ) + set( main_module "${_file}" ) + elseif( NOT "${_file}" STREQUAL "${cython_arguments_MAIN_MODULE}" ) + set( PYTHON_MODULE_${_file_we}_static_BUILD_SHARED OFF ) + compile_pyx( "${_file_we}_static" generated_file "${_file}" ) + list( APPEND pyx_module_sources "${generated_file}" ) + endif() + else() + list( APPEND other_module_sources ${_file} ) + endif() + endforeach() + + if( cython_arguments_MAIN_MODULE ) + set( main_module ${cython_arguments_MAIN_MODULE} ) + endif() + if( NOT main_module ) + message( FATAL_ERROR "main module not found." ) + endif() + get_filename_component( main_module_we "${main_module}" NAME_WE ) + set( CYTHON_FLAGS ${CYTHON_FLAGS} --embed ) + compile_pyx( "${main_module_we}_static" generated_file ${main_module} ) + add_executable( ${_name} ${generated_file} ${pyx_module_sources} ${other_module_sources} ) + target_link_libraries( ${_name} ${PYTHON_LIBRARIES} ${pyx_module_libs} ) +endfunction() diff --git a/3rdparty/rbdl/CMakeLists.txt b/3rdparty/rbdl/CMakeLists.txt new file mode 100644 index 0000000..a8e6c4d --- /dev/null +++ b/3rdparty/rbdl/CMakeLists.txt @@ -0,0 +1,214 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) +CMAKE_POLICY(SET CMP0048 NEW) + +SET ( RBDL_VERSION_MAJOR 2 ) +SET ( RBDL_VERSION_MINOR 5 ) +SET ( RBDL_VERSION_PATCH 0 ) +SET ( RBDL_VERSION + ${RBDL_VERSION_MAJOR}.${RBDL_VERSION_MINOR}.${RBDL_VERSION_PATCH} +) +SET ( RBDL_SO_VERSION + ${RBDL_VERSION_MAJOR}.${RBDL_VERSION_MINOR}.${RBDL_VERSION_PATCH} +) +SET (PROJECT_VERSION ${RBDL_VERSION}) +PROJECT (RBDL VERSION ${RBDL_VERSION}) + +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake ) + +INCLUDE_DIRECTORIES ( + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_BINARY_DIR}/include +) + +# SET (CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +INCLUDE(GNUInstallDirs) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX + ) + +# Set a default build type to 'Release' if none was specified +IF(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + MESSAGE(STATUS "Setting build type to 'Release' as none was specified.") + SET(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + # Set the possible values of build type for cmake-gui + SET_PROPERTY(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +ENDIF() + +# Find and use the system's Eigen3 library +FIND_PACKAGE (Eigen3 3.0.0) + +IF (NOT EIGEN3_FOUND AND NOT RBDL_USE_SIMPLE_MATH) + MESSAGE (WARNING "Could not find Eigen3 on your system. Install it or use the slower SimpleMath library by enabling RBDL_USE_SIMPLE_MATH.") +ENDIF (NOT EIGEN3_FOUND AND NOT RBDL_USE_SIMPLE_MATH) + +IF (EIGEN3_FOUND AND NOT RBDL_USE_SIMPLE_MATH) + INCLUDE_DIRECTORIES (${EIGEN3_INCLUDE_DIR}) +ENDIF (EIGEN3_FOUND AND NOT RBDL_USE_SIMPLE_MATH) + +# Options +SET (RBDL_BUILD_STATIC_DEFAULT OFF) +IF (MSVC) + SET (RBDL_BUILD_STATIC_DEFAULT ON) +ENDIF (MSVC) + +OPTION (RBDL_BUILD_STATIC "Build statically linked library (otherwise dynamiclly linked)" ${RBDL_BUILD_STATIC_DEFAULT}) +OPTION (RBDL_BUILD_TESTS "Build the test executables" OFF) +OPTION (RBDL_ENABLE_LOGGING "Enable logging (warning: major impact on performance!)" OFF) +OPTION (RBDL_USE_SIMPLE_MATH "Use slow math instead of the fast Eigen3 library (faster compilation)" OFF) +OPTION (RBDL_STORE_VERSION "Enable storing of version information in the library (requires build from valid repository)" OFF) +OPTION (RBDL_BUILD_ADDON_URDFREADER "Build the (experimental) urdf reader" OFF) +OPTION (RBDL_BUILD_ADDON_BENCHMARK "Build the benchmarking tool" OFF) +OPTION (RBDL_BUILD_ADDON_LUAMODEL "Build the lua model reader" OFF) +OPTION (RBDL_BUILD_PYTHON_WRAPPER "Build experimental python wrapper" OFF) +OPTION (RBDL_BUILD_ADDON_GEOMETRY "Build the geometry library" OFF) +OPTION (RBDL_BUILD_ADDON_MUSCLE "Build the muscle library" OFF) + +# Addons +IF (RBDL_BUILD_ADDON_URDFREADER) + ADD_SUBDIRECTORY ( addons/urdfreader ) +ENDIF (RBDL_BUILD_ADDON_URDFREADER) + +IF (RBDL_BUILD_ADDON_BENCHMARK) + ADD_SUBDIRECTORY ( addons/benchmark ) +ENDIF (RBDL_BUILD_ADDON_BENCHMARK) + +IF (RBDL_BUILD_ADDON_LUAMODEL) + ADD_SUBDIRECTORY ( addons/luamodel ) +ENDIF (RBDL_BUILD_ADDON_LUAMODEL) + +IF(RBDL_BUILD_ADDON_MUSCLE) + SET(RBDL_BUILD_ADDON_GEOMETRY ON CACHE BOOL "Build the geometry library" FORCE) + ADD_SUBDIRECTORY ( addons/muscle ) + IF(RBDL_BUILD_TESTS) + ADD_SUBDIRECTORY ( addons/muscle/tests ) + ENDIF(RBDL_BUILD_TESTS) +ENDIF(RBDL_BUILD_ADDON_MUSCLE) + + +IF(RBDL_BUILD_ADDON_GEOMETRY) + ADD_SUBDIRECTORY ( addons/geometry ) + IF(RBDL_BUILD_TESTS) + ADD_SUBDIRECTORY ( addons/geometry/tests ) + ENDIF(RBDL_BUILD_TESTS) +ENDIF(RBDL_BUILD_ADDON_GEOMETRY) + + + +IF (RBDL_BUILD_TESTS) + ADD_SUBDIRECTORY ( tests ) +ENDIF (RBDL_BUILD_TESTS) + +# Source files for RBDL +SET ( RBDL_SOURCES + src/rbdl_version.cc + src/rbdl_mathutils.cc + src/rbdl_utils.cc + src/Constraints.cc + src/Dynamics.cc + src/Logging.cc + src/Joint.cc + src/Model.cc + src/Kinematics.cc + ) + +IF (MSVC AND NOT RBDL_BUILD_STATIC) + MESSAGE (FATAL_ERROR, "Compiling RBDL as a DLL currently not supported. Please enable RBDL_BUILD_STATIC.") +ENDIF (MSVC AND NOT RBDL_BUILD_STATIC) + +# Static / dynamic builds +IF (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl-static STATIC ${RBDL_SOURCES} ) + IF (NOT WIN32) + SET_TARGET_PROPERTIES ( rbdl-static PROPERTIES PREFIX "lib") + ENDIF (NOT WIN32) + SET_TARGET_PROPERTIES ( rbdl-static PROPERTIES OUTPUT_NAME "rbdl") + + IF (RBDL_BUILD_ADDON_LUAMODEL) + TARGET_LINK_LIBRARIES ( rbdl-static + rbdl_luamodel-static + ) + ENDIF (RBDL_BUILD_ADDON_LUAMODEL) + + INSTALL (TARGETS rbdl-static + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ELSE (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl SHARED ${RBDL_SOURCES} ) + SET_TARGET_PROPERTIES ( rbdl PROPERTIES + VERSION ${RBDL_VERSION} + SOVERSION ${RBDL_SO_VERSION} + ) + + INSTALL (TARGETS rbdl + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ENDIF (RBDL_BUILD_STATIC) + +IF (RBDL_STORE_VERSION) + # Set versioning information that can be queried during runtime + EXEC_PROGRAM("hg" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "id -i" + OUTPUT_VARIABLE RBDL_BUILD_REVISION) + EXEC_PROGRAM("hg" ${CMAKE_CURRENT_SOURCE_DIR} ARGS "branch" + OUTPUT_VARIABLE RBDL_BUILD_BRANCH) + SET (RBDL_BUILD_TYPE ${CMAKE_BUILD_TYPE}) +ELSE (RBDL_STORE_VERSION) + SET (RBDL_BUILD_REVISION "unknown") + SET (RBDL_BUILD_BRANCH "unknown") + SET (RBDL_BUILD_TYPE "unknown") +ENDIF (RBDL_STORE_VERSION) + +CONFIGURE_FILE ( + "${CMAKE_CURRENT_SOURCE_DIR}/include/rbdl/rbdl_config.h.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/include/rbdl/rbdl_config.h" + ) + +# Python wrapper +IF (RBDL_BUILD_PYTHON_WRAPPER) + add_subdirectory ( python ) + +ENDIF (RBDL_BUILD_PYTHON_WRAPPER) + +# Installation +FILE ( GLOB headers + ${CMAKE_CURRENT_SOURCE_DIR}/include/rbdl/*.h + ${CMAKE_CURRENT_BINARY_DIR}/include/rbdl/rbdl_config.h + ) + +INSTALL ( FILES ${headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rbdl ) + +# Setup of SimpleMath install settings +IF (RBDL_USE_SIMPLE_MATH) + INSTALL ( DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/rbdl/SimpleMath" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rbdl + ) +ENDIF (RBDL_USE_SIMPLE_MATH) + +# pkg-config +CONFIGURE_FILE ( + ${CMAKE_CURRENT_SOURCE_DIR}/rbdl.pc.cmake + ${CMAKE_CURRENT_BINARY_DIR}/rbdl.pc @ONLY + ) +INSTALL ( + FILES ${CMAKE_CURRENT_BINARY_DIR}/rbdl.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig + ) + +# Packaging +SET(CPACK_GENERATOR "DEB") +SET(CPACK_DEBIAN_PACKAGE_MAINTAINER "Martin Felis ") +SET(CPACK_PACKAGE_DESCRIPTION_SUMMARY "the Rigid Body Dynamics Library (RBDL)") +SET(CPACK_PACKAGE_VENDOR "Martin Felis") +SET(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md") +SET(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +SET(CPACK_PACKAGE_VERSION_MAJOR ${RBDL_VERSION_MAJOR}) +SET(CPACK_PACKAGE_VERSION_MINOR ${RBDL_VERSION_MINOR}) +SET(CPACK_PACKAGE_VERSION_PATCH ${RBDL_VERSION_PATCH}) +SET(CPACK_PACKAGE_INSTALL_DIRECTORY "CPACK_PACKAGE ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}") +SET(CPACK_PACKAGE_FILE_NAME "rbdl-${CMAKE_LIBRARY_ARCHITECTURE}-${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}") +SET(CPACK_PACKAGE_EXECUTABLES "rbdl_luamodel_util;RBDL Lua Model Utility") + +INCLUDE(CPack) diff --git a/3rdparty/rbdl/CONTRIBUTING.md b/3rdparty/rbdl/CONTRIBUTING.md new file mode 100644 index 0000000..ca9429b --- /dev/null +++ b/3rdparty/rbdl/CONTRIBUTING.md @@ -0,0 +1,209 @@ +# Coding style for RBDL + +This documents gives an overview of the coding style used in RBDL and also +the general goals of RBDL. + +If you are considering contributing to this library please read the whole +document. + +## General Purpose of RBDL + +RBDL implements large parts of the algorithms and methods described in +Featherstone's book Rigid Body Dynamics Algorithms. One of the main goals +of this library is to serve as an efficient implementation for the methods +described in Featherstone's book and to a lesser extent general multibody +dynamics and kinematics computations. A person who is familiar with +Featherstone's book should have an easy time to understand the code and +therefore the variable naming conventions used in the book should when +possible used in the code, e.g.: + + - the joint space inertia matrix is denoted with H + - the coriolis forces are denoted with C + +The algorithmic parts of RBDL's code try to follow the algorithmic or +mathematical notations instead of wrapping algorithms in elegant +programming patterns. + +### Aims and Non-Aims of RBDL + +This is what RBDL aims to be: + +* RBDL aims to be lean +* RBDL aims to be easily integrated into other projects +* RBDL aims to be suitable as a foundation for sophisticated dynamics packages +* RBDL gives you access to its internals and provides only a thin abstraction layer over the actual computation +* RBDL aims to be self-contained and dependant on as few libraries as possible + +And this is what RBDL is ***not*** about: + +* RBDL is ***not*** a fully fledged simulator with collision detection or fancy graphics +* RBDL does not keep you from screwing up things. + +Multibody dynamics is a complicated subject and in this codebase the +preference is mathematical and algorithmic clarity over elegant software +architecture. + +## Licensing + +RBDL is published under the very permissive zlib license that gives you a +lot of freedom in the use of full library or parts of it. The core part +of the library is solely using this license but addons may use different +licenses. + +There is no formal contributor license agreement for this project. Instead +when you submit patches or create a pull request it is assumed that you +have the rights to transfer the corresponding code to the RBDL project and +that you are okay that the code will be published as part of RBDL. + +## Data Storage + +RBDL tries to avoid dynamic allocations and prefers contiguous memory +storage such as in ```std::vector```s over possibly fragmented memory as in +```std::list``` or heap allocated tree structures. + +Where possible we use the Structure-of-Arrays (SOA) approach to store data, +e.g. the velocities v of all bodies is stored in an array (```std::vector```) +of ```SpatialVector```s in the ```Model``` structure. + +## Naming Conventions + +1. Structs and classes use CamelCase, e.g. ```ConstraintSet``` +2. Struct and class members use the lowerCamelCase convention, e.g. + ```Model::dofCount```. + Exceptions are: + 1. The member variable is a mathematical symbol in an + algorithm reference, E.g. ```S``` is commonly used to denote the joint + motion subspace, then we use the algorithm notation. For mathematical + symbols we also allow the underscore ```_``` to denote a subscript. + 2. Specializations of existing variables may be prefixed with an identifier, + followed by a underscore. E.g. ```Model::S``` is the default storage for + joint motion subspaces, however for the specialized 3-DOF joints it uses + the prefix ```multdof3_``` and are therefore stored in + ```Model::multdof3_S```. +3. Only the first letter of an acronym is using a capital letter, e.g. + degree of freedom (DOF) would be used as ```jointDofCount```, or + ```dofCount```. +4. Variables that are not member variables use the ```snake_case``` convention. + +### Examples + + struct Model { + std::vector v; // ok, v is an + std::vector S; // ok, S is commonly used in a reference algorithm + std::vector u; // ok + std::vector multdof3_u; // ok, 3-dof specialization of Model::u + + std::vector mJointIndex; // NOT OK: invalid prefix + unsigned int DOFCount; // NOT OK: only first letter of abbreviation should be in upper case + double error_tol; // NOT OK: use camelCase instead of snake_case + void CalcPositions(); // NOT OK: camelCase for member variables and function must start with lower-case name + + }; + +## Spacing and Line Width + +We use spaces to indent code and use two spaces to indent a block. Do not +use tabs. Namespaces should not be indented. + +Lines should not exceed 80 characters in width. + +Hint: in the root directory of the RBDL repository you find the file +.editorconfig which uses the correct spacing settings automatically for +many editors (see http://editorconfig.org/ for more details). + +## Error Handling + +RBDL will fail loudly and abort if an error occurs. This allows you to spot +errors early on. RBDL does not use exceptions. + +Code must compile without warnings with all compiler warnings enabled. +Please also consider checking code with static code analyzers such as +clang-analyzer (http://clang-analyzer.llvm.org/). + +## Const Correctness + +This code uses const correctness, i.e. parameters that are not expected to +change must be specified as const. Use const references whenever possible. +For some optional variables we use pointers, but when possible use +references. + +## Braces + +Use braces whenever possible. E.g. even there is only a single line of code +after an if-statement wrap it with curly braces. The opening brace starts +in the same line as the ```if``` or the ```else``` statement. + +## Documentation + +Most importantly the code should be readable to someone who is familiar +with multibody dynamics, especially with Featherstone's notation. The +documentation should mainly serve to clarify the API in terms of doxygen +comments. Within the code itself comments may be used to emphasize on ideas +behind it or to clarify sections. But in general it is best to write +readable code in the first place as comments easily become deprecated. + +The doxygen comments should be written in the header files and not in the +```.cc``` files. + +## Testing + +All code contributions must provide unit tests. RBDL uses UnitTest++ +(https://github.com/unittest-cpp/unittest-cpp) as a testing framework. Many +small tests that check single features are preferred over large tests that +test multiple things simultaneously. + +Bugfixes ideally come with a test case that reproduce the bug. + +## Branching and Bookmarks + +RBDL uses Mercurial (https://mercurial-scm.org) as version control system. +The branching concept of mercurial is different than in git. Git's +branching concept is captured in mercurial using bookmarks +(https://www.mercurial-scm.org/wiki/Bookmarks). + +The ```default``` branch is reserved for releases. Bugfixes and +contributions happen in the ```dev``` branch but should have a bookmark +assigned to them. + +Please do not create new branches, i.e. do not run ```hg branch +```. + +### Working on a new feature + +The following steps are advised when working on a new feature for RBDL: + +1. Clone the official repository. +2. Switch to the ```dev``` branch. +3. Create a bookmark that describes that feature preferably in a single + word. +4. Commit all changes to this bookmark in the ```dev``` branch. +5. Publish your work online and notify the RBDL maintainer(s). + +Here are the commands to perform steps 1.-4.: + + # Step 1: clone official repository + hg clone https://bitbucket.org/rbdl/rbdl && cd + + # Step 2: switch to the dev branch + hg update dev + + # Step 3: create a bookmark + hg bookmark newfeature + + # ... + # Make changes + # ... + + # Step 4: commit changes + hg commit + +For step 5 the easiest would be to push your changes to a fork of the +official repository on bitbucket and create a pull request. + +## Debugging + +* Todo: mention logging facility +* Todo: mention SimpleMath as a fast-compiling (but slower runtime) linear +algebra package. + + diff --git a/3rdparty/rbdl/Doxyfile b/3rdparty/rbdl/Doxyfile new file mode 100644 index 0000000..1d2b380 --- /dev/null +++ b/3rdparty/rbdl/Doxyfile @@ -0,0 +1,1837 @@ +# Doxyfile 1.7.6.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Rigid Body Dynamics Library" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = ./doc + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = YES + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 2 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = YES + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ./src \ + include/rbdl \ + doc/api_changes.txt \ + doc/example.h \ + doc/python_example.h \ + doc/Mainpage.h \ + doc/images \ + doc/luamodel_example.h \ + addons/luamodel/luamodel.h \ + addons/urdfreader/rbdl_urdfreader.h \ + addons/urdfreader/urdfreader.h \ + addons/geometry/Function.h \ + addons/geometry/SegmentedQuinticBezierToolkit.h \ + addons/geometry/SmoothSegmentedFunction.h \ + addons/muscle/Millard2016TorqueMuscle.h \ + addons/muscle/MuscleFunctionFactory.h \ + addons/muscle/TorqueMuscleFunctionFactory.h \ + ./include/rbdl + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.f90 \ + *.f \ + *.vhd \ + *.vhdl + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = ./include/rbdl/SimpleMath + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = doc \ + examples/luamodel \ + examples/simple \ + examples/python + + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = doc/images \ + doc \ + doc/logo \ + + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = YES + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 3 + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 248 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 12 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = amsmath xr amsfonts + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + diff --git a/3rdparty/rbdl/LICENSE b/3rdparty/rbdl/LICENSE new file mode 100644 index 0000000..e1fcd42 --- /dev/null +++ b/3rdparty/rbdl/LICENSE @@ -0,0 +1,23 @@ +RBDL - Rigid Body Dynamics Library +Copyright (c) 2011-2016 Martin Felis + +(zlib license) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/3rdparty/rbdl/README.md b/3rdparty/rbdl/README.md new file mode 100644 index 0000000..6289abe --- /dev/null +++ b/3rdparty/rbdl/README.md @@ -0,0 +1,171 @@ +RBDL - Rigid Body Dynamics Library +Copyright (c) 2011-2016 Martin Felis + +Introduction +============ + +RBDL is a highly efficient C++ library that contains some essential rigid +body dynamics algorithms such as the Articulated Body Algorithm (ABA) for +forward dynamics, Recursive Newton-Euler Algorithm (RNEA) for inverse +dynamics and the Composite Rigid Body Algorithm (CRBA) for the efficient +computation of the joint space inertia matrix. It further contains code for +Jacobians, forward and inverse kinematics, and handling of external +constraints such as contacts and collisions. + +The code is developed by Martin Felis +at the research group [Optimization in Robotics and Biomechanics +(ORB)](http://orb.iwr.uni-heidelberg.de) of the [Interdisciplinary Center +for Scientific Computing (IWR)](http://www.iwr.uni-heidelberg.de) at +[Heidelberg University](http://www.uni-heidelberg.de). The code tightly +follows the notation used in Roy Featherstone''s book "Rigid Body Dynamics +Algorithm". + +Recent Changes +============== + * 28 April 2016: Nev version 2.5.0: + * Added an experimental Cython based Python wrapper of RBDL. The API is + very close to the C++ API. For a brief glimpse of the API see the file + python/test_wrapper.py. + * Matthew Millard added CustomJoints which allow to create different joint + types completely by user code. They are implemented as proxy joints for + which their behaviour is specified using virtual functions. + * Added CalcMInvTimesTau() that evaluates multiplication of the inverse of + the joint space inertia matrix with a vector in O(n) time. + * Added JointTypeFloatingBase which uses TX,TY,TZ and a spherical joint for + the floating base joint. + * Loading of floating base URDF models must now be specified as a third + parameter to URDFReadFromFile() and URDFReadFromString() + * Added the URDF code from Bullet3 which gets used when ROS is not found. + Otherwise use the URDF libraries found via Catkin. + * Added CalcPointVelocity6D, CalcPointAcceleration6D, and CalcPointJacobian6D + that compute both linear and angular quantities + * Removed Model::SetFloatingBase (body). Use a 6-DoF joint or + JointTypeFloatingBase instead. + * Fixed building issues when building DLL with MSVC++. + * 20 April 2016: New version 2.4.1: + * This is a bugfix release that maintains binary compatibility and only fixes + erroneous behaviour. + * critical: fixed termination criterion for InverseKinematics. The termination + criterion would be evaluated too early and thus report convergence too + early. This was reported independently by Kevin Stein, Yun Fei, and Davide + Corradi. Thanks for the reports! + * critical: fixed CompositeRigidBodyAlgorithm when using spherical joints + (thanks to Sébastien Barthélémy for reporting!) + * 23 February 2015: New version 2.4.0: + * Added sparse range-space method ForwardDynamicsContactsRangeSpaceSparse() + and ComputeContactImpulsesRangeSpaceSparse() + * Added null-space method ForwardDynamicsContactsNullSpace() + and ComputeContactImpulsesNullSpace() + * Renamed ForwardDynamicsContactsLagrangian() to + ForwardDynamicsContactsDirect() and + ComputeContactImpulsesLagrangian() to ComputeContactImpulsesDirect() + * Renamed ForwardDynamicsContacts() to ForwardDynamicsContactsKokkevis() + * Removed/Fixed CalcAngularMomentum(). The function produced wrong values. The + functionality has been integrated into CalcCenterOfMass(). + * CalcPointJacobian() does not clear the argument of the result anymore. + Caller has to ensure that the matrix was set to zero before using this + function. + * Added optional workspace parameters for ForwardDynamicsLagrangian() to + optionally reduce memory allocations + * Added JointTypeTranslationXYZ, JointTypeEulerXYZ, and JointTypeEulerYXZ + which are equivalent to the emulated multidof joints but faster. + * Added optional parameter to CalcCenterOfMass to compute angular momentum. + * Added CalcBodySpatialJacobian() + * Added CalcContactJacobian() + * Added NonlinearEffects() + * Added solving of linear systems using standard Householder QR + * LuaModel: Added LuaModelReadFromLuaState() + * URDFReader: Fixed various issues and using faster joints for floating + base models + * Various performance improvements + +For a complete history see doc/api_changes.txt. + +Documentation +============= + +The documentation is contained in the code and can be extracted with the +tool [doxygen](http://www.doxygen.org). + +To create the documentation simply run + + doxygen Doxyfile + +which will generate the documentation in the subdirectory ./doc/html. The +main page will then be located in ./doc/html/index.html. + +An online version of the generated documentation can be found at +[http://rbdl.bitbucket.org](http://rbdl.bitbucket.org). + +Getting RBDL +============ + +The latest stable code can be obtained from + + https://bitbucket.org/rbdl/rbdl/get/default.zip + +The official mercurial repository can be cloned from + + https://bitbucket.org/rbdl/rbdl + +(See [http://mercurial.selenic.com/](http://mercurial.selenic.com) for +mercurial clients.) + +Building and Installation +========================= + +The RBDL is built using CMake +([http://www.cmake.org](http://www.cmake.org)). To compile the library in +a separate directory in Release mode use: + + mkdir build + cd build/ + cmake -D CMAKE_BUILD_TYPE=Release ../ + make + +For optimal performance it is highly recommended to install the Eigen3 +linear algebra library from +[http://eigen.tuxfamily.org](http://eigen.tuxfamily.org). RBDL also +comes with a simple, albeit much slower math library (SimpleMath) that can +be used by enabling `RBDL_USE_SIMPLE_MATH`, i.e.: + + cmake -D RBDL_USE_SIMPLE_MATH=TRUE ../ + +Licensing +========= + +The library is published under the very permissive zlib free software +license which should allow you to use the software wherever you need. + +This is the full license text (zlib license): + + RBDL - Rigid Body Dynamics Library + Copyright (c) 2011-2016 Martin Felis + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + +Acknowledgements +================ + +Work on this library was originally funded by the [Heidelberg Graduate +School of Mathematical and Computational Methods for the Sciences +(HGS)](http://hgs.iwr.uni-heidelberg.de/hgs.mathcomp/), and the European +FP7 projects [ECHORD](http://echord.eu) (grant number 231143) and +[Koroibot](http://koroibot.eu) (grant number 611909). diff --git a/3rdparty/rbdl/addons/benchmark/CMakeLists.txt b/3rdparty/rbdl/addons/benchmark/CMakeLists.txt new file mode 100644 index 0000000..fd79df0 --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/CMakeLists.txt @@ -0,0 +1,60 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +CMAKE_POLICY(SET CMP0048 NEW) + +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake ) + +INCLUDE_DIRECTORIES ( + ${CMAKE_CURRENT_BINARY_DIR}/include/rbdl + ) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX + ) + +# Perform the proper linking +SET (CMAKE_SKIP_BUILD_RPATH FALSE) +SET (CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) +SET (CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +SET (CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# Options +SET ( BENCHMARK_SOURCES + model_generator.cc + Human36Model.cc + benchmark.cc + ) + +ADD_EXECUTABLE ( benchmark ${BENCHMARK_SOURCES} ) + +IF (RBDL_BUILD_STATIC) + SET (LIBRARIES rbdl-static) + + IF (RBDL_BUILD_ADDON_LUAMODEL) + SET (LIBRARIES ${LIBRARIES} rbdl_luamodel-static) + ENDIF (RBDL_BUILD_ADDON_LUAMODEL) + + IF (RBDL_BUILD_ADDON_URDFREADER) + SET (LIBRARIES ${LIBRARIES} rbdl_urdfreader-static) + ENDIF (RBDL_BUILD_ADDON_URDFREADER) + + TARGET_LINK_LIBRARIES ( benchmark + rbdl-static + ${LIBRARIES} + ) +ELSE (RBDL_BUILD_STATIC) + SET (LIBRARIES rbdl) + + IF (RBDL_BUILD_ADDON_LUAMODEL) + SET (LIBRARIES ${LIBRARIES} rbdl_luamodel) + ENDIF (RBDL_BUILD_ADDON_LUAMODEL) + + IF (RBDL_BUILD_ADDON_URDFREADER) + SET (LIBRARIES ${LIBRARIES} rbdl_urdfreader) + ENDIF (RBDL_BUILD_ADDON_URDFREADER) + + TARGET_LINK_LIBRARIES ( benchmark + rbdl + ${LIBRARIES} + ) +ENDIF (RBDL_BUILD_STATIC) diff --git a/3rdparty/rbdl/addons/benchmark/Human36Model.cc b/3rdparty/rbdl/addons/benchmark/Human36Model.cc new file mode 100644 index 0000000..565daf4 --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/Human36Model.cc @@ -0,0 +1,158 @@ +#include "Human36Model.h" + +#include "rbdl/rbdl.h" + +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +enum SegmentName { + SegmentPelvis = 0, + SegmentThigh, + SegmentShank, + SegmentFoot, + SegmentMiddleTrunk, + SegmentUpperTrunk, + SegmentUpperArm, + SegmentLowerArm, + SegmentHand, + SegmentHead, + SegmentNameLast +}; + +double SegmentLengths[SegmentNameLast] = { + 0.1457, + 0.4222, + 0.4403, + 0.1037, + 0.2155, + 0.2421, + 0.2817, + 0.2689, + 0.0862, + 0.2429 +}; + +double SegmentMass[SegmentNameLast] = { + 0.8154, + 10.3368, + 3.1609, + 1.001, + 16.33, + 15.96, + 1.9783, + 1.1826, + 0.4453, + 5.0662 +}; + +double SegmentCOM[SegmentNameLast][3] = { + { 0., 0., 0.0891}, + { 0., 0., -0.1729}, + { 0., 0., -0.1963}, + { 0.1254, 0., -0.0516}, + { 0., 0., 0.1185}, + { 0., 0., 0.1195}, + { 0., 0., -0.1626}, + { 0., 0., -0.1230}, + { 0., 0., -0.0680}, + { 0., 0., 1.1214} +}; + +double SegmentRadiiOfGyration[SegmentNameLast][3] = { + { 0.0897, 0.0855, 0.0803}, + { 0.1389, 0.0629, 0.1389}, + { 0.1123, 0.0454, 0.1096}, + { 0.0267, 0.0129, 0.0254}, + { 0.0970, 0.1009, 0.0825}, + { 0.1273, 0.1172, 0.0807}, + { 0.0803, 0.0758, 0.0445}, + { 0.0742, 0.0713, 0.0325}, + { 0.0541, 0.0442, 0.0346}, + { 0.0736, 0.0634, 0.0765} +}; + +Body create_body (SegmentName segment) { + Matrix3d inertia_C (Matrix3d::Zero()); + inertia_C(0,0) = pow(SegmentRadiiOfGyration[segment][0] * SegmentLengths[segment], 2) * SegmentMass[segment]; + inertia_C(1,1) = pow(SegmentRadiiOfGyration[segment][1] * SegmentLengths[segment], 2) * SegmentMass[segment]; + inertia_C(2,2) = pow(SegmentRadiiOfGyration[segment][2] * SegmentLengths[segment], 2) * SegmentMass[segment]; + + return RigidBodyDynamics::Body ( + SegmentMass[segment], + RigidBodyDynamics::Math::Vector3d ( + SegmentCOM[segment][0], + SegmentCOM[segment][1], + SegmentCOM[segment][2] + ), + inertia_C); +} + +void generate_human36model (RigidBodyDynamics::Model *model) { + Body pelvis_body = create_body (SegmentPelvis); + Body thigh_body = create_body (SegmentThigh); + Body shank_body = create_body (SegmentShank); + Body foot_body = create_body (SegmentFoot); + Body middle_trunk_body = create_body (SegmentMiddleTrunk); + Body upper_trunk_body = create_body (SegmentUpperTrunk); + Body upperarm_body = create_body (SegmentUpperArm); + Body lowerarm_body = create_body (SegmentLowerArm); + Body hand_body = create_body (SegmentHand); + Body head_body = create_body (SegmentHead); + + Joint free_flyer ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + + Joint rot_yxz ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + + Joint rot_yz ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + + Joint rot_y ( + SpatialVector (0., 1., 0., 0., 0., 0.) + ); + + Joint fixed (JointTypeFixed); + + model->gravity = Vector3d (0., 0., -9.81); + + unsigned int pelvis_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), free_flyer, pelvis_body, "pelvis"); + + // right leg + model->AddBody (pelvis_id, Xtrans(Vector3d(0., -0.0872, 0.)), rot_yxz, thigh_body, "thigh_r"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentThigh])), rot_y, shank_body, "shank_r"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentShank])), rot_yz, foot_body, "foot_r"); + + // left leg + model->AddBody (pelvis_id, Xtrans(Vector3d(0., 0.0872, 0.)), rot_yxz, thigh_body, "thigh_l"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentThigh])), rot_y, shank_body, "shank_l"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentShank])), rot_yz, foot_body, "foot_l"); + + // trunk + model->AddBody (pelvis_id, Xtrans(Vector3d(0., 0., SegmentLengths[SegmentPelvis])), rot_yxz, middle_trunk_body, "middletrunk"); + unsigned int uppertrunk_id = model->AppendBody (Xtrans(Vector3d(0., 0., SegmentLengths[SegmentMiddleTrunk])), fixed, upper_trunk_body, "uppertrunk"); + + // right arm + model->AddBody (uppertrunk_id, Xtrans(Vector3d(0., -0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz, upperarm_body, "upperarm_r"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentUpperArm])), rot_y, lowerarm_body, "lowerarm_r"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentLowerArm])), rot_yz, hand_body, "hand_r"); + + // left arm + model->AddBody (uppertrunk_id, Xtrans(Vector3d(0., 0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz, upperarm_body, "upperarm_l"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentUpperArm])), rot_y, lowerarm_body, "lowerarm_l"); + model->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentLowerArm])), rot_yz, hand_body, "hand_l"); + + // head + model->AddBody (uppertrunk_id, Xtrans(Vector3d(0., 0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz, upperarm_body, "head"); +} diff --git a/3rdparty/rbdl/addons/benchmark/Human36Model.h b/3rdparty/rbdl/addons/benchmark/Human36Model.h new file mode 100644 index 0000000..9d95d32 --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/Human36Model.h @@ -0,0 +1,11 @@ +#ifndef _HUMAN36MODEL_H +#define _HUMAN36MODEL_H + +namespace RigidBodyDynamics { +class Model; +} + +void generate_human36model (RigidBodyDynamics::Model *model); + +/* _HUMAN36MODEL_H */ +#endif diff --git a/3rdparty/rbdl/addons/benchmark/SampleData.h b/3rdparty/rbdl/addons/benchmark/SampleData.h new file mode 100644 index 0000000..3bc75c9 --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/SampleData.h @@ -0,0 +1,85 @@ +#ifndef _SAMPLE_DATA_H +#define _SAMPLE_DATA_H + +struct SampleData { + SampleData() : + count (0), q(NULL), qdot(NULL), qddot(NULL), tau(NULL) + {} + ~SampleData() { + deleteData(); + } + SampleData(const SampleData &data) { + count = data.count; + + q = new RigidBodyDynamics::Math::VectorNd[count]; + qdot = new RigidBodyDynamics::Math::VectorNd[count]; + qddot = new RigidBodyDynamics::Math::VectorNd[count]; + tau = new RigidBodyDynamics::Math::VectorNd[count]; + + for (int si = 0; si < count; si++) { + q[si] = data.q[si]; + qdot[si] = data.qdot[si]; + qddot[si] = data.qddot[si]; + tau[si] = data.tau[si]; + } + } + SampleData& operator= (const SampleData &data) { + if (this != &data) { + deleteData(); + *this = SampleData (data); + } + return *this; + } + + unsigned int count; + RigidBodyDynamics::Math::VectorNd *q; + RigidBodyDynamics::Math::VectorNd *qdot; + RigidBodyDynamics::Math::VectorNd *qddot; + RigidBodyDynamics::Math::VectorNd *tau; + + void deleteData() { + count = 0; + + if (q) + delete[] q; + q = NULL; + + if (qdot) + delete[] qdot; + qdot = NULL; + + if (qddot) + delete[] qddot; + qddot = NULL; + + if (tau) + delete[] tau; + tau = NULL; + } + + void fillRandom (int dof_count, int sample_count) { + deleteData(); + count = sample_count; + + q = new RigidBodyDynamics::Math::VectorNd[count]; + qdot = new RigidBodyDynamics::Math::VectorNd[count]; + qddot = new RigidBodyDynamics::Math::VectorNd[count]; + tau = new RigidBodyDynamics::Math::VectorNd[count]; + + for (int si = 0; si < count; si++) { + q[si].resize (dof_count); + qdot[si].resize (dof_count); + qddot[si].resize (dof_count); + tau[si].resize (dof_count); + + for (int i = 0; i < dof_count; i++) { + q[si][i] = (static_cast(rand()) / static_cast(RAND_MAX)) * 2. -1.; + qdot[si][i] = (static_cast(rand()) / static_cast(RAND_MAX)) * 2. -1.; + qddot[si][i] = (static_cast(rand()) / static_cast(RAND_MAX)) * 2. -1.; + tau[si][i] = (static_cast(rand()) / static_cast(RAND_MAX)) * 2. -1.; + } + } + } +}; + +#endif diff --git a/3rdparty/rbdl/addons/benchmark/Timer.h b/3rdparty/rbdl/addons/benchmark/Timer.h new file mode 100644 index 0000000..2656f64 --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/Timer.h @@ -0,0 +1,29 @@ +#ifndef _TIMER_H +#define _TIMER_H + +#include + +struct TimerInfo { + /// time stamp when timer_start() gets called + clock_t clock_start_value; + + /// time stamp when the timer was stopped + clock_t clock_end_value; + + /// duration between clock_start_value and clock_end_value in seconds + double duration_sec; +}; + +inline void timer_start (TimerInfo *timer) { + timer->clock_start_value = clock(); +} + +inline double timer_stop (TimerInfo *timer) { + timer->clock_end_value = clock(); + + timer->duration_sec = static_cast(timer->clock_end_value - timer->clock_start_value) * 1 / CLOCKS_PER_SEC; + + return timer->duration_sec; +} + +#endif diff --git a/3rdparty/rbdl/addons/benchmark/benchmark.cc b/3rdparty/rbdl/addons/benchmark/benchmark.cc new file mode 100644 index 0000000..db44ecd --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/benchmark.cc @@ -0,0 +1,879 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "rbdl/rbdl.h" +#include "model_generator.h" +#include "Human36Model.h" +#include "SampleData.h" +#include "Timer.h" + +#ifdef RBDL_BUILD_ADDON_LUAMODEL +#include "../addons/luamodel/luamodel.h" +bool have_luamodel = true; +#else +bool have_luamodel = false; +#endif + +#ifdef RBDL_BUILD_ADDON_URDFREADER +#include "../addons/urdfreader/urdfreader.h" +bool have_urdfreader = true; +bool urdf_floating_base = false; +#else +bool have_urdfreader = false; +#endif + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +int benchmark_sample_count = 1000; +int benchmark_model_max_depth = 5; + +bool benchmark_run_fd_aba = true; +bool benchmark_run_fd_lagrangian = true; +bool benchmark_run_id_rnea = true; +bool benchmark_run_crba = true; +bool benchmark_run_nle = true; +bool benchmark_run_calc_minv_times_tau = true; +bool benchmark_run_contacts = false; +bool benchmark_run_ik = true; + +string model_file = ""; + +enum ContactsMethod { + ContactsMethodLagrangian = 0, + ContactsMethodRangeSpaceSparse, + ContactsMethodNullSpace, + ContactsMethodKokkevis +}; + +double run_forward_dynamics_ABA_benchmark (Model *model, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + ForwardDynamics (*model, + sample_data.q[i], + sample_data.qdot[i], + sample_data.tau[i], + sample_data.qddot[i]); + } + + double duration = timer_stop (&tinfo); + + cout << "#DOF: " << setw(3) << model->dof_count + << " #samples: " << sample_count + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + return duration; +} + +double run_forward_dynamics_lagrangian_benchmark (Model *model, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + MatrixNd H (MatrixNd::Zero(model->dof_count, model->dof_count)); + VectorNd C (VectorNd::Zero(model->dof_count)); + + for (int i = 0; i < sample_count; i++) { + ForwardDynamicsLagrangian (*model, + sample_data.q[i], + sample_data.qdot[i], + sample_data.tau[i], + sample_data.qddot[i], + Math::LinearSolverPartialPivLU, + NULL, + &H, + &C + ); + } + + double duration = timer_stop (&tinfo); + + cout << "#DOF: " << setw(3) << model->dof_count + << " #samples: " << sample_count + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + return duration; +} + +double run_inverse_dynamics_RNEA_benchmark (Model *model, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + InverseDynamics (*model, + sample_data.q[i], + sample_data.qdot[i], + sample_data.qddot[i], + sample_data.tau[i] + ); + } + + double duration = timer_stop (&tinfo); + + cout << "#DOF: " << setw(3) << model->dof_count + << " #samples: " << sample_count + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + return duration; +} + +double run_CRBA_benchmark (Model *model, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + Math::MatrixNd H = Math::MatrixNd::Zero(model->dof_count, model->dof_count); + Math::MatrixNd identity = Math::MatrixNd::Identity(model->dof_count, model->dof_count); + Math::MatrixNd Hinv = Math::MatrixNd::Zero(model->dof_count, model->dof_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + CompositeRigidBodyAlgorithm (*model, sample_data.q[i], H, true); + } + + double duration = timer_stop (&tinfo); + + cout << "#DOF: " << setw(3) << model->dof_count + << " #samples: " << sample_count + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + return duration; +} + +double run_nle_benchmark (Model *model, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + NonlinearEffects (*model, + sample_data.q[i], + sample_data.qdot[i], + sample_data.tau[i] + ); + } + + double duration = timer_stop (&tinfo); + + cout << "#DOF: " << setw(3) << model->dof_count + << " #samples: " << sample_count + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + return duration; +} + +double run_calc_minv_times_tau_benchmark (Model *model, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + CalcMInvTimesTau (*model, sample_data.q[0], sample_data.tau[0], sample_data.qddot[0]); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + CalcMInvTimesTau (*model, sample_data.q[i], sample_data.tau[i], sample_data.qddot[i]); + } + + double duration = timer_stop (&tinfo); + + cout << "#DOF: " << setw(3) << model->dof_count + << " #samples: " << sample_count + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + return duration; +} + +double run_contacts_lagrangian_benchmark (Model *model, ConstraintSet *constraint_set, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + ForwardDynamicsConstraintsDirect (*model, sample_data.q[i], sample_data.qdot[i], sample_data.tau[i], *constraint_set, sample_data.qddot[i]); + } + + double duration = timer_stop (&tinfo); + + return duration; +} + +double run_contacts_lagrangian_sparse_benchmark (Model *model, ConstraintSet *constraint_set, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + ForwardDynamicsConstraintsRangeSpaceSparse (*model, sample_data.q[i], sample_data.qdot[i], sample_data.tau[i], *constraint_set, sample_data.qddot[i]); + } + + double duration = timer_stop (&tinfo); + + return duration; +} + +double run_contacts_null_space (Model *model, ConstraintSet *constraint_set, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + ForwardDynamicsConstraintsNullSpace (*model, sample_data.q[i], sample_data.qdot[i], sample_data.tau[i], *constraint_set, sample_data.qddot[i]); + } + + double duration = timer_stop (&tinfo); + + return duration; +} + +double run_contacts_kokkevis_benchmark (Model *model, ConstraintSet *constraint_set, int sample_count) { + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + TimerInfo tinfo; + timer_start (&tinfo); + + for (int i = 0; i < sample_count; i++) { + ForwardDynamicsContactsKokkevis(*model, sample_data.q[i], sample_data.qdot[i], sample_data.tau[i], *constraint_set, sample_data.qddot[i]); + } + + double duration = timer_stop (&tinfo); + + return duration; +} + +double contacts_benchmark (int sample_count, ContactsMethod contacts_method) { + // initialize the human model + Model *model = new Model(); + generate_human36model(model); + + // initialize the constraint sets + unsigned int foot_r = model->GetBodyId ("foot_r"); + unsigned int foot_l = model->GetBodyId ("foot_l"); + unsigned int hand_r = model->GetBodyId ("hand_r"); + unsigned int hand_l = model->GetBodyId ("hand_l"); + + ConstraintSet one_body_one_constraint; + ConstraintSet two_bodies_one_constraint; + ConstraintSet four_bodies_one_constraint; + + ConstraintSet one_body_four_constraints; + ConstraintSet two_bodies_four_constraints; + ConstraintSet four_bodies_four_constraints; + + LinearSolver linear_solver = LinearSolverPartialPivLU; + + one_body_one_constraint.linear_solver = linear_solver; + two_bodies_one_constraint.linear_solver = linear_solver; + four_bodies_one_constraint.linear_solver = linear_solver; + one_body_four_constraints.linear_solver = linear_solver; + two_bodies_four_constraints.linear_solver = linear_solver; + four_bodies_four_constraints.linear_solver = linear_solver; + + // one_body_one + one_body_one_constraint.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + one_body_one_constraint.Bind (*model); + + // two_bodies_one + two_bodies_one_constraint.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + two_bodies_one_constraint.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + two_bodies_one_constraint.Bind (*model); + + // four_bodies_one + four_bodies_one_constraint.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_one_constraint.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_one_constraint.AddContactConstraint (hand_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_one_constraint.AddContactConstraint (hand_l, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_one_constraint.Bind (*model); + + // one_body_four + one_body_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + one_body_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + one_body_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + one_body_four_constraints.AddContactConstraint (foot_r, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + one_body_four_constraints.Bind (*model); + + // two_bodies_four + two_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + two_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + two_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + two_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + two_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + two_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + two_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + two_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + two_bodies_four_constraints.Bind (*model); + + // four_bodies_four + four_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + four_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + four_bodies_four_constraints.AddContactConstraint (foot_r, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + four_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + four_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + four_bodies_four_constraints.AddContactConstraint (foot_l, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + four_bodies_four_constraints.AddContactConstraint (hand_r, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_four_constraints.AddContactConstraint (hand_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + four_bodies_four_constraints.AddContactConstraint (hand_r, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + four_bodies_four_constraints.AddContactConstraint (hand_r, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + four_bodies_four_constraints.AddContactConstraint (hand_l, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + four_bodies_four_constraints.AddContactConstraint (hand_l, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + four_bodies_four_constraints.AddContactConstraint (hand_l, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + four_bodies_four_constraints.AddContactConstraint (hand_l, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + four_bodies_four_constraints.Bind (*model); + + cout << "= #DOF: " << setw(3) << model->dof_count << endl; + cout << "= #samples: " << sample_count << endl; + cout << "= No constraints (Articulated Body Algorithm):" << endl; + run_forward_dynamics_ABA_benchmark (model, sample_count); + cout << "= Constraints:" << endl; + double duration; + + // one body one + if (contacts_method == ContactsMethodLagrangian) { + duration = run_contacts_lagrangian_benchmark (model, &one_body_one_constraint, sample_count); + } else if (contacts_method == ContactsMethodRangeSpaceSparse) { + duration = run_contacts_lagrangian_sparse_benchmark (model, &one_body_one_constraint, sample_count); + } else if (contacts_method == ContactsMethodNullSpace) { + duration = run_contacts_null_space (model, &one_body_one_constraint, sample_count); + } else { + duration = run_contacts_kokkevis_benchmark (model, &one_body_one_constraint, sample_count); + } + + cout << "ConstraintSet: 1 Body 1 Constraint : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + // two_bodies_one + if (contacts_method == ContactsMethodLagrangian) { + duration = run_contacts_lagrangian_benchmark (model, &two_bodies_one_constraint, sample_count); + } else if (contacts_method == ContactsMethodRangeSpaceSparse) { + duration = run_contacts_lagrangian_sparse_benchmark (model, &two_bodies_one_constraint, sample_count); + } else if (contacts_method == ContactsMethodNullSpace) { + duration = run_contacts_null_space (model, &two_bodies_one_constraint, sample_count); + } else { + duration = run_contacts_kokkevis_benchmark (model, &two_bodies_one_constraint, sample_count); + } + + cout << "ConstraintSet: 2 Bodies 1 Constraint : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + + // four_bodies_one + if (contacts_method == ContactsMethodLagrangian) { + duration = run_contacts_lagrangian_benchmark (model, &four_bodies_one_constraint, sample_count); + } else if (contacts_method == ContactsMethodRangeSpaceSparse) { + duration = run_contacts_lagrangian_sparse_benchmark (model, &four_bodies_one_constraint, sample_count); + } else if (contacts_method == ContactsMethodNullSpace) { + duration = run_contacts_null_space (model, &four_bodies_one_constraint, sample_count); + } else { + duration = run_contacts_kokkevis_benchmark (model, &four_bodies_one_constraint, sample_count); + } + + cout << "ConstraintSet: 4 Bodies 1 Constraint : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + // one_body_four + if (contacts_method == ContactsMethodLagrangian) { + duration = run_contacts_lagrangian_benchmark (model, &one_body_four_constraints, sample_count); + } else if (contacts_method == ContactsMethodRangeSpaceSparse) { + duration = run_contacts_lagrangian_sparse_benchmark (model, &one_body_four_constraints, sample_count); + } else if (contacts_method == ContactsMethodNullSpace) { + duration = run_contacts_null_space (model, &one_body_four_constraints, sample_count); + } else { + duration = run_contacts_kokkevis_benchmark (model, &one_body_four_constraints, sample_count); + } + + cout << "ConstraintSet: 1 Body 4 Constraints : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + // two_bodies_four + if (contacts_method == ContactsMethodLagrangian) { + duration = run_contacts_lagrangian_benchmark (model, &two_bodies_four_constraints, sample_count); + } else if (contacts_method == ContactsMethodRangeSpaceSparse) { + duration = run_contacts_lagrangian_sparse_benchmark (model, &two_bodies_four_constraints, sample_count); + } else if (contacts_method == ContactsMethodNullSpace) { + duration = run_contacts_null_space (model, &two_bodies_four_constraints, sample_count); + } else { + duration = run_contacts_kokkevis_benchmark (model, &two_bodies_four_constraints, sample_count); + } + + cout << "ConstraintSet: 2 Bodies 4 Constraints: " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + + // four_bodies_four + if (contacts_method == ContactsMethodLagrangian) { + duration = run_contacts_lagrangian_benchmark (model, &four_bodies_four_constraints, sample_count); + } else if (contacts_method == ContactsMethodRangeSpaceSparse) { + duration = run_contacts_lagrangian_sparse_benchmark (model, &four_bodies_four_constraints, sample_count); + } else if (contacts_method == ContactsMethodNullSpace) { + duration = run_contacts_null_space (model, &four_bodies_four_constraints, sample_count); + } else { + duration = run_contacts_kokkevis_benchmark (model, &four_bodies_four_constraints, sample_count); + } + + cout << "ConstraintSet: 4 Bodies 4 Constraints: " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + delete model; + + return duration; +} + +double run_single_inverse_kinematics_benchmark(Model *model, std::vector &CS, int sample_count){ + TimerInfo tinfo; + timer_start (&tinfo); + VectorNd qinit = VectorNd::Zero(model->dof_count); + VectorNd qres = VectorNd::Zero(model->dof_count); + VectorNd failures = VectorNd::Zero(model->dof_count); + + for (int i = 0; i < sample_count; i++) { + if (!InverseKinematics(*model, qinit, CS[i], qres)){ + failures[i] = 1; + } + } + double duration = timer_stop (&tinfo); + std::cout << "Success Rate: " << (1-failures.mean())*100 << "% for: "; + return duration; + +} + +double run_all_inverse_kinematics_benchmark (int sample_count){ + + //initialize the human model + Model *model = new Model(); + generate_human36model(model); + + unsigned int foot_r = model->GetBodyId ("foot_r"); + unsigned int foot_l = model->GetBodyId ("foot_l"); + unsigned int hand_r = model->GetBodyId ("hand_r"); + unsigned int hand_l = model->GetBodyId ("hand_l"); + unsigned int head = model->GetBodyId ("head"); + + Vector3d foot_r_point (1., 0., 0.); + Vector3d foot_l_point (-1., 0., 0.); + Vector3d hand_r_point (0., 1., 0.); + Vector3d hand_l_point (1., 0., 1.); + Vector3d head_point (0.,0.,-1.); + + SampleData sample_data; + sample_data.fillRandom(model->dof_count, sample_count); + + + //create constraint sets + std::vector cs_one_point; + std::vector cs_two_point_one_orientation; + std::vector cs_two_full_one_point; + std::vector cs_two_full_two_point_one_orientation; + std::vector cs_five_full; + + for (unsigned int i = 0; i < sample_count; i++){ + Vector3d foot_r_position = CalcBodyToBaseCoordinates (*model, sample_data.q[i], foot_r, foot_r_point); + Vector3d foot_l_position = CalcBodyToBaseCoordinates (*model, sample_data.q[i], foot_l, foot_l_point); + Vector3d hand_r_position = CalcBodyToBaseCoordinates (*model, sample_data.q[i], hand_r, hand_r_point); + Vector3d hand_l_position = CalcBodyToBaseCoordinates (*model, sample_data.q[i], hand_l, hand_l_point); + Vector3d head_position = CalcBodyToBaseCoordinates (*model, sample_data.q[i], head , head_point); + + Matrix3d foot_r_orientation = CalcBodyWorldOrientation (*model, sample_data.q[i], foot_r, false); + Matrix3d foot_l_orientation = CalcBodyWorldOrientation (*model, sample_data.q[i], foot_l, false); + Matrix3d hand_r_orientation = CalcBodyWorldOrientation (*model, sample_data.q[i], hand_r, false); + Matrix3d hand_l_orientation = CalcBodyWorldOrientation (*model, sample_data.q[i], hand_l, false); + Matrix3d head_orientation = CalcBodyWorldOrientation (*model, sample_data.q[i], head , false); + + //single point + InverseKinematicsConstraintSet one_point; + one_point.AddPointConstraint(foot_r, foot_r_point, foot_r_position); + one_point.step_tol = 1e-12; + cs_one_point.push_back(one_point); + + //two point and one orientation + InverseKinematicsConstraintSet two_point_one_orientation; + two_point_one_orientation.AddPointConstraint(foot_l,foot_l_point, foot_l_position); + two_point_one_orientation.AddPointConstraint(foot_r, foot_r_point, foot_r_position); + two_point_one_orientation.AddOrientationConstraint(head, head_orientation); + two_point_one_orientation.step_tol = 1e-12; + cs_two_point_one_orientation.push_back(two_point_one_orientation); + + //two full and one point + InverseKinematicsConstraintSet two_full_one_point; + two_full_one_point.AddFullConstraint(hand_r, hand_r_point, hand_r_position, hand_r_orientation); + two_full_one_point.AddFullConstraint(hand_l, hand_l_point, hand_l_position, hand_l_orientation); + two_full_one_point.AddPointConstraint(head, head_point, head_position); + two_full_one_point.step_tol = 1e-12; + cs_two_full_one_point.push_back(two_full_one_point); + + //two full, two points and one orienation + InverseKinematicsConstraintSet two_full_two_point_one_orientation; + two_full_two_point_one_orientation.AddPointConstraint(foot_r, foot_r_point, foot_r_position); + two_full_two_point_one_orientation.AddPointConstraint(foot_l, foot_l_point, foot_l_position); + two_full_two_point_one_orientation.AddFullConstraint(hand_r, hand_r_point, hand_r_position, hand_r_orientation); + two_full_two_point_one_orientation.AddFullConstraint(hand_l, hand_l_point, hand_l_position, hand_l_orientation); + two_full_two_point_one_orientation.AddOrientationConstraint(head, head_orientation); + two_full_two_point_one_orientation.step_tol = 1e-12; + cs_two_full_two_point_one_orientation.push_back(two_full_two_point_one_orientation); + + //five points 5 orientations + InverseKinematicsConstraintSet five_full; + five_full.AddFullConstraint(foot_r, foot_r_point, foot_r_position, foot_r_orientation); + five_full.AddFullConstraint(foot_l, foot_l_point, foot_l_position, foot_l_orientation); + five_full.AddFullConstraint(hand_r, hand_r_point, hand_r_position, hand_r_orientation); + five_full.AddFullConstraint(hand_l, hand_l_point, hand_l_position, hand_l_orientation); + five_full.AddFullConstraint(head, head_point, head_position, head_orientation); + five_full.step_tol = 1e-12; + cs_five_full.push_back(five_full); + } + + cout << "= #DOF: " << setw(3) << model->dof_count << endl; + cout << "= #samples: " << sample_count << endl; + double duration; + + duration = run_single_inverse_kinematics_benchmark(model, cs_one_point, sample_count); + cout << "Constraints: 1 Body: 1 Point : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + duration = run_single_inverse_kinematics_benchmark(model, cs_two_point_one_orientation, sample_count); + cout << "Constraints: 3 Bodies: 2 Points 1 Orienation : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + duration = run_single_inverse_kinematics_benchmark(model, cs_two_full_one_point, sample_count); + cout << "Constraints: 3 Bodies: 2 Full 1 Point : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + duration = run_single_inverse_kinematics_benchmark(model, cs_two_full_two_point_one_orientation, sample_count); + cout << "Constraints: 5 Bodies: 2 Full 2 Points 1 Orienation : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + + duration = run_single_inverse_kinematics_benchmark(model, cs_five_full, sample_count); + cout << "Constraints: 5 Bodies: 5 Full : " + << " duration = " << setw(10) << duration << "(s)" + << " (~" << setw(10) << duration / sample_count << "(s) per call)" << endl; + return duration; +} + +void print_usage () { +#if defined (RBDL_BUILD_ADDON_LUAMODEL) || defined (RBDL_BUILD_ADDON_URDFREADER) + cout << "Usage: benchmark [--count|-c ] [--depth|-d ] " << endl; +#else + cout << "Usage: benchmark [--count|-c ] [--depth|-d ]" << endl; +#endif + cout << "Simple benchmark tool for the Rigid Body Dynamics Library." << endl; + cout << " --count | -c : sets the number of sample states that should" << endl; + cout << " be calculated (default: 1000)" << endl; + cout << " --depth | -d : sets maximum depth for the branched test model" << endl; + cout << " which is created increased from 1 to (default: 5)." << endl; +#if defined RBDL_BUILD_ADDON_URDFREADER + cout << " --floating-base | -f : the specified URDF model is a floating base model." << endl; +#endif + cout << " --no-fd : disables benchmarking of forward dynamics." << endl; + cout << " --no-fd-aba : disables benchmark for forwards dynamics using" << endl; + cout << " the Articulated Body Algorithm" << endl; + cout << " --no-fd-lagrangian : disables benchmark for forward dynamics via" << endl; + cout << " solving the lagrangian equation." << endl; + cout << " --no-id-rnea : disables benchmark for inverse dynamics using" << endl; + cout << " the recursive newton euler algorithm." << endl; + cout << " --no-crba : disables benchmark for joint space inertia" << endl; + cout << " matrix computation using the composite rigid" << endl; + cout << " body algorithm." << endl; + cout << " --no-nle : disables benchmark for the nonlinear effects." << endl; + cout << " --no-calc-minv : disables benchmark M^-1 * tau benchmark." << endl; + cout << " --only-contacts | -C : only runs contact model benchmarks." << endl; + cout << " --help | -h : prints this help." << endl; +} + +void disable_all_benchmarks () { + benchmark_run_fd_aba = false; + benchmark_run_fd_lagrangian = false; + benchmark_run_id_rnea = false; + benchmark_run_crba = false; + benchmark_run_nle = false; + benchmark_run_calc_minv_times_tau = false; + benchmark_run_contacts = false; +} + +void parse_args (int argc, char* argv[]) { + int argi = 1; + + while (argi < argc) { + string arg = argv[argi]; + + if (arg == "--help" || arg == "-h") { + print_usage(); + exit (1); + } else if (arg == "--count" || arg == "-c" ) { + if (argi == argc - 1) { + print_usage(); + + cerr << "Error: missing number of samples!" << endl; + exit (1); + } + + argi++; + stringstream count_stream (argv[argi]); + + count_stream >> benchmark_sample_count; + } else if (arg == "--depth" || arg == "-d" ) { + if (argi == argc - 1) { + print_usage(); + + cerr << "Error: missing number for model depth!" << endl; + exit (1); + } + + argi++; + stringstream depth_stream (argv[argi]); + + depth_stream >> benchmark_model_max_depth; +#ifdef RBDL_BUILD_ADDON_URDFREADER + } else if (arg == "--floating-base" || arg == "-f") { + urdf_floating_base = true; +#endif + } else if (arg == "--no-fd" ) { + benchmark_run_fd_aba = false; + benchmark_run_fd_lagrangian = false; + } else if (arg == "--no-fd-aba" ) { + benchmark_run_fd_aba = false; + } else if (arg == "--no-fd-lagrangian" ) { + benchmark_run_fd_lagrangian = false; + } else if (arg == "--no-id-rnea" ) { + benchmark_run_id_rnea = false; + } else if (arg == "--no-crba" ) { + benchmark_run_crba = false; + } else if (arg == "--no-nle" ) { + benchmark_run_nle = false; + } else if (arg == "--no-calc-minv" ) { + benchmark_run_calc_minv_times_tau = false; + } else if (arg == "--only-contacts" || arg == "-C") { + disable_all_benchmarks(); + benchmark_run_contacts = true; + benchmark_run_ik = false; +#if defined (RBDL_BUILD_ADDON_LUAMODEL) || defined (RBDL_BUILD_ADDON_URDFREADER) + } else if (model_file == "") { + model_file = arg; +#endif + } else { + print_usage(); + cerr << "Invalid argument '" << arg << "'." << endl; + exit(1); + } + argi++; + } +} + +int main (int argc, char *argv[]) { + parse_args (argc, argv); + + Model *model = NULL; + + model = new Model(); + + if (model_file != "") { + if (model_file.substr (model_file.size() - 4, 4) == ".lua") { +#ifdef RBDL_BUILD_ADDON_LUAMODEL + RigidBodyDynamics::Addons::LuaModelReadFromFile (model_file.c_str(), model); +#else + cerr << "Could not load Lua model: LuaModel addon not enabled!" << endl; + abort(); +#endif + } + if (model_file.substr (model_file.size() - 5, 5) == ".urdf") { +#ifdef RBDL_BUILD_ADDON_URDFREADER + RigidBodyDynamics::Addons::URDFReadFromFile(model_file.c_str(), model, urdf_floating_base); +#else + cerr << "Could not load URDF model: urdfreader addon not enabled!" << endl; + abort(); +#endif + } + + if (benchmark_run_fd_aba) { + cout << "= Forward Dynamics: ABA =" << endl; + run_forward_dynamics_ABA_benchmark (model, benchmark_sample_count); + } + + if (benchmark_run_fd_lagrangian) { + cout << "= Forward Dynamics: Lagrangian (Piv. LU decomposition) =" << endl; + run_forward_dynamics_lagrangian_benchmark (model, benchmark_sample_count); + } + + if (benchmark_run_id_rnea) { + cout << "= Inverse Dynamics: RNEA =" << endl; + run_inverse_dynamics_RNEA_benchmark (model, benchmark_sample_count); + } + + if (benchmark_run_crba) { + cout << "= Joint Space Inertia Matrix: CRBA =" << endl; + run_CRBA_benchmark (model, benchmark_sample_count); + } + + if (benchmark_run_nle) { + cout << "= Nonlinear effects =" << endl; + run_nle_benchmark (model, benchmark_sample_count); + } + + delete model; + + return 0; + } + + rbdl_print_version(); + cout << endl; + + if (benchmark_run_fd_aba) { + cout << "= Forward Dynamics: ABA =" << endl; + for (int depth = 1; depth <= benchmark_model_max_depth; depth++) { + model = new Model(); + model->gravity = Vector3d (0., -9.81, 0.); + + generate_planar_tree (model, depth); + + run_forward_dynamics_ABA_benchmark (model, benchmark_sample_count); + + delete model; + } + cout << endl; + } + + if (benchmark_run_fd_lagrangian) { + cout << "= Forward Dynamics: Lagrangian (Piv. LU decomposition) =" << endl; + for (int depth = 1; depth <= benchmark_model_max_depth; depth++) { + model = new Model(); + model->gravity = Vector3d (0., -9.81, 0.); + + generate_planar_tree (model, depth); + + run_forward_dynamics_lagrangian_benchmark (model, benchmark_sample_count); + + delete model; + } + cout << endl; + } + + if (benchmark_run_id_rnea) { + cout << "= Inverse Dynamics: RNEA =" << endl; + for (int depth = 1; depth <= benchmark_model_max_depth; depth++) { + model = new Model(); + model->gravity = Vector3d (0., -9.81, 0.); + + generate_planar_tree (model, depth); + + run_inverse_dynamics_RNEA_benchmark (model, benchmark_sample_count); + + delete model; + } + cout << endl; + } + + if (benchmark_run_crba) { + cout << "= Joint Space Inertia Matrix: CRBA =" << endl; + for (int depth = 1; depth <= benchmark_model_max_depth; depth++) { + model = new Model(); + model->gravity = Vector3d (0., -9.81, 0.); + + generate_planar_tree (model, depth); + + run_CRBA_benchmark (model, benchmark_sample_count); + + delete model; + } + cout << endl; + } + + if (benchmark_run_nle) { + cout << "= Nonlinear Effects =" << endl; + for (int depth = 1; depth <= benchmark_model_max_depth; depth++) { + model = new Model(); + model->gravity = Vector3d (0., -9.81, 0.); + + generate_planar_tree (model, depth); + + run_nle_benchmark (model, benchmark_sample_count); + + delete model; + } + cout << endl; + } + + if (benchmark_run_calc_minv_times_tau) { + cout << "= CalcMInvTimesTau =" << endl; + for (int depth = 1; depth <= benchmark_model_max_depth; depth++) { + model = new Model(); + model->gravity = Vector3d (0., -9.81, 0.); + + generate_planar_tree (model, depth); + + run_calc_minv_times_tau_benchmark (model, benchmark_sample_count); + + delete model; + } + cout << endl; + } + + if (benchmark_run_contacts) { + cout << "= Contacts: ForwardDynamicsConstraintsLagrangian" << endl; + contacts_benchmark (benchmark_sample_count, ContactsMethodLagrangian); + + cout << "= Contacts: ForwardDynamicsConstraintsRangeSpaceSparse" << endl; + contacts_benchmark (benchmark_sample_count, ContactsMethodRangeSpaceSparse); + + cout << "= Contacts: ForwardDynamicsConstraintsNullSpace" << endl; + contacts_benchmark (benchmark_sample_count, ContactsMethodNullSpace); + + cout << "= Contacts: ForwardDynamicsContactsKokkevis" << endl; + contacts_benchmark (benchmark_sample_count, ContactsMethodKokkevis); + } + + if (benchmark_run_ik) { + cout << "= Inverse Kinematics" << endl; + run_all_inverse_kinematics_benchmark(benchmark_sample_count); + } + + return 0; +} diff --git a/3rdparty/rbdl/addons/benchmark/model_generator.cc b/3rdparty/rbdl/addons/benchmark/model_generator.cc new file mode 100644 index 0000000..743d60b --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/model_generator.cc @@ -0,0 +1,54 @@ +#include "model_generator.h" + +#include "rbdl/rbdl.h" + +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +void generate_planar_tree_recursive (Model *model, + unsigned int parent_body_id, + int depth, + double length) { + if (depth == 0) + return; + + // create left child + Joint joint_rot_z (JointTypeRevolute, Vector3d (0., 0., 1.)); + Body body (length, Vector3d (0., -0.25 * length, 0.), Vector3d (length, length, length)); + + Vector3d displacement (-0.5 * length, -0.25 * length, 0.); + unsigned int child_left = model->AddBody (parent_body_id, Xtrans (displacement), joint_rot_z, body); + + generate_planar_tree_recursive (model, + child_left, + depth - 1, + length * 0.4); + + displacement.set (0.5 * length, -0.25 * length, 0.); + unsigned int child_right = model->AddBody (parent_body_id, Xtrans (displacement), joint_rot_z, body); + + generate_planar_tree_recursive (model, + child_right, + depth - 1, + length * 0.4); +} + +void generate_planar_tree (Model *model, int depth) { + // we first add a single body that is hanging straight down from + // (0, 0, 0). After that we generate the tree recursively such that each + // call adds two children. + // + double length = 1.; + + Joint joint_rot_z (JointTypeRevolute, Vector3d (0., 0., 1.)); + Body body (length, Vector3d (0., -0.25 * length, 0.), Vector3d (length, length, length)); + + unsigned int base_child = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_rot_z, body); + + generate_planar_tree_recursive ( + model, + base_child, + depth, + length * 0.4); +} + diff --git a/3rdparty/rbdl/addons/benchmark/model_generator.h b/3rdparty/rbdl/addons/benchmark/model_generator.h new file mode 100644 index 0000000..964e28c --- /dev/null +++ b/3rdparty/rbdl/addons/benchmark/model_generator.h @@ -0,0 +1,11 @@ +#ifndef _MODEL_GENERATOR_H +#define _MODEL_GENERATOR_H + +namespace RigidBodyDynamics { +class Model; +} + +void generate_planar_tree (RigidBodyDynamics::Model *model, int depth); + +/* _MODEL_GENERATOR_H */ +#endif diff --git a/3rdparty/rbdl/addons/geometry/CMakeLists.txt b/3rdparty/rbdl/addons/geometry/CMakeLists.txt new file mode 100644 index 0000000..d255ed5 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/CMakeLists.txt @@ -0,0 +1,80 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +CMAKE_POLICY(SET CMP0048 NEW) + +SET ( RBDL_ADDON_GEOMETRY_VERSION_MAJOR 1 ) +SET ( RBDL_ADDON_GEOMETRY_VERSION_MINOR 0 ) +SET ( RBDL_ADDON_GEOMETRY_VERSION_PATCH 0 ) + +SET ( RBDL_ADDON_GEOMETRY_VERSION + ${RBDL_ADDON_GEOMETRY_VERSION_MAJOR}.${RBDL_ADDON_GEOMETRY_VERSION_MINOR}.${RBDL_ADDON_GEOMETRY_VERSION_PATCH} +) + +PROJECT (RBDL_ADDON_GEOMETRY VERSION ${RBDL_ADDON_GEOMETRY_VERSION}) + +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake ) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX + ) + +INCLUDE_DIRECTORIES ( + ${CMAKE_CURRENT_BINARY_DIR}/include/rbdl +) + +SET(GEOMETRY_SOURCES + SegmentedQuinticBezierToolkit.cc + SmoothSegmentedFunction.cc + SegmentedQuinticBezierToolkit.h + SmoothSegmentedFunction.h + geometry.h + Function.h +) + +SET(GEOMETRY_HEADERS + geometry.h + Function.h + SegmentedQuinticBezierToolkit.h + SmoothSegmentedFunction.h +) + +IF (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl_geometry-static STATIC ${GEOMETRY_SOURCES} ) + SET_TARGET_PROPERTIES ( rbdl_geometry-static PROPERTIES PREFIX "lib") + SET_TARGET_PROPERTIES ( rbdl_geometry-static PROPERTIES OUTPUT_NAME "rbdl_geometry") + + TARGET_LINK_LIBRARIES ( + rbdl_geometry-static + rbdl-static + ) + + INSTALL (TARGETS rbdl_geometry-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ELSE (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl_geometry SHARED ${GEOMETRY_SOURCES} ) + SET_TARGET_PROPERTIES ( rbdl_geometry PROPERTIES + VERSION ${RBDL_VERSION} + SOVERSION ${RBDL_SO_VERSION} + ) + + TARGET_LINK_LIBRARIES ( + rbdl_geometry + rbdl + ) + + INSTALL (TARGETS rbdl_geometry + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ENDIF (RBDL_BUILD_STATIC) + +FILE ( GLOB headers + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + ) + +INSTALL ( FILES ${GEOMETRY_HEADERS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/rbdl/addons/geometry + ) \ No newline at end of file diff --git a/3rdparty/rbdl/addons/geometry/Function.h b/3rdparty/rbdl/addons/geometry/Function.h new file mode 100644 index 0000000..0daaf02 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/Function.h @@ -0,0 +1,525 @@ +#ifndef SimTK_SimTKCOMMON_FUNCTION_H_ +#define SimTK_SimTKCOMMON_FUNCTION_H_ + +/* -------------------------------------------------------------------------- * + * Simbody(tm): SimTKcommon * + * -------------------------------------------------------------------------- * + * This is part of the SimTK biosimulation toolkit originating from * + * Simbios, the NIH National Center for Physics-Based Simulation of * + * Biological Structures at Stanford, funded under the NIH Roadmap for * + * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. * + * * + * Portions copyright (c) 2008-12 Stanford University and the Authors. * + * Authors: Peter Eastman * + * Contributors: Michael Sherman * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ + +// Note: this file was moved from Simmath to SimTKcommon 20100601; see the +// Simmath repository for earlier history. + +//#include "SimTKcommon/basics.h" +//#include "SimTKcommon/Simmatrix.h" +/* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ + +#include +#include +#include +#include +#include +/** + This abstract class represents a mathematical function that calculates a + value of arbitrary type based on M real arguments. The output type is set + as a template argument, while the number of input components may be + determined at runtime. The name "Function" (with no trailing _) may be + used as a synonym for Function_. + + Subclasses define particular mathematical functions. Predefined subclasses + are provided for several common function types: Function_::Constant, + Function_::Linear, Function_::Polynomial, and Function_::Step. + You can define your own subclasses for other function types. The + Spline_ class also provides a convenient way to create various types of + Functions. + */ + +namespace RigidBodyDynamics { + namespace Addons { + namespace Geometry{ + + +template +class Function_ { +public: + class Constant; + class Linear; + class Polynomial; + class Sinusoid; + class Step; + virtual ~Function_() { + } + /** + Calculate the value of this function at a particular point. + + @param x the RigidBodyDynamics::Math::VectorNd of input arguments. Its + size must equal the value returned by getArgumentSize(). + */ + virtual T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const = 0; + /** + Calculate a partial derivative of this function at a particular point. + Which derivative to take is specified by listing the input components + with which to take it. For example, if derivComponents=={0}, that + indicates a first derivative with respective to component 0. If + derivComponents=={0, 0, 0}, that indicates a third derivative with + respective to component 0. If derivComponents=={4, 7}, that indicates a + partial second derivative with respect to components 4 and 7. + + @param derivComponents + The input components with respect to which the derivative should be + taken. Its size must be less than or equal to the value returned + by getMaxDerivativeOrder(). + @param x + The RigidBodyDynamics::Math::VectorNd of input arguments. Its size must + equal the value + returned by getArgumentSize(). + @return + The value of the selected derivative, which is of type T. + */ + virtual T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const = 0; + + /** This provides compatibility with std::vector without requiring any + copying. **/ + /* + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const + { return calcDerivative(std::vector(derivComponents),x); } + */ + + /** + * Get the number of components expected in the input vector. + */ + virtual int getArgumentSize() const = 0; + /** + * Get the maximum derivative order this Function_ object can calculate. + */ + virtual int getMaxDerivativeOrder() const = 0; +}; + +/** This typedef is used for the very common case that the return type of +the Function object is double. **/ +typedef Function_ Function; + + + +/** + * This is a Function_ subclass which simply returns a fixed value, independent + * of its arguments. + */ +template +class Function_::Constant : public Function_ { +public: + /** + * Create a Function_::Constant object. + * + * @param value the value which should be returned by calcValue(); + * @param argumentSize the value which should be returned by + * getArgumentSize(), with a default of 1. + */ + explicit Constant(T value, int argumentSize=1) + : argumentSize(argumentSize), value(value) { + } + T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const { + assert(x.size() == argumentSize); + return value; + } + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const { + return static_cast(0); + } + virtual int getArgumentSize() const { + return argumentSize; + } + int getMaxDerivativeOrder() const { + return std::numeric_limits::max(); + } + + /** This provides compatibility with std::vector without requiring any + copying. **/ + /* + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const + { return calcDerivative(std::vector(derivComponents),x); } + */ +private: + const int argumentSize; + const T value; +}; + +/** + * This is a Function_ subclass whose output value is a linear function of its + * arguments: f(x, y, ...) = ax+by+...+c. + */ +template +class Function_::Linear : public Function_ { +public: + /** + * Create a Function_::Linear object. + * + * @param coefficients + * The coefficients of the linear function. The number of arguments + * expected by the function is equal to coefficients.size()-1. + * coefficients[0] is the coefficient for the first argument, + * coefficients[1] is the coefficient for the second argument, etc. + * The final element of coefficients contains the constant term. + */ + explicit Linear( + const RigidBodyDynamics::Math::VectorNd& coefficients) + : coefficients(coefficients) { + } + T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const { + assert(x.size() == coefficients.size()-1); + T value = static_cast(0); + for (int i = 0; i < x.size(); ++i) + value += x[i]*coefficients[i]; + value += coefficients[x.size()]; + return value; + } + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const { + assert(x.size() == coefficients.size()-1); + assert(derivComponents.size() > 0); + if (derivComponents.size() == 1) + return coefficients(derivComponents[0]); + return static_cast(0); + } + virtual int getArgumentSize() const { + return coefficients.size()-1; + } + int getMaxDerivativeOrder() const { + return std::numeric_limits::max(); + } + + /** This provides compatibility with std::vector without requiring any + copying. **/ + /* + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const + { return calcDerivative(ArrayViewConst_(derivComponents),x); } + */ +private: + const RigidBodyDynamics::Math::VectorNd coefficients; +}; + + +/** + * This is a Function_ subclass whose output value is a polynomial of its + * argument: f(x) = ax^n+bx^(n-1)+...+c. + */ +template +class Function_::Polynomial : public Function_ { +public: + /** + * Create a Function_::Polynomial object. + * + * @param coefficients the polynomial coefficients in order of decreasing + * powers + */ + Polynomial(const RigidBodyDynamics::Math::VectorNd& coefficients) + : coefficients(coefficients) { + } + T calcValue(const RigidBodyDynamics::Math::VectorNd& x) const { + assert(x.size() == 1); + double arg = x[0]; + T value = static_cast(0); + for (int i = 0; i < coefficients.size(); ++i) + value = value*arg + coefficients[i]; + return value; + } + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const { + assert(x.size() == 1); + assert(derivComponents.size() > 0); + double arg = x[0]; + T value = static_cast(0); + const int derivOrder = (int)derivComponents.size(); + const int polyOrder = coefficients.size()-1; + for (int i = 0; i <= polyOrder-derivOrder; ++i) { + T coeff = coefficients[i]; + for (int j = 0; j < derivOrder; ++j) + coeff *= polyOrder-i-j; + value = value*arg + coeff; + } + return value; + } + virtual int getArgumentSize() const { + return 1; + } + int getMaxDerivativeOrder() const { + return std::numeric_limits::max(); + } + + /** This provides compatibility with std::vector without requiring any + copying. **/ + /* + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const + { return calcDerivative(ArrayViewConst_(derivComponents),x); } + */ +private: + const RigidBodyDynamics::Math::VectorNd coefficients; +}; + + +/** + * This is a Function_ subclass whose output value is a sinusoid of its + * argument: f(x) = a*sin(w*x + p) where a is amplitude, w is frequency + * in radians per unit of x, p is phase in radians. + * + * This is only defined for a scalar (double) return value. + */ +template <> +class Function_::Sinusoid : public Function_ { +public: + /** + * Create a Function::Sinusoid object, returning a*sin(w*x+p). + * + * @param[in] amplitude 'a' in the above formula + * @param[in] frequency 'w' in the above formula + * @param[in] phase 'p' in the above formula + */ + Sinusoid(double amplitude, double frequency, double phase=0) + : a(amplitude), w(frequency), p(phase) {} + + void setAmplitude(double amplitude) {a=amplitude;} + void setFrequency(double frequency) {w=frequency;} + void setPhase (double phase) {p=phase;} + + double getAmplitude() const {return a;} + double getFrequency() const {return w;} + double getPhase () const {return p;} + + // Implementation of Function_ virtuals. + + virtual double calcValue( + const RigidBodyDynamics::Math::VectorNd& x) const { + + const double t = x[0]; // we expect just one argument + return a*std::sin(w*t + p); + } + + virtual double calcDerivative( + const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const { + + const double t = x[0]; // time is the only argument + const int order = derivComponents.size(); + // The n'th derivative is + // sign * a * w^n * sc + // where sign is -1 if floor(order/2) is odd, else 1 + // and sc is cos(w*t+p) if order is odd, else sin(w*t+p) + switch(order) { + case 0: return a* std::sin(w*t + p); + case 1: return a*w* std::cos(w*t + p); + case 2: return -a*w*w* std::sin(w*t + p); + case 3: return -a*w*w*w*std::cos(w*t + p); + default: + const double sign = double(((order/2) & 0x1) ? -1 : 1); + const double sc = (order & 0x1) ? std::cos(w*t+p) : std::sin(w*t+p); + const double wn = std::pow(w, order); + return sign*a*wn*sc; + } + } + + virtual int getArgumentSize() const {return 1;} // just time + virtual int getMaxDerivativeOrder() const { + return std::numeric_limits::max(); + } + + /** This provides compatibility with std::vector without requiring any + copying. **/ + /* + double calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const + { return calcDerivative(ArrayViewConst_(derivComponents),x); } + */ +private: + double a, w, p; +}; + +/** + * This is a Function_ subclass whose output value y=f(x) is smoothly stepped + * from y=y0 to y1 as its input argument goes from x=x0 to x1. This is + * an S-shaped function with first and second derivatives y'(x0)=y'(x1)=0 + * and y''(x0)=y''(x1)==0. The third derivative y''' exists and is continuous + * but we cannot guarantee anything about it at the end points. + */ +template +class Function_::Step : public Function_ { +public: + /** + * Create a Function_::Step object that smoothly interpolates its output + * through a given range as its input moves through its range. + * + * @param y0 Output value when (x-x0)*sign(x1-x0) <= 0. + * @param y1 Output value when (x-x1)*sign(x1-x0) >= 0. + * @param x0 Start of switching interval. + * @param x1 End of switching interval. + * + * @tparam T The template type is the type of y0 and y1. This must + * be a type that supports subtraction and scalar + * multiplication by a double so that we can compute + * an expression like y=y0 + f*(y1-y0) for some double scalar f. + * + * Note that the numerical values of x0 and x1 can be in either order + * x0 < x1 or x0 > x1. + */ + Step(const T& y0, const T& y1, double x0, double x1) + : m_y0(y0), m_y1(y1), m_yr(y1-y0), m_zero(double(0)*y0), + m_x0(x0), m_x1(x1), m_ooxr(1/(x1-x0)), m_sign(copysign(1,m_ooxr)) + { + /* + SimTK_ERRCHK1_ALWAYS(x0 != x1, "Function_::Step::ctor()", + "A zero-length switching interval is illegal; both ends were %g.", x0); + */ + assert(x0 != x1); + std::printf( "Function_::Step::ctor():" + "A zero-length switching interval " + "is illegal; both ends were %g.", x0); + + } + + T calcValue(const RigidBodyDynamics::Math::VectorNd& xin) const { + /* + SimTK_ERRCHK1_ALWAYS(xin.size() == 1, + "Function_::Step::calcValue()", + "Expected just one input argument but got %d.", xin.size()); + */ + assert(xin.size() == 1); + std::printf( "Function_::Step::calcValue() " + "Expected just one input argument but got %d.", + xin.size()); + + + const double x = xin[0]; + if ((x-m_x0)*m_sign <= 0) return m_y0; + if ((x-m_x1)*m_sign >= 0) return m_y1; + // f goes from 0 to 1 as x goes from x0 to x1. + const double f = step01(m_x0,m_ooxr, x); + return m_y0 + f*m_yr; + } + + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& xin) const { + /* + SimTK_ERRCHK1_ALWAYS(xin.size() == 1, + "Function_::Step::calcDerivative()", + "Expected just one input argument but got %d.", xin.size()); + */ + assert(xin.size() == 1); + std::printf( "Function_::Step::calcDerivative() " + "Expected just one input argument but got %d.", + xin.size()); + + const int derivOrder = (int)derivComponents.size(); + + /* + SimTK_ERRCHK1_ALWAYS(1 <= derivOrder && derivOrder <= 3, + "Function_::Step::calcDerivative()", + "Only 1st, 2nd, and 3rd derivatives of the step are available," + " but derivative %d was requested.", derivOrder); + */ + assert(1 <= derivOrder && derivOrder <= 3); + std::printf("Function_::Step::calcDerivative() " + "Only 1st, 2nd, and 3rd derivatives of the step are available," + " but derivative %d was requested.", derivOrder); + + const double x = xin[0]; + if ((x-m_x0)*m_sign <= 0) return m_zero; + if ((x-m_x1)*m_sign >= 0) return m_zero; + switch(derivOrder) { + case 1: return dstep01(m_x0,m_ooxr, x) * m_yr; + case 2: return d2step01(m_x0,m_ooxr, x) * m_yr; + case 3: return d3step01(m_x0,m_ooxr, x) * m_yr; + default: assert(!"impossible derivOrder"); + } + return NAN*m_yr; /*NOTREACHED*/ + } + + virtual int getArgumentSize() const {return 1;} + int getMaxDerivativeOrder() const {return 3;} + + /** This provides compatibility with std::vector without requiring any + copying. **/ + /* + T calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const + { return calcDerivative(ArrayViewConst_(derivComponents),x); } + */ +private: + const T m_y0, m_y1, m_yr; // precalculate yr=(y1-y0) + const T m_zero; // precalculate T(0) + const double m_x0, m_x1, m_ooxr; // precalculate ooxr=1/(x1-x0) + const double m_sign; // sign(x1-x0) is 1 or -1 + + double step01(double x0, double x1, double x){ + double u = (x-x0)/(x1-x0); + double u2 = u*u; + double u3 = u2*u; + return (3*u2 - 2*u3); + } + + double dstep01(double x0, double x1, double x){ + double u = (x-x0)/(x1-x0); + double du = (1)/(x1-x0); + double du2 = 2*u*du; + double du3 = 3*u*u*du; + return (3*du2 - 2*du3); + } + + double d2step01(double x0, double x1, double x){ + double u = (x-x0)/(x1-x0); + double du = (1)/(x1-x0); + //double ddu = 0; + double ddu2 = 2*du*du;// + 2*u*ddu since ddu=0; + double ddu3 = 3*du*u*du + 3*u*du*du;// + 3*u*u*du; ditto + return (3*ddu2 - 2*ddu3); + } + + double d3step01(double x0, double x1, double x){ + double u = (x-x0)/(x1-x0); + double du = (1)/(x1-x0); + //double ddu = 0; + //double dddu = 0; + double dddu2 = 0; //2*du*du;// since ddu=0; + double dddu3 = 3*du*du*du + 3*du*du*du;// ditto + return (3*dddu2 - 2*dddu3); + } +}; + +} +} +} + +#endif // SimTK_SimTKCOMMON_FUNCTION_H_ + + diff --git a/3rdparty/rbdl/addons/geometry/LICENSE b/3rdparty/rbdl/addons/geometry/LICENSE new file mode 100644 index 0000000..6e03346 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/LICENSE @@ -0,0 +1,23 @@ +Rigid Body Dynamics Library Geometry Addon - +Copyright (c) 2016 Matthew Millard + +(zlib license) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/3rdparty/rbdl/addons/geometry/LICENSE_APACHE-2.0.txt b/3rdparty/rbdl/addons/geometry/LICENSE_APACHE-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/LICENSE_APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/3rdparty/rbdl/addons/geometry/NOTICE b/3rdparty/rbdl/addons/geometry/NOTICE new file mode 100644 index 0000000..d99650d --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/NOTICE @@ -0,0 +1,24 @@ +Author: + Matthew Millard + +Date: + July 2016 + +Notice: these files + + -Function.h + -SegmentedQuinticBezierToolkit.h + -SegmentedQuinticBezierToolkit.cc + -SmoothSegmentedFunction.h + -SmoothSegmentedFunction.cc + -tests/numericalTestFunctions.h + -tests/numericalTestFunctions.cc + -tests/testSmoothSegmentedFunction.cc + +were originally part of OpenSim and have been ported +over to RBDL with some modification. These files are licenced under the +APACHE 2.0 license which, like the zlib license, is quite liberal. The +full licence can be found in this folder in the file "LICENSE_APACHE-2.0.txt" +and online here: + +http://www.apache.org/licenses/LICENSE-2.0.txt diff --git a/3rdparty/rbdl/addons/geometry/README.md b/3rdparty/rbdl/addons/geometry/README.md new file mode 100644 index 0000000..be2861b --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/README.md @@ -0,0 +1,60 @@ +@brief geometry - a set of static tool kits for creating and evaluating curves, + surfaces and solids. This addon is maintained by Matthew + Millard, so if you have problems with it email him. + +@author Matthew Millard + +\copyright 2016 Matthew Millard + +\b Requirements +This addon is standalone as of its first release + +\b Description + This addon currently contains an optimized library for creating and + evaluating 5th order 2D Bezier splines: SegmentedQuinticBezierToolkit.h + and SegmentedQuinticBezierToolkit.cc. In addition, there is a nice class + that can be used to package the memory and functions required to + evaluate these curves: SmoothSegmentedFunction.h and + SmoothSegmentedFunction.cc. + +\b Future Development +In the near future this library will also contain + +1. Geometry tools to represent C2 convex implicit surfaces and enforce + contact constraints between two surfaces. This tool kit will be first + used for simulating foot-ground contact. It could later be used for + 3D muscle wrapping: + + SmoothImplicitSurfaceToolkit + SmoothImplicitSurface + +2. Geometry tools to represent quintic Pythagorean Hodograph curves - which are + special Bezier curves that have an analytic equation for arc-length. This + package will also contain code to represent polar Pythagorean Hodographs + which will be first used to formulate a foot-ground joint. Later this toolkit + will be used for a 2D cable transmission system + (to simulate muscle wrapping). + + SegmentedQuinticPythagoreanHodographToolkit + PolarFunctionToolkit + + +\b Licensing +The following files have been ported over from OpenSim and Simbody and as such +are licenced under the Apache 2.0 Licence: + +SmoothSegmentedFunction.h +SmoothSegmentedFunction.cc +SegmentedQuinticBezierToolkit.h +SegmentedQuinticBezierToolkit.cc +Function.h + +The Apache License is very similar to the zlib license and is quite liberal. +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain a +copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + +The remaining code has been written from scratch and is licenced under the +zlib license. See the LICENSE file for details. + + diff --git a/3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.cc b/3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.cc new file mode 100644 index 0000000..526efcd --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.cc @@ -0,0 +1,1285 @@ +/* -------------------------------------------------------------------------- * + * OpenSim: SegmentedQuinticBezierToolkit.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ +/* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ + +//============================================================================= +// INCLUDES +//============================================================================= +#include "SegmentedQuinticBezierToolkit.h" +#include +#include +#include +#include +#include + +//#include +using namespace RigidBodyDynamics::Addons::Geometry; + +//============================================================================= +// STATICS +//============================================================================= +//using namespace SimTK; +//using namespace OpenSim; +using namespace std; +using namespace RigidBodyDynamics::Math; +static double UTOL_DESIRED = std::numeric_limits::epsilon()*1e2; +static double UTOL_INITIALSOLN = std::numeric_limits::epsilon()*1e11; +static int MAXITER_INITIALSOLN = 12; + +static int NUM_SAMPLE_PTS = 100; //The number of knot points to use to sample + //each Bezier corner section + +double SegmentedQuinticBezierToolkit::scaleCurviness(double curviness) +{ + double c = 0.1 + 0.8*curviness; + return c; +} + +/** +This function will print cvs file of the column vector col0 and the matrix data +@params col0: A vector that must have the same number of rows as the data matrix + This column vector is printed as the first column +@params data: A matrix of data +@params filename: The name of the file to print +*/ +void SegmentedQuinticBezierToolkit:: + printMatrixToFile( const VectorNd& col0, + const MatrixNd& data, + std::string& filename) +{ + + ofstream datafile; + datafile.open(filename.c_str()); + + for(int i = 0; i < data.rows(); i++){ + datafile << col0[i] << ","; + for(int j = 0; j < data.cols(); j++){ + if(j& curveFit, + MatrixNd& ctrlPts, + VectorNd& xVal, + VectorNd& yVal, + std::string& filename) +{ + std::string caller = "printBezierSplineFitCurves"; + int nbezier = int(ctrlPts.cols()/2.0); + int rows = NUM_SAMPLE_PTS*nbezier - (nbezier-1); + + VectorNd y1Val(rows); + VectorNd y2Val(rows); + + VectorNd ySVal(rows); + VectorNd y1SVal(rows); + VectorNd y2SVal(rows); + + MatrixNd printMatrix(rows,6); + + VectorNd tmp(1); + std::vector deriv1(1); + std::vector deriv2(2); + + deriv1[0] = 0; + deriv2[0] = 0; + deriv2[1] = 0; + double u = 0; + int oidx = 0; + int offset = 0; + for(int j=0; j < nbezier ; j++) + { + if(j > 0){ + offset = 1; + } + + for(int i=0; i=0 && curviness <= 1) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCornerControlPoints", + "Error: double argument curviness must be between 0.0 and 1.0."); + */ + if( !(curviness>=0 && curviness <= 1) ){ + + cerr << "SegmentedQuinticBezierToolkit::" + <<"calcQuinticBezierCornerControlPoints" + <<"Error: double argument curviness must be between 0.0 and 1.0." + <<"curviness : " << curviness << " " + << endl; + assert (0); + abort(); + } + + + //1. Calculate the location where the two lines intersect + // (x-x0)*dydx0 + y0 = (x-x1)*dydx1 + y1 + // x*(dydx0-dydx1) = y1-y0-x1*dydx1+x0*dydx0 + // x = (y1-y0-x1*dydx1+x0*dydx0)/(dydx0-dydx1); + + double xC = 0.; + double yC = 0.; + double epsilon = std::numeric_limits::epsilon(); + double rootEPS = sqrt(epsilon); + if(abs(dydx0-dydx1) > rootEPS){ + xC = (y1-y0-x1*dydx1+x0*dydx0)/(dydx0-dydx1); + }else{ + xC = (x1+x0)/2.0; + } + + yC = (xC-x1)*dydx1 + y1; + //Check to make sure that the inputs are consistent with a corner, and will + //not produce an 's' shaped section. To check this we compute the sides of + //a triangle that is formed by the two points that the user entered, and + //also the intersection of the 2 lines the user entered. If the distance + //between the two points the user entered is larger than the distance from + //either point to the intersection loctaion, this function will generate a + //'C' shaped curve. If this is not true, an 'S' shaped curve will result, + //and this function should not be used. + + double xCx0 = (xC-x0); + double yCy0 = (yC-y0); + double xCx1 = (xC-x1); + double yCy1 = (yC-y1); + double x0x1 = (x1-x0); + double y0y1 = (y1-y0); + + + + double a = xCx0*xCx0 + yCy0*yCy0; + double b = xCx1*xCx1 + yCy1*yCy1; + double c = x0x1*x0x1 + y0y1*y0y1; + + //This error message needs to be better. + /* + SimTK_ERRCHK_ALWAYS( ((c > a) && (c > b)), + "SegmentedQuinticBezierToolkit::calcQuinticBezierCornerControlPoints", + "The intersection point for the two lines defined by the input" + "parameters must be consistent with a C shaped corner."); + */ + + + + if( !((c > a) && (c > b)) ){ + cerr << "SegmentedQuinticBezierToolkit" + << "::calcQuinticBezierCornerControlPoints:" + << "The line segments at the end of the curve sections " + << "do not intersect within the domain " + << "("<< x0 << "," << x1 << ") of the curve. " + << "and so there is a chance that curve will not" + << " be monotonic. There are 2 ways to fix this problem: " + << endl + << "1. Add an intermediate point," + << endl + << " 2. Space the domain points more widely " + << endl + << "Details: " + << endl << " a: " << a + << endl << " b: " << b + << endl << " c: " << c << endl; + assert (0); + abort(); + + } + + /* + Value of the 2nd derivative at the end points. + This is not exposed to the user for now, as rarely is possible + or even easy to know what these values should be. Internally + I'm using this here because we get curves with nicer 1st + derivatives than if we take the easy option to get a second + derivative of zero (by setting the middle control points equal + to their neighbors. + */ + + double d2ydx20 = 0; + double d2ydx21 = 0; + + //Start point + xyPts(0,0) = x0; + xyPts(0,1) = y0; + //End point + xyPts(5,0) = x1; + xyPts(5,1) = y1; + + + /* + //Original code - leads to 2 localized corners + xyPts(1,0) = x0 + curviness*(xC-xyPts(0,0)); + xyPts(1,1) = y0 + curviness*(yC-xyPts(0,1)); + //xyPts(2,0) = xyPts(1,0); + //xyPts(2,1) = xyPts(1,1); + + //Second two midpoints + xyPts(3,0) = xyPts(5,0) + curviness*(xC-xyPts(5,0)); + xyPts(3,1) = xyPts(5,1) + curviness*(yC-xyPts(5,1)); + xyPts(4,0) = xyPts(3,0); + xyPts(4,1) = xyPts(3,1); + */ + + //Set the 1st and 4th control points (nearest to the end points) + //to get the correct first derivative + xyPts(1,0) = x0 + curviness*(xC-xyPts(0,0)); + xyPts(1,1) = y0 + curviness*(yC-xyPts(0,1)); + + xyPts(4,0) = xyPts(5,0) + curviness*(xC-xyPts(5,0)); + xyPts(4,1) = xyPts(5,1) + curviness*(yC-xyPts(5,1)); + + //Now go and update the middle points to get the desired 2nd + //derivative at the ends. Note that even if d2ydx2 = 0 the + //resulting curve using this method has a much smoother 1st + //derivative than if the middle control points are set to be + //equal to the 1st and 4th control points. + + double dxdu0 = 5.0*(xyPts(1,0) - xyPts(0,0)); + xyPts(2,0) = xyPts(1,0) + 0.5*(xC-xyPts(1,0)) ; + double d2xdu20 = 20.0*(xyPts(2,0) - 2.0*xyPts(1,0) + xyPts(0,0)); + double d2ydu20 = (dxdu0*dxdu0*(d2ydx20) + d2xdu20*(dydx0)) ; + xyPts(2,1) = d2ydu20*(1.0/20.0) + 2.0*xyPts(1,1) - xyPts(0,1) ; + + double dxdu1 = 5.0*(xyPts(5,0) - xyPts(4,0)); + xyPts(3,0) = xyPts(4,0) + 0.5*(xC-xyPts(4,0)); + double d2xdu21 = 20.0*(xyPts(3,0) - 2.0*xyPts(4,0) + xyPts(5,0) ); + double d2ydu21 = (dxdu1*dxdu1*(d2ydx21) + d2xdu21*(dydx1)); + xyPts(3,1) = d2ydu21*(1.0/20.0) + 2.0*xyPts(4,1) - xyPts(5,1); + + return xyPts; +} + +//============================================================================= +// BASIC QUINTIC BEZIER EVALUATION FUNCTIONS +//============================================================================= + +/* +Multiplications Additions Assignments +21 20 13 +*/ +double SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveVal(double u1, const VectorNd& pts) +{ + double val = -1; + + /* + SimTK_ERRCHK1_ALWAYS( (u>=0 && u <= 1) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveVal", + "Error: double argument u must be between 0.0 and 1.0" + "but %f was entered.",u); + */ + if(!(u1 >= 0 && u1 <= 1)){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveVal" + << "Error: double argument u must be between 0.0 and 1.0" + << "but " << u1 <<" was entered."; + assert (0); + abort(); + } + + /* + SimTK_ERRCHK_ALWAYS( (pts.size() == 6) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveVal", + "Error: vector argument pts must have a length of 6."); + */ + if(!(pts.size() == 6) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveVal:" + << "Error: vector argument pts must have a length of 6."; + assert (0); + abort(); + } + + double u2 = u1*u1; + double u3 = u2*u1; + double u4 = u3*u1; + double u5 = u4*u1; + + //v0 = 1; + double v1 = (1-u1); + double v2 = v1*v1; + double v3 = v2*v1; + double v4 = v3*v1; + double v5 = v4*v1; + + val = pts[0] *v5*1.0 + + pts[1]*u1*v4*5.0 + + pts[2]*u2*v3*10.0 + + pts[3]*u3*v2*10.0 + + pts[4]*u4*v1*5.0 + + pts[5]*u5 *1.0; + + + return val; +} + +/* +Detailed Computational Costs + dy/dx Divisions Multiplications Additions Assignments + dy/du 20 19 11 + dx/du 20 19 11 + dy/dx 1 + total 1 40 38 22 + + d2y/dx2 Divisions Multiplications Additions Assignments + dy/du 20 19 11 + dx/du 20 19 11 + d2y/du2 17 17 9 + d2x/du2 17 17 9 + d2y/dx2 2 4 1 3 + total 2 78 73 23 + + d3y/dx3 Divisions Multiplications Additions Assignments + dy/du 20 19 11 + dx/du 20 19 11 + d2y/du2 17 17 9 + d2x/du2 17 17 9 + d3y/du3 14 14 6 + d3x/du3 14 14 6 + + d3y/dx3 4 16 5 6 + total 4 118 105 58 + + d4y/dx4 Divisions Multiplications Additions Assignments + dy/du 20 19 11 + dx/du 20 19 11 + d2y/du2 17 17 9 + d2x/du2 17 17 9 + d3y/du3 14 14 6 + d3x/du3 14 14 6 + d4y/du4 11 11 3 + d4x/du4 11 11 3 + + d4y/dx4 5 44 15 13 + total 5 168 137 71 + + d5y/dx5 Divisions Multiplications Additions Assignments + dy/du 20 19 11 + dx/du 20 19 11 + d2y/du2 17 17 9 + d2x/du2 17 17 9 + d3y/du3 14 14 6 + d3x/du3 14 14 6 + d4y/du4 11 11 3 + d4x/du4 11 11 3 + d5y/du5 6 6 1 + d5x/du5 6 6 1 + + d5y/dx5 7 100 36 28 + total 7 236 170 88 + + d6y/dx6 + dy/du 20 19 11 + dx/du 20 19 11 + d2y/du2 17 17 9 + d2x/du2 17 17 9 + d3y/du3 14 14 6 + d3x/du3 14 14 6 + d4y/du4 11 11 3 + d4x/du4 11 11 3 + d5y/du5 6 6 1 + d5x/du5 6 6 1 + + d6y/dx6 9 198 75 46 + total 9 334 209 106 + +*/ +double SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivDYDX( + double u, + const VectorNd& xpts, + const VectorNd& ypts, + int order) +{ + double val = NAN;//SimTK::NaN; + + //Bounds checking on the input + /* + SimTK_ERRCHK_ALWAYS( (u>=0 && u <= 1) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: double argument u must be between 0.0 and 1.0."); + + SimTK_ERRCHK_ALWAYS( (xpts.size()==6) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: vector argument xpts \nmust have a length of 6."); + + SimTK_ERRCHK_ALWAYS( (ypts.size()==6) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: vector argument ypts \nmust have a length of 6."); + + SimTK_ERRCHK_ALWAYS( (order >= 1), + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: order must be greater than."); + + SimTK_ERRCHK_ALWAYS( (order <= 6), + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: order must be less than, or equal to 6."); + */ + if( !(u>=0 && u <= 1) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: double argument u must be between 0.0 and 1.0." + << endl; + assert(0); + abort(); + + } + + if( !(xpts.size()==6) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: vector argument xpts must have a length of 6." + << endl; + assert(0); + abort(); + + } + if( !(ypts.size()==6) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: vector argument ypts must have a length of 6." + << endl; + assert(0); + abort(); + } + + if( !(order >= 1) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: order must be greater than." + << endl; + assert(0); + abort(); + } + + if( !(order <= 6) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: order must be less than, or equal to 6." + << endl; + assert(0); + abort(); + } + //std::string localCaller = caller; + //localCaller.append(".calcQuinticBezierCurveDerivDYDX"); + //Compute the derivative d^n y/ dx^n + switch(order){ + case 1: //Calculate dy/dx + { + double dxdu =calcQuinticBezierCurveDerivU(u,xpts,1); + double dydu =calcQuinticBezierCurveDerivU(u,ypts,1); + double dydx = dydu/dxdu; + + val = dydx; + //Question: + //how is a divide by zero treated? Is SimTK::INF returned? + } + break; + case 2: //Calculate d^2y/dx^2 + { + double dxdu =calcQuinticBezierCurveDerivU(u,xpts,1); + double dydu =calcQuinticBezierCurveDerivU(u,ypts,1); + double d2xdu2=calcQuinticBezierCurveDerivU(u,xpts,2); + double d2ydu2=calcQuinticBezierCurveDerivU(u,ypts,2); + + //Optimized code from Maple - + //see MuscleCurveCodeOpt_20120210 for details + double t1 = 0.1e1 / dxdu; + double t3 = dxdu*dxdu;//dxdu ^ 2; + double d2ydx2 = (d2ydu2 * t1 - dydu / t3 * d2xdu2) * t1; + + val = d2ydx2; + + } + break; + case 3: //Calculate d^3y/dx^3 + { + double dxdu =calcQuinticBezierCurveDerivU(u,xpts,1); + double dydu =calcQuinticBezierCurveDerivU(u,ypts,1); + double d2xdu2=calcQuinticBezierCurveDerivU(u,xpts,2); + double d2ydu2=calcQuinticBezierCurveDerivU(u,ypts,2); + double d3xdu3=calcQuinticBezierCurveDerivU(u,xpts,3); + double d3ydu3=calcQuinticBezierCurveDerivU(u,ypts,3); + + double t1 = 1 / dxdu; + double t3 = dxdu*dxdu;//(dxdu ^ 2); + double t4 = 1 / t3; + double t11 = d2xdu2*d2xdu2;//(d2xdu2 ^ 2); + double t14 = (dydu * t4); + double d3ydx3 = ((d3ydu3 * t1 - 2 * d2ydu2 * t4 * d2xdu2 + + 2 * dydu / t3 / dxdu * t11 - t14 * d3xdu3) * t1 + - (d2ydu2 * t1 - t14 * d2xdu2) * t4 * d2xdu2) * t1; + + val = d3ydx3; + } + break; + case 4: //Calculate d^4y/dx^4 + { + double dxdu =calcQuinticBezierCurveDerivU(u,xpts,1); + double dydu =calcQuinticBezierCurveDerivU(u,ypts,1); + double d2xdu2=calcQuinticBezierCurveDerivU(u,xpts,2); + double d2ydu2=calcQuinticBezierCurveDerivU(u,ypts,2); + double d3xdu3=calcQuinticBezierCurveDerivU(u,xpts,3); + double d3ydu3=calcQuinticBezierCurveDerivU(u,ypts,3); + double d4xdu4=calcQuinticBezierCurveDerivU(u,xpts,4); + double d4ydu4=calcQuinticBezierCurveDerivU(u,ypts,4); + + double t1 = 1 / dxdu; + double t3 = dxdu*dxdu;//dxdu ^ 2; + double t4 = 1 / t3; + double t9 = (0.1e1 / t3 / dxdu); + double t11 = d2xdu2*d2xdu2;//(d2xdu2 ^ 2); + double t14 = (d2ydu2 * t4); + double t17 = t3*t3;//(t3 ^ 2); + double t23 = (dydu * t9); + double t27 = (dydu * t4); + double t37 = d3ydu3 * t1 - 2 * t14 * d2xdu2 + 2 * t23 * t11 + - t27 * d3xdu3; + double t43 = d2ydu2 * t1 - t27 * d2xdu2; + double t47 = t43 * t4; + double d4ydx4 = (((d4ydu4 * t1 - 3 * d3ydu3 * t4 * d2xdu2 + + 6 * d2ydu2 * t9 * t11 - 3 * t14 * d3xdu3 + - 6 * dydu / t17 * t11 * d2xdu2 + + 6 * t23 * d2xdu2 * d3xdu3 + - t27 * d4xdu4) * t1 - 2 * t37 * t4 * d2xdu2 + + 2 * t43 * t9 * t11 - t47 * d3xdu3) * t1 + - (t37 * t1 - t47 * d2xdu2) * t4 * d2xdu2) * t1; + + + val = d4ydx4; + + } + break; + case 5: + { + double dxdu =calcQuinticBezierCurveDerivU(u,xpts,1); + double dydu =calcQuinticBezierCurveDerivU(u,ypts,1); + double d2xdu2=calcQuinticBezierCurveDerivU(u,xpts,2); + double d2ydu2=calcQuinticBezierCurveDerivU(u,ypts,2); + double d3xdu3=calcQuinticBezierCurveDerivU(u,xpts,3); + double d3ydu3=calcQuinticBezierCurveDerivU(u,ypts,3); + double d4xdu4=calcQuinticBezierCurveDerivU(u,xpts,4); + double d4ydu4=calcQuinticBezierCurveDerivU(u,ypts,4); + double d5xdu5=calcQuinticBezierCurveDerivU(u,xpts,5); + double d5ydu5=calcQuinticBezierCurveDerivU(u,ypts,5); + + double t1 = 1 / dxdu; + double t3 = dxdu*dxdu;//dxdu ^ 2; + double t4 = 1 / t3; + double t9 = (0.1e1 / t3 / dxdu); + double t11 = d2xdu2*d2xdu2;//(d2xdu2 ^ 2); + double t14 = (d3ydu3 * t4); + double t17 = t3*t3;//(t3 ^ 2); + double t18 = 1 / t17; + double t20 = (t11 * d2xdu2); + double t23 = (d2ydu2 * t9); + double t24 = (d2xdu2 * d3xdu3); + double t27 = (d2ydu2 * t4); + double t33 = t11*t11;//(t11 ^ 2); + double t36 = (dydu * t18); + double t40 = (dydu * t9); + double t41 = d3xdu3*d3xdu3;//(d3xdu3 ^ 2); + double t47 = (dydu * t4); + double t49 = d5ydu5 * t1 - 4 * d4ydu4 * t4 * d2xdu2 + + 12 * d3ydu3 * t9 * t11 - 6 * t14 * d3xdu3 + - 24 * d2ydu2 * t18 * t20 + 24 * t23 * t24 + - 4 * t27 * d4xdu4 + 24 * dydu / t17 / dxdu * t33 + - 36 * t36 * t11 * d3xdu3 + 6 * t40 * t41 + + 8 * t40 * d2xdu2 * d4xdu4 - t47 * d5xdu5; + double t63 = d4ydu4 * t1 - 3 * t14 * d2xdu2 + 6 * t23 * t11 + - 3 * t27 * d3xdu3 - 6 * t36 * t20 + + 6 * t40 * t24 - t47 * d4xdu4; + double t73 = d3ydu3 * t1 - 2 * t27 * d2xdu2 + 2 * t40 * t11 + - t47 * d3xdu3; + double t77 = t73 * t4; + double t82 = d2ydu2 * t1 - t47 * d2xdu2; + double t86 = t82 * t9; + double t89 = t82 * t4; + double t99 = t63 * t1 - 2 * t77 * d2xdu2 + 2 * t86 * t11 + - t89 * d3xdu3; + double t105 = t73 * t1 - t89 * d2xdu2; + double t109 = t105 * t4; + double d5ydx5 = (((t49 * t1 - 3 * t63 * t4 * d2xdu2 + + 6 * t73 * t9 * t11 - 3 * t77 * d3xdu3 + - 6 * t82 * t18 * t20 + + 6 * t86 * t24 - t89 * d4xdu4) * t1 + - 2 * t99 * t4 * d2xdu2 + + 2 * t105 * t9 * t11 - t109 * d3xdu3) * t1 + - (t99 * t1 - t109 * d2xdu2) * t4 * d2xdu2) * t1; + + + val = d5ydx5; + + } + break; + case 6: + { + double dxdu =calcQuinticBezierCurveDerivU(u,xpts,1); + double dydu =calcQuinticBezierCurveDerivU(u,ypts,1); + double d2xdu2=calcQuinticBezierCurveDerivU(u,xpts,2); + double d2ydu2=calcQuinticBezierCurveDerivU(u,ypts,2); + double d3xdu3=calcQuinticBezierCurveDerivU(u,xpts,3); + double d3ydu3=calcQuinticBezierCurveDerivU(u,ypts,3); + double d4xdu4=calcQuinticBezierCurveDerivU(u,xpts,4); + double d4ydu4=calcQuinticBezierCurveDerivU(u,ypts,4); + double d5xdu5=calcQuinticBezierCurveDerivU(u,xpts,5); + double d5ydu5=calcQuinticBezierCurveDerivU(u,ypts,5); + double d6xdu6=calcQuinticBezierCurveDerivU(u,xpts,6); + double d6ydu6=calcQuinticBezierCurveDerivU(u,ypts,6); + + double t1 = dxdu*dxdu;//(dxdu ^ 2); + double t3 = (0.1e1 / t1 / dxdu); + double t5 = d2xdu2*d2xdu2;//(d2xdu2 ^ 2); + double t8 = t1*t1;//(t1 ^ 2); + double t9 = 1 / t8; + double t11 = (t5 * d2xdu2); + double t14 = (d3ydu3 * t3); + double t15 = (d2xdu2 * d3xdu3); + double t19 = (0.1e1 / t8 / dxdu); + double t21 = t5*t5;//(t5 ^ 2); + double t24 = (d2ydu2 * t9); + double t25 = (t5 * d3xdu3); + double t28 = (d2ydu2 * t3); + double t29 = d3xdu3*d3xdu3;//(d3xdu3 ^ 2); + double t32 = (d2xdu2 * d4xdu4); + double t41 = (dydu * t19); + double t45 = (dydu * t9); + double t49 = (dydu * t3); + double t56 = 1 / dxdu; + double t61 = 1 / t1; + double t62 = (dydu * t61); + double t67 = (d4ydu4 * t61); + double t70 = (d2ydu2 * t61); + double t73 = (d3ydu3 * t61); + double t76 = 20 * d4ydu4 * t3 * t5 - 60 * d3ydu3 * t9 * t11 + + 60 * t14 * t15 + 120 * d2ydu2 * t19 * t21 + - 180 * t24 * t25 + + 30 * t28 * t29 + 40 * t28 * t32 + - 120 * dydu / t8 / t1 * t21 * d2xdu2 + + 240 * t41 *t11*d3xdu3 + - 60 * t45 * t5 * d4xdu4 + 20 * t49 * d3xdu3 * d4xdu4 + + 10 * t49 * d2xdu2 * d5xdu5 + d6ydu6 * t56 + - 90 * t45 * d2xdu2 * t29 - t62 * d6xdu6 + - 5 * d5ydu5 * t61 * d2xdu2 - 10 * t67 * d3xdu3 + - 5 * t70 * d5xdu5 - 10 * t73 * d4xdu4; + + double t100 = d5ydu5 * t56 - 4 * t67 * d2xdu2 + 12 * t14 * t5 + - 6 * t73 * d3xdu3 - 24 * t24 * t11 + 24 * t28 * t15 + - 4 * t70 * d4xdu4 + 24 * t41 * t21 - 36 * t45 * t25 + + 6 * t49 * t29 + 8 * t49 * t32 - t62 * d5xdu5; + + double t116 = d4ydu4 * t56 - 3 * t73 * d2xdu2 + 6 * t28 * t5 + - 3 * t70 * d3xdu3 - 6 * t45 * t11 + 6 * t49 * t15 + - t62 * d4xdu4; + + double t120 = t116 * t61; + double t129 = d3ydu3 * t56 - 2 * t70 * d2xdu2 + 2 * t49 * t5 + - t62 * d3xdu3; + double t133 = t129 * t3; + double t136 = t129 * t61; + double t141 = d2ydu2 * t56 - t62 * d2xdu2; + double t145 = t141 * t9; + double t148 = t141 * t3; + double t153 = t141 * t61; + double t155 = t76 * t56 - 4 * t100 * t61 * d2xdu2 + + 12 * t116 * t3 * t5 - 6 * t120 * d3xdu3 + - 24 * t129 * t9 * t11 + 24 * t133 * t15 + - 4 * t136 * d4xdu4 + + 24 * t141 * t19 * t21 - 36 * t145 * t25 + 6 * t148 * t29 + + 8 * t148 * t32 - t153 * d5xdu5; + + double t169 = t100 * t56 - 3 * t120 * d2xdu2 + 6 * t133 * t5 + - 3 * t136 * d3xdu3 - 6 * t145 * t11 + 6 * t148 * t15 + - t153 * d4xdu4; + + double t179 = t116 * t56 - 2 * t136 * d2xdu2 + 2 * t148 * t5 + - t153 * d3xdu3; + + double t183 = t179 * t61; + double t188 = t129 * t56 - t153 * d2xdu2; + double t192 = t188 * t3; + double t195 = t188 * t61; + double t205 = t169 * t56 - 2 * t183 * d2xdu2 + 2 * t192 * t5 + - t195 * d3xdu3; + double t211 = t179 * t56 - t195 * d2xdu2; + double t215 = t211 * t61; + double d6ydx6 = (((t155 * t56 - 3 * t169 * t61 * d2xdu2 + + 6 * t179 * t3 * t5 - 3 * t183 * d3xdu3 + - 6 * t188 * t9 *t11 + + 6 * t192 * t15 - t195 * d4xdu4) * t56 + - 2 * t205 * t61 * d2xdu2 + + 2 * t211*t3*t5-t215*d3xdu3)*t56 + - (t205 * t56 - t215 * d2xdu2) * t61 * d2xdu2) * t56; + + + val = d6ydx6; + } + break; + default: + val = NAN; //SimTK::NaN; + } + return val; +} + +/* Computational Cost Details + Divisions Multiplications Additions Assignments +dx/du 20 19 11 +d2x/du2 17 17 9 +d3y/du3 14 14 6 + +*/ +double SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU( + double u, + const VectorNd& pts, + int order) +{ + double val = -1; + /* + SimTK_ERRCHK_ALWAYS( (u>=0 && u <= 1) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: double argument u must be between 0.0 and 1.0."); + + SimTK_ERRCHK_ALWAYS( (pts.size()==6) , + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: vector argument pts \nmust have a length of 6."); + + SimTK_ERRCHK_ALWAYS( (order >= 1), + "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU", + "Error: order must be greater than, or equal to 1"); + */ + if( !(u>=0 && u <= 1) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: double argument u must be between 0.0 and 1.0." + << endl; + assert(0); + abort(); + } + if( !(pts.size()==6) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: vector argument pts must have a length of 6." + << endl; + assert(0); + abort(); + } + + if( !(order >= 1) ){ + cerr << "SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivU:" + << "Error: order must be greater than, or equal to 1" + << endl; + assert(0); + abort(); + } + + //Compute the Bezier point + double p0 = pts[0]; + double p1 = pts[1]; + double p2 = pts[2]; + double p3 = pts[3]; + double p4 = pts[4]; + double p5 = pts[5]; + + switch(order){ + case 1: + { + double t1 = u*u;//u ^ 2; + double t2 = t1*t1;//t1 ^ 2; + double t4 = t1 * u; + double t5 = t4 * 0.20e2; + double t6 = t1 * 0.30e2; + double t7 = u * 0.20e2; + double t10 = t2 * 0.25e2; + double t11 = t4 * 0.80e2; + double t12 = t1 * 0.90e2; + double t16 = t2 * 0.50e2; + val = p0 * (t2 * (-0.5e1) + t5 - t6 + t7 - 0.5e1) + + p1 * (t10 - t11 + t12 + u * (-0.40e2) + 0.5e1) + + p2 * (-t16 + t4 * 0.120e3 - t12 + t7) + + p3 * (t16 - t11 + t6) + + p4 * (-t10 + t5) + + p5 * t2 * 0.5e1; + + } + break; + case 2: + { + double t1 = u*u;//u ^ 2; + double t2 = t1 * u; + double t4 = t1 * 0.60e2; + double t5 = u * 0.60e2; + double t8 = t2 * 0.100e3; + double t9 = t1 * 0.240e3; + double t10 = u * 0.180e3; + double t13 = t2 * 0.200e3; + val = p0 * (t2 * (-0.20e2) + t4 - t5 + 0.20e2) + + p1 * (t8 - t9 + t10 - 0.40e2) + + p2 * (-t13 + t1 * 0.360e3 - t10 + 0.20e2) + + p3 * (t13 - t9 + t5) + + p4 * (-t8 + t4) + + p5 * t2 * 0.20e2; + + } + break; + case 3: + { + double t1 = u*u;//u ^ 2; + double t3 = u * 0.120e3; + double t6 = t1 * 0.300e3; + double t7 = u * 0.480e3; + double t10 = t1 * 0.600e3; + val = p0 * (t1 * (-0.60e2) + t3 - 0.60e2) + + p1 * (t6 - t7 + 0.180e3) + + p2 * (-t10 + u * 0.720e3 - 0.180e3) + + p3 * (t10 - t7 + 0.60e2) + + p4 * (-t6 + t3) + + p5 * t1 * 0.60e2; + + } + break; + case 4: + { + double t4 = u * 0.600e3; + double t7 = u * 0.1200e4; + val = p0 * (u * (-0.120e3) + 0.120e3) + + p1 * (t4 - 0.480e3) + + p2 * (-t7 + 0.720e3) + + p3 * (t7 - 0.480e3) + + p4 * (-t4 + 0.120e3) + + p5 * u * 0.120e3; + } + break; + case 5: + { + val = p0 * (-0.120e3) + + p1 * 0.600e3 + + p2 * (-0.1200e4) + + p3 * 0.1200e4 + + p4 * (-0.600e3) + + p5 * 0.120e3; + } + break; + default: + val=0; + + } + + return val; + + +} + +double SegmentedQuinticBezierToolkit::clampU(double u){ + double uC = u; + if(u<0.0){ + uC=0; + } + if(u>1.0){ + uC=1; + } + return uC; +} + +/*Detailed Computational Costs + Comparisons Div Mult Additions Assignments + Geuss calculation 1 1 1 + + Newton Iter + f 21 20 13 + df 20 19 11 + update 4 1 3 6 + total 4 1 41 42 30 + \endverbatim + + To evaluate u to SimTK::Eps*100 this typically involves 2 Newton + iterations, yielding a total cost of + + \verbatim + Comparisons Div Mult Additions Assignments + eval U 7+8=15 2 82 42 60 +*/ +double SegmentedQuinticBezierToolkit::calcU(double ax, + const VectorNd& bezierPtsX, + double tol, + int maxIter) +{ + //Check to make sure that ax is in the curve domain + double minX = std::numeric_limits::max(); + double maxX = -minX; + for(int i=0; i maxX) + maxX = bezierPtsX[i]; + if(bezierPtsX[i] < minX) + minX = bezierPtsX[i]; + } + + /* + SimTK_ERRCHK_ALWAYS( ax >= minX && ax <= maxX, + "SegmentedQuinticBezierToolkit::calcU", + "Error: input ax was not in the domain of the Bezier curve specified \n" + "by the control points in bezierPtsX."); + */ + if( !(ax >= minX && ax <= maxX) ){ + cerr << "SegmentedQuinticBezierToolkit::calcU:" + << "Error: input ax was not in the domain of the " + << "Bezier curve specified by the control points in bezierPtsX." + << endl; + assert(0); + abort(); + } + + double u = ax/(maxX-minX); + double f = 0; + + u = clampU(u); + f = calcQuinticBezierCurveVal(u,bezierPtsX)-ax; + + //Use the bisection method to get a good initial + //start for the Newton method. This is necessary + //as these curves, though C2, can be so nonlinear + //that the Newton method oscillates unless it is + //close to the initial solution. + if(abs(f) > tol){ + double uL = 0; + double uR = 1; + + double fL = calcQuinticBezierCurveVal(uL,bezierPtsX)-ax; + double fR = calcQuinticBezierCurveVal(uR,bezierPtsX)-ax; + + int iterBisection = 0; + + while(iterBisection < MAXITER_INITIALSOLN + && min(abs(fL),abs(fR)) > UTOL_INITIALSOLN ){ + u = 0.5*(uL+uR); + f = calcQuinticBezierCurveVal(u,bezierPtsX)-ax; + + if(signbit(f) == signbit(fL)){ + fL = f; + uL = u; + }else{ + fR = f; + uR = u; + } + iterBisection++; + } + + if(abs(fL) < abs(fR)){ + u = uL; + f = fL; + }else{ + u = uR; + f = fR; + } + } + + + + double df = 0; + double du = 0; + int iter = 0; + bool pathologic = false; + double fprev = f; + double stepLength = 1.0; + double perturbation01 = 0.0; + //Take Newton steps to the desired tolerance + while((abs(f) > min(tol,UTOL_DESIRED)) + && (iter < maxIter) + && (pathologic == false) ){ + //Take a Newton step + df = calcQuinticBezierCurveDerivU(u,bezierPtsX,1); + + if(abs(df) > 0){ + du = -f/df; + u = u + stepLength*du; + u = clampU(u); + fprev = f; + f = calcQuinticBezierCurveVal(u,bezierPtsX)-ax; + }else{ + //This should never happen. If we are unluky enough to get here + //purturb the current solution and continue until we run out of + //iterations. + perturbation01 = double(rand()%100)/100.0; + u = u + perturbation01*0.1; + u = clampU(u); + } + + iter++; + } + + //Check for convergence + if( abs(f) > tol ){ + std::stringstream errMsg; + errMsg.precision(17); + errMsg << "SegmentedQuinticBezierToolkit::calcU:" << endl + << "Error: desired tolerance of " << tol << endl + << " on U not met by the Newton iteration." << endl + << " A tolerance of " << f << " was reached." << endl + << " Curve range x(u): " << minX << "-" << maxX << endl + << " Desired x: " << ax << " closest u " << u << endl + << " Bezier points " << endl << bezierPtsX << endl; + cerr << errMsg.str(); + assert(0); + abort(); + } + + return u; +} +/* + +Cost: n comparisons, for a quintic Bezier curve with n-spline sections + + Comp Div Mult Add Assignments +Cost 3*n+2 1*n 3 + +*/ +int SegmentedQuinticBezierToolkit::calcIndex(double x, + const MatrixNd& bezierPtsX) +{ + int idx = 0; + bool flag_found = false; + + for(int i=0; i= bezierPtsX(0,i) && x < bezierPtsX(5,i) ){ + idx = i; + i = bezierPtsX.cols(); + flag_found = true; + } + } + + //Check if the value x is identically the last point + if(flag_found == false && x == bezierPtsX(5,bezierPtsX.cols()-1)){ + idx = bezierPtsX.cols()-1; + flag_found = true; + } + + /* + SimTK_ERRCHK_ALWAYS( (flag_found == true), + "SegmentedQuinticBezierToolkit::calcIndex", + "Error: A value of x was used that is not within the Bezier curve set."); + */ + if( !(flag_found == true)){ + cerr << "SegmentedQuinticBezierToolkit::calcIndex" + << "Error: A value of x was used that is not" + << " within the Bezier curve set." << endl; + assert(0); + abort(); + } + + + + + return idx; +} + +int SegmentedQuinticBezierToolkit::calcIndex(double x, + const std::vector& bezierPtsX) +{ + int idx = 0; + bool flag_found = false; + + int n = bezierPtsX.size(); + for(int i=0; i= bezierPtsX[i][0] && x < bezierPtsX[i][5] ){ + idx = i; + flag_found = true; + break; + } + } + + //Check if the value x is identically the last point + if(!flag_found && x == bezierPtsX[n-1][5]){ + idx = n-1; + flag_found = true; + } + + if(!(flag_found == true)){ + cerr << "SegmentedQuinticBezierToolkit::calcIndex " + << "Error: A value of x was used that is not " + << "within the Bezier curve set." + << endl; + assert(0); + abort(); + } + + return idx; +} + + + +/* + Comp Div Mult Additions Assignments +calcIdx 3*3+2=11 1*3=3 3 +calcU 15 2 82 42 60 +calcQuinticBezierCurveVal + 21 20 13 +Total 26 2 103 65 76 +\endverbatim + +Ignoring the costs associated with the integrator itself, and assuming +that the integrator evaluates the function 6 times per integrated point, +the cost of evaluating the integral at each point in vX is: + +\verbatim + Comp Div Mult Additions Assignments +RK45 on 1pt 6*(26 2 103 65 76) +Total 156 12 618 390 456 +\endverbatim + +Typically the integral is evaluated 100 times per section in order to +build an accurate spline-fit of the integrated function. Once again, +ignoring the overhead of the integrator, the function evaluations alone +for the current example would be + +\verbatim +RK45 on 100pts per section, over 3 sections + Comp Div Mult Additions Assignments + 3*100*(156 12 618 390 456 +Total 46,800 3600 185,400 117,000 136,000 + +*/ +/* +MatrixNd SegmentedQuinticBezierToolkit::calcNumIntBezierYfcnX( + const VectorNd& vX, + double ic0, double intAcc, + double uTol, int uMaxIter, + const MatrixNd& mX, const MatrixNd& mY, + const SimTK::Array_& aSplineUX, + bool flag_intLeftToRight, + const std::string& caller) +{ + MatrixNd intXY(vX.size(),2); + BezierData bdata; + bdata._mX = mX; + bdata._mY = mY; + bdata._initalValue = ic0; + bdata._aArraySplineUX = aSplineUX; + bdata._uMaxIter = uMaxIter; + bdata._uTol = uTol; + bdata._flag_intLeftToRight = flag_intLeftToRight; + bdata._name = caller; + + //These aren't really times, but I'm perpetuating the SimTK language + //so that I don't make a mistake + double startTime = vX(0); + double endTime = vX(vX.size()-1); + + if(flag_intLeftToRight){ + bdata._startValue = startTime; + }else{ + bdata._startValue = endTime; + } + + MySystem sys(bdata); + State initState = sys.realizeTopology(); + initState.setTime(startTime); + + + RungeKuttaMersonIntegrator integ(sys); + integ.setAccuracy(intAcc); + integ.setFinalTime(endTime); + integ.setReturnEveryInternalStep(false); + integ.initialize(initState); + + int idx = 0; + double nextTimeInterval = 0; + Integrator::SuccessfulStepStatus status; + + while (idx < vX.nelt()) { + if(idx < vX.nelt()){ + if(flag_intLeftToRight){ + nextTimeInterval = vX(idx); + }else{ + nextTimeInterval = endTime-vX(vX.size()-idx-1); + } + } + status=integ.stepTo(nextTimeInterval); + + // Use this for variable step output. + //status = integ.stepTo(Infinity); + + if (status == Integrator::EndOfSimulation) + break; + + const State& state = integ.getState(); + + if(flag_intLeftToRight){ + intXY(idx,0) = nextTimeInterval; + intXY(idx,1) = (double)state.getZ()[0]; + }else{ + intXY(vX.size()-idx-1,0) = vX(vX.size()-idx-1); + intXY(vX.size()-idx-1,1) = (double)state.getZ()[0]; + } + idx++; + + } + //intXY.resizeKeep(idx,2); + return intXY; +} +*/ diff --git a/3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.h b/3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.h new file mode 100644 index 0000000..bb76346 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/SegmentedQuinticBezierToolkit.h @@ -0,0 +1,836 @@ +#ifndef SEGMENTEDQUINTICBEZIERTOOLKIT_H_ +#define SEGMENTEDQUINTICBEZIERTOOLKIT_H_ +/* -------------------------------------------------------------------------- * + * OpenSim: SegmentedQuinticBezierToolkit.h * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ +/* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ +#include +#include +#include "Function.h" + +/** +This is a low level Quintic Bezier curve class that contains functions to design +continuous sets of 'C' shaped Bezier curves, and to evaluate their values and +derivatives. A set in this context is used to refer to 2 or more quintic Bezier +curves that are continuously connected to eachother to form one smooth curve, +hence the name QuinticBezierSet. + +In the special case when this class is being used to generate and evaluate +2D Bezier curves, that is x(u) and y(u), there are also functions to evaluate +y(x), the first six derivatives of y(x), and also the first integral of y(x). + +This class was not designed to be a stand alone Quintic Bezier class, but rather +was developed out of necessity to model muscles. I required curves that, when +linearly extrapolated, were C2 continuous, and by necessity I had to use +quintic Bezier curves. In addition, the curves I was developing were functions +in x,y space, allowing many of the methods (such as the evaluation of y(x) given +that x(u) and y(u), the derivatives of y(x), and its first integral) to be +developed, though in general this can't always be done. + +I have parcelled all of these tools into their own class so that others may more +easily use and develop this starting point for their own means. I used the +following text during the development of this code: + +Mortenson, Michael E (2006). Geometric Modeling Third Edition. Industrial Press +Inc., New York. Chapter 4 was quite helpful. + +Future Upgrades + +1. Analytical Inverse to x(u): + I think this is impossible because it is not possible, in general, to find the + roots to a quintic polynomial, however, this fact may not preclude forming the + inverse curve. The impossibility of finding the roots to a quintic polynomial + was proven by Abel (Abel's Impossibility Theorem) and Galois + + http://mathworld.wolfram.com/QuinticEquation.html + + At the moment I am approximating the curve u(x) using cubic splines to return + an approximate value for u(x), which I polish using Newton's method to the + desired precision. + + **Note as of Nov 2015** + -> The cubic spline approximation of the inverse curve has been + removed. Since there is no spline class in RBDL (and I'm not + motivated to port it over from SimTK) this functionality does + not work. In addition, I've since found that this nice inverse + only saves a few Newton iterations over a calculated guess. + It's not worth the effort to include. + +2. Analytical Integral of y(x): + This is possible using the Divergence Theorem applied to 2D curves. A nice + example application is shown in link 2 for computing the area of a closed + cubic Bezier curve. While I have been able to get the simple examples to work, + I have failed to successfully compute the area under a quintic Bezier curve + correctly. I ran out of time trying to fix this problem, and so at the present + time I am numerically computing the integral at a number of knot points and + then evaluating the spline to compute the integral value. + + a. http://en.wikipedia.org/wiki/Divergence_theorem + b. http://objectmix.com/graphics/133553-area-closed-bezier-curve.html + + **Note as of Nov 2015** + -> The splined numeric integral of the curve has been removed. There + is not an error-controlled numerical integrator in RBDL and so + it is not straight forward to include this feature. + -> For later: discuss options with Martin. + +3. calcU: + Currently the Bezier curve value and its derivative are computed separately to + evaluate f and df in the Newton iteration to solve for u(x). Code optimized to + compute both of these quantites at the same time could cut the cost of + evaluating x(u) and dx/du in half. Since these quantities are evaluated in an + iterative loop, this one change could yield substantial computational savings. + +4. calcIndex: +The function calcIndex computes which spline the contains the point of interest. +This function has been implemented assuming a small number of Bezier curve sets, +and so it simply linearly scans through the sets to determine the correct one to +use. This function should be upgraded to use the bisection method if large +quintic Bezier curve sets are desired. + +5. The addition of additional Bezier control point design algorithms, to create + 'S' shaped curves, and possibly do subdivision. + +6. Low level Code Optimization: +I have exported all of the low level code as optimized code from Maple. Although +the code produced using this means is reasonably fast, it is usally possible +to obtain superior performance (and sometimes less round off error) by +doing this work by hand. + +Computational Cost Details +All computational costs assume the following operation costs: + +\verbatim +Operation Type : #flops ++,-,=,Boolean Op : 1 + / : 10 + sqrt: 20 + trig: 40 +\endverbatim + +These relative weightings will vary processor to processor, and so any of +the quoted computational costs are approximate. + + + RBDL Port Notes + +The port of this code from OpenSim has been accompanied by a few changes: + +1. The 'calcIntegral' method has been removed. Why? This function + relied on having access to a variable-step error controlled + integrator. There is no such integrator built into RBDL. Rather + than add a dependency (by using Boost perhaps) this functionality + has been removed. + +2. The function name .printMuscleCurveToFile(...) has been changed + to .printCurveToFile(). + +3. There have been some improvements to the function calcU in the + SegmentedQuinticBezierToolkit.cc code. This function evaluates + u such that x(u) - x* = 0. This is done using a Newton method. + However, because these curves can be very nonlinear, the Newton + method requires a very good initial start. In the OpenSim version + this good initial guess was provided by splined interpolation of + u(x). In the RBDL version this initial guess is provided by using + a bisection method until the error of x(u)-x* is within + sqrt(sqrt(tol)) or 2 Newton steps of the desired tolerance. + +@author Matt Millard +@version 0.0 + +*/ +namespace RigidBodyDynamics { + namespace Addons { + namespace Geometry{ + +class SegmentedQuinticBezierToolkit +{ + + + public: + + /** + This scales the users value of curviness to be between [0+delta, 1-delta] + because if curviness is allowed to equal 0 or 1, the second derivative + becomes quite violent and the resulting curve is difficult to fit + splines to. + + @param curviness + @retval a scaled version of curviness + + */ + static double scaleCurviness(double curviness); + + /** + This function will compute the u value that correesponds to the given x + for a quintic Bezier curve. + + @param ax The x value + @param bezierPtsX The 6 Bezier point values + @param tol The desired tolerance on u. + @param maxIter The maximum number of Newton iterations allowed + + \b aborts \b + -if ax is outside the range defined in this Bezier spline section + -if the desired tolerance is not met + -if the derivative goes to 0 to machine precision + + This function will compute the u value that correesponds to the given x + for a quintic Bezier curve. This is accomplished by using an approximate + spline inverse of u(x) to get a very good initial guess, and then one or + two Newton iterations to polish the answer to the desired tolerance. + + Computational Costs + \verbatim + ~219 flops + \endverbatim + + Example: + @code + double xVal = 2; + + //Choose the control points + RigidBodyDynamics::Math::VectorNd vX(6); + vX(0) = -2; + vX(1) = 0; + vX(2) = 0; + vX(3) = 4; + vX(4) = 4; + vX(5) = 6; + + RigidBodyDynamics::Math::VectorNd x(100); + RigidBodyDynamics::Math::VectorNd u(100); + + //Now evalutate u at the given xVal + double u = SegmentedQuinticBezierToolkit:: + calcU(xVal,vX, 1e-12,20); + + @endcode + */ + static double calcU( + double ax, + const RigidBodyDynamics::Math::VectorNd& bezierPtsX, + double tol, + int maxIter); + + + + /** + Given a set of Bezier curve control points, return the index of the + set of control points that x lies within. + + @param x A value that is interpolated by the set of Bezier + curves + @param bezierPtsX A matrix of 6xn Bezier control points + + \b aborts \b + -If the index is not located within this set of Bezier points + + Given a set of Bezier curve control points, return the index of the + set of control points that x lies within. This function has been coded + assuming a small number of Bezier curve sets (less than 10), and so, + it simply scans through the Bezier curve sets until it finds the correct + one. + + + Computational Costs + Quoted for a Bezier curve set containing 1 to 5 curves. + \verbatim + ~9-25 + \endverbatim + + Example: + @code + RigidBodyDynamics::Math::MatrixNd mX(6,2); + + //The first set of spline points + mX(0,0) = -2; + mX(1,0) = -1; + mX(2,0) = -1; + mX(3,0) = 1; + mX(4,0) = 1; + mX(5,0) = 2; + //The second set of spline points + mX(0,1) = 2; + mX(1,1) = 3; + mX(2,1) = 3; + mX(3,1) = 5; + mX(4,1) = 5; + mX(5,1) = 6; + + //The value of x for which we want the index for + double xVal = 1.75; + int idx = SegmentedQuinticBezierToolkit::calcIndex(xVal,mX); + @endcode + + + */ + static int calcIndex( + double x, + const RigidBodyDynamics::Math::MatrixNd& bezierPtsX); + + static int calcIndex( + double x, + const std::vector& bezierPtsX); + + + + + + /** + Calculates the value of a quintic Bezier curve at value u. + + @param u The independent variable of a Bezier curve, which ranges + between 0.0 and 1.0. + @param pts The locations of the control points in 1 dimension. + + \b aborts \b + -If u is outside of [0,1] + -if pts has a length other than 6 + @return The value of the Bezier curve located at u. + + Calculates the value of a quintic Bezier curve at value u. This + calculation is acheived by mulitplying a row vector comprised of powers + of u, by the 6x6 coefficient matrix associated with a quintic Bezier + curve, by the vector of Bezier control points, pV, in a particular + dimension. The code to compute the value of a quintic bezier curve has + been optimized to have the following cost: + + Computational Costs + \verbatim + ~54 flops + \endverbatim + + + + The math this function executes is decribed in pseudo code as the + following: + + \verbatim + uV = [u^5 u^4 u^3 u^2 u 1]; + + cM = [ -1 5 -10 10 -5 1; + 5 -20 30 -20 5 0; + -10 30 -30 10 0 0; + 10 -20 10 0 0 0; + -5 5 0 0 0 0; + 1 0 0 0 0 0 ]; + pV = [x1; x2; x3; x4; x5; x6]; + + xB = (uV*cM)*pV + \endverbatim + + + + Example: + @code + double u = 0.5; + + //Choose the control points + RigidBodyDynamics::Math::VectorNd vX(6); + vX(0) = -2; + vX(1) = 0; + vX(2) = 0; + vX(3) = 4; + vX(4) = 4; + vX(5) = 6; + + yVal = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveVal(u,vX); + @endcode + + + */ + static double calcQuinticBezierCurveVal( + double u, + const RigidBodyDynamics::Math::VectorNd& pts); + + /** + Calculates the value of a quintic Bezier derivative curve at value u. + @param u The independent variable of a Bezier curve, which ranges + between 0.0 and 1.0. + @param pts The locations of the control points in 1 dimension. + @param order The desired order of the derivative. Order must be >= 1 + + \b aborts \b + -u is outside [0,1] + -pts is not 6 elements long + -if order is less than 1 + @return The value of du/dx of Bezier curve located at u. + + Calculates the value of a quintic Bezier derivative curve at value u. + This calculation is acheived by taking the derivative of the row vector + uV and multiplying it by the 6x6 coefficient matrix associated with a + quintic Bezier curve, by the vector of Bezier control points, pV, in a + particular dimension. + + Pseudo code for the first derivative (order == 1) would be + \verbatim + uV = [5*u^4 4*u^3 3*u^2 2u 1 0]; + + cM = [ -1 5 -10 10 -5 1; + 5 -20 30 -20 5 0; + -10 30 -30 10 0 0; + 10 -20 10 0 0 0; + -5 5 0 0 0 0; + 1 0 0 0 0 0 ]; + pV = [x1; x2; x3; x4; x5; x6]; + + dxdu = (uV*cM)*pV + \endverbatim + + Note that the derivative of uV only needed to be computed to compute + dxdu. This process is continued for all 5 derivatives of x(u) until + the sixth and all following derivatives, which are 0. Higher derivatives + w.r.t. to U are less expensive to compute than lower derivatives. + + Computational Costs + \verbatim + dy/dx : ~50 flops + d2x/du2: ~43 flops + d3x/du3: ~34 flops + d4x/du4: ~26 flops + d5x/du5: ~15 flops + d6x/du6: ~1 flop + \endverbatim + + + Example: + @code + double u = 0.5; + + //Choose the control points + RigidBodyDynamics::Math::VectorNd vX(6); + vX(0) = -2; + vX(1) = 0; + vX(2) = 0; + vX(3) = 4; + vX(4) = 4; + vX(5) = 6; + + double dxdu =calcQuinticBezierCurveDerivU(u,vX,1); + @endcode + */ + static double calcQuinticBezierCurveDerivU( + double u, + const RigidBodyDynamics::Math::VectorNd& pts, + int order); + + /** + Calculates the value of dydx of a quintic Bezier curve derivative at u. + + @param u The u value of interest. Note that u must be [0,1] + @param xpts The 6 control points associated with the x axis + @param ypts The 6 control points associated with the y axis + @param order The order of the derivative. Currently only orders from 1-6 + can be evaluated + + \b aborts \b + -If u is outside [0,1] + -If xpts is not 6 elements long + -If ypts is not 6 elements long + -If the order is less than 1 + -If the order is greater than 6 + @retval The value of (d^n y)/(dx^n) evaluated at u + + Calculates the value of dydx of a quintic Bezier curve derivative at u. + Note that a 2D Bezier curve can have an infinite number of derivatives, + because x and y are functions of u. Thus + + \verbatim + dy/dx = (dy/du)/(dx/du) + d^2y/dx^2 = d/du(dy/dx)*du/dx + = [(d^2y/du^2)*(dx/du) - (dy/du)*(d^2x/du^2)]/(dx/du)^2 + *(1/(dx/du)) + etc. + \endverbatim + + Computational Costs + + This obviously only functions when the Bezier curve in question has a + finite derivative. Additionally, higher order derivatives are more + numerically expensive to evaluate than lower order derivatives. For + example, here are the number of operations required to compute the + following derivatives + \verbatim + Name : flops + dy/dx : ~102 + d2y/dx2 : ~194 + d3y/dx3 : ~321 + d4y/dx4 : ~426 + d5y/dx5 : ~564 + d6y/dx6 : ~739 + \endverbatim + + Example: + @code + RigidBodyDynamics::Math::VectorNd vX(6), vY(6); + + double u = 0.5; + + vX(0) = 1; + vX(1) = 1.01164; + vX(2) = 1.01164; + vX(3) = 1.02364; + vX(4) = 1.02364; + vY(5) = 1.04; + + vY(0) = 0; + vY(1) = 3e-16; + vY(2) = 3e-16; + vY(3) = 0.3; + vY(4) = 0.3; + vY(5) = 1; + + + d2ydx2 = SegmentedQuinticBezierToolkit::calcQuinticBezierCurveDerivDYDX( + u,vX, vY, 2); + @endcode + + */ + static double calcQuinticBezierCurveDerivDYDX( + double u, + const RigidBodyDynamics::Math::VectorNd& xpts, + const RigidBodyDynamics::Math::VectorNd& ypts, + int order); + + + /** + Calculates the location of quintic Bezier curve control points to + create a C shaped curve like that shown in the figure. Using a series + of these simple and predictably shaped Bezier curves it is easy to build + quite complex curves. + + \image html fig_GeometryAddon_quinticCornerSections.png + + + @param x0 First intercept x location + @param y0 First intercept y location + @param dydx0 First intercept slope + @param x1 Second intercept x location + @param y1 Second intercept y location + @param dydx1 Second intercept slope + @param curviness A parameter that ranges between 0 and 1 to denote a + straight line or a curve + + \b aborts \b + -If the curviness parameter is less than 0, or greater than 1; + -If the points and slopes are chosen so that an "S" shaped curve would + be produced. This is tested by examining the points (x0,y0) and + (x1,y1) together with the intersection (xC,yC) of the lines beginning + at these points with slopes of dydx0 and dydx1 form a triangle. If the + line segment from (x0,y0) to (x1,y1) is not the longest line segment, + an exception is thrown. This is an overly conservative test as it + prevents very deep 'V' shapes from being respresented. + + @return a RigidBodyDynamics::Math::MatrixNd of 6 points Matrix(6,2) that + correspond to the X, and Y control points for a quintic Bezier curve that + has the above properties + + + Calculates the location of quintic Bezier curve control points to + create a C shaped curve that intersects points 0 (x0, y0) and point 1 + (x1, y1) with slopes dydx0 and dydx1 respectively, and a second + derivative of 0. The curve that results can approximate a line + (curviness = 0), or in a smooth C shaped curve (curviniess = 1) + + The current implementation of this function is not optimized in anyway + and has the following costs: + + Computational Costs + \verbatim + ~55 flops + \endverbatim + + + Example: + @code + double x0 = 1; + double y0 = 0; + double dydx0 = 0; + double x1 = 1.04; + double y1 = 1; + double dydx1 = 43; + double c = 0.75; + + RigidBodyDynamics::Math::MatrixNd p0 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0, y0, dydx0,x1,y1,dydx01, + c); + @endcode + + */ + static RigidBodyDynamics::Math::MatrixNd + calcQuinticBezierCornerControlPoints( double x0, double y0, + double dydx0, + double x1, double y1, + double dydx1, + double curviness); + + /* + This function numerically integrates the Bezier curve y(x). + + @param vX Values of x to evaluate the integral of y(x) + @param ic0 The initial value of the integral + @param intAcc Accuracy of the integrated solution + @param uTol Tolerance on the calculation of the intermediate u term + @param uMaxIter Maximum number of iterations allowed for u to reach its + desired tolerance. + @param mX The 6xn matrix of Bezier control points for x(u) + @param mY The 6xn matrix of Bezier control points for y(u) + + @param flag_intLeftToRight Setting this flag to true will evaluate the + integral from the left most point to the right most + point. Setting this flag to false will cause the + integral to be evaluated from right to left. + @param name Name of caller. + @return RigidBodyDynamics::Math::MatrixNd Col 0: X vector, Col 1: int(y(x)) + + + This function numerically integrates the Bezier curve y(x), when really + both x and y are specified in terms of u. Evaluate the integral at the + locations specified in vX and return the result. + + Computational Costs + + This the expense of this function depends on the number of points in + vX, the points for which the integral y(x) must be calculated. The + integral is evaluated using a Runge Kutta 45 integrator, and so each + point requires 6 function evaluations. + (http://en.wikipedia.org/wiki/Dormand%E2%80%93Prince_method) + + The cost of evaluating 1 Bezier curve y(x) scales with the number + of points in xVal: + \verbatim + ~1740 flops per point + \endverbatim + + The example below is quite involved, but just so it can show you an + example of how to create the array of Spline objects that approximate + the function u(x). Although the example has been created for only 1 + Bezier curve set, simply changing the size and entries of the matricies + _mX and _mY will allow multiple sets to be integrated. + + + Example: + @code + //Integrator and u tolerance settings + double INTTOL = 1e-12; + double UTOL = 1e-14; + int MAXITER= 10; + + //Make up a Bezier curve - these happen to be the control points + //for a tendon curve + RigidBodyDynamics::Math::MatrixNd _mX(6,1), _mY(6,1); + _mX(0)= 1; + _mX(1)= 1.01164; + _mX(2)= 1.01164; + _mX(3)= 1.02364; + _mX(4)= 1.02364; + _mX(5)= 1.04; + + _mY(0) = 0; + _mY(1) = 3.10862e-16; + _mY(2) = 3.10862e-16; + _mY(3) = 0.3; + _mY(4) = 0.3; + _mY(5) = 1; + + _numBezierSections = 1; + bool _intx0x1 = true; //Says we're integrating from x0 to x1 + ////////////////////////////////////////////////// + //Generate the set of splines that approximate u(x) + ////////////////////////////////////////////////// + RigidBodyDynamics::Math::VectorNd u(NUM_SAMPLE_PTS); + //Used for the approximate inverse + RigidBodyDynamics::Math::VectorNd x(NUM_SAMPLE_PTS); + //Used for the approximate inverse + + //Used to generate the set of knot points of the integral of y(x) + RigidBodyDynamics::Math::VectorNd + xALL(NUM_SAMPLE_PTS*_numBezierSections-(_numBezierSections-1)); + _arraySplineUX.resize(_numBezierSections); + int xidx = 0; + + for(int s=0; s < _numBezierSections; s++){ + //Sample the local set for u and x + for(int i=0;i 1){ + //Skip the last point of a set that has another set of points + //after it. Why? The last point and the starting point of the + //next set are identical in value. + if(i<(NUM_SAMPLE_PTS-1) || s == (_numBezierSections-1)){ + xALL(xidx) = x(i); + xidx++; + } + }else{ + xALL(xidx) = x(i); + xidx++; + } + } + //Create the array of approximate inverses for u(x) + _arraySplineUX[s] = SimTK::SplineFitter:: + fitForSmoothingParameter(3,x,u,0).getSpline(); + } + + + ////////////////////////////////////////////////// + //Compute the integral of y(x) and spline the result + ////////////////////////////////////////////////// + + RigidBodyDynamics::Math::VectorNd yInt = SegmentedQuinticBezierToolkit:: + calcNumIntBezierYfcnX(xALL,0,INTTOL, UTOL, MAXITER,_mX, _mY, + _arraySplineUX,_name); + + if(_intx0x1==false){ + yInt = yInt*-1; + yInt = yInt - yInt(yInt.nelt()-1); + } + + _splineYintX = SimTK::SplineFitter:: + fitForSmoothingParameter(3,xALL,yInt,0).getSpline(); + + @endcode + + + */ + +//MM: Can't port this over to RBDL as RBDL doesn't have an error +// controlled integrator. I could add this if a dependency +// like Boost was allowed. +// +// static RigidBodyDynamics::Math::MatrixNd +// calcNumIntBezierYfcnX( +// const RigidBodyDynamics::Math::VectorNd& vX, +// double ic0, +// double intAcc, +// double uTol, +// int uMaxIter, +// const RigidBodyDynamics::Math::MatrixNd& mX, +// const RigidBodyDynamics::Math::MatrixNd& mY, +// const SimTK::Array_& aSplineUX, +// bool flag_intLeftToRight,const std::string& name); + + + private: + + + /** + This function will print cvs file of the column vector col0 and the + matrix data. + + @param col0 A vector that must have the same number of rows as the + data matrix. This column vector is printed as the first + column + @param data A matrix of data + @param filename The name of the file to print + */ + static void printMatrixToFile( + const RigidBodyDynamics::Math::VectorNd& col0, + const RigidBodyDynamics::Math::MatrixNd& data, + std::string& filename); + + /** + @param curveFit a function that evaluates a curve + @param ctrlPts control point locations for the fitted Bezier curve + @param xVal the x values at the control points + @param yVal the y values at the control points + @param filename of the output file. + + */ + static void printBezierSplineFitCurves( + const Function_& curveFit, + RigidBodyDynamics::Math::MatrixNd& ctrlPts, + RigidBodyDynamics::Math::VectorNd& xVal, + RigidBodyDynamics::Math::VectorNd& yVal, + std::string& filename); + + /** + This function will return a value that is equal to u, except when u is + outside of[0,1], then it is clamped to be 0, or 1 + @param u The parameter to be clamped + @retval u but restricted to 0,1. + */ + static double clampU(double u); + + +///@cond +/** +Class that contains data that describes the Bezier curve set. This class is used +by the function calcNumIntBezierYfcnX, which evaluates the numerical integral +of a Bezier curve set. +*/ +class BezierData { + public: + /**A 6xn matrix of Bezier control points for the X axis (domain)*/ + RigidBodyDynamics::Math::MatrixNd _mX; + /**A 6xn matrix of Bezier control points for the Y axis (range)*/ + RigidBodyDynamics::Math::MatrixNd _mY; + /**An n element array containing the approximate spline fits of the + inverse function of x(u), namely u(x)*/ + //std::vector< std::vector > _aArraySplineUX; + /**The initial value of the integral*/ + double _initalValue; + /**The tolerance to use when computing u. Solving u(x) can only be done + numerically at the moment, first by getting a good guess (using the + splines) and then using Newton's method to polish the value up. This + is the tolerance that is used in the polishing stage*/ + double _uTol; + /**The maximum number of interations allowed when evaluating u(x) using + Newton's method. In practice the guesses are usually very close to the + actual solution, so only 1-3 iterations are required.*/ + int _uMaxIter; + /**If this flag is true the function is integrated from its left most + control point to its right most. If this flag is false, the function + is integrated from its right most control point to its left most.*/ + //bool _flag_intLeftToRight; + /**The starting value*/ + //double _startValue; + + /**The name of the curve being intergrated. This is used to generate + useful error messages when something fails*/ + std::string _name; +}; +///@endcond + + +}; + +} +} +} + + +#endif diff --git a/3rdparty/rbdl/addons/geometry/SmoothSegmentedFunction.cc b/3rdparty/rbdl/addons/geometry/SmoothSegmentedFunction.cc new file mode 100644 index 0000000..69094d9 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/SmoothSegmentedFunction.cc @@ -0,0 +1,844 @@ +/* -------------------------------------------------------------------------- * + * OpenSim: SmoothSegmentedFunction.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ + /* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ + +//============================================================================= +// INCLUDES +//============================================================================= +#include "SmoothSegmentedFunction.h" +#include +#include + +//============================================================================= +// STATICS +//============================================================================= +//using namespace SimTK; +//using namespace OpenSim; +using namespace std; +using namespace RigidBodyDynamics::Addons::Geometry; + +static bool DEBUG = false; +static double UTOL = std::numeric_limits::epsilon()*1e6; +static double INTTOL = std::numeric_limits::epsilon()*1e2; +static double SQRTEPS = std::sqrt(numeric_limits::epsilon()); +static int MAXITER = 20; +static int NUM_SAMPLE_PTS = 100; +//============================================================================= +// UTILITY FUNCTIONS +//============================================================================= +/* + DETAILED COMPUTATIONAL COSTS: + ========================================================================= + WITHOUT INTEGRAL + _________________________________________________________________________ + Function Comp Div Mult Add Assignments + _________________________________________________________________________ + member assign M:2, 9 + curve gen: m,m*100 m m*100 m m*100*(4) + +m*100(3) +m*100*(3) + + Function detail + Evaluations Function + m SimTK::SplineFitter:: + fitForSmoothingParameter(3,x,u,0).getSpline(); + Cost: ? + + m*100 SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveVal + Cost: Mult Add Assignments + 21 20 13 + + Total ~typically > 2100*m multiplications, additions, + > 1000*m assignments + > 100*m divisions + _________________________________________________________________________ + Comp Div Mult Add Assignments + Total: m+m*100(3) m*100 m*100*21 m*100*20 m*100*13 + +m+m*100*3 +m*100*4+9+M:2 + + m*Cost(SimTK::SplineFitter ...) + ========================================================================= + ADDITIONAL COST OF COMPUTING THE INTEGRAL CURVE + + Comp Div Mult Add Assign + RK45 Fn.Eval m*100*(156 12 618 390 456) + RK45 Overhead m*100*(? ? ? ? ? ) + Spline cost m*100*(? ? ? ? ? ) + + Total: ~typically > 100,000's mult, additions, assignments + > 40,000 comparisions + > 3000 divisions + + ========================================================================= + M: Matrix + V: Vector + + N.B. These costs are dependent on SegmentedQuinticBezierToolkit +*/ +SmoothSegmentedFunction:: + SmoothSegmentedFunction( + const RigidBodyDynamics::Math::MatrixNd& mX, + const RigidBodyDynamics::Math::MatrixNd& mY, + double x0, double x1, + double y0, double y1, + double dydx0, double dydx1, + const std::string& name): + _x0(x0),_x1(x1),_y0(y0),_y1(y1),_dydx0(dydx0),_dydx1(dydx1), + _name(name) +{ + + + _numBezierSections = mX.cols(); + _mXVec.resize(_numBezierSections); + _mYVec.resize(_numBezierSections); + for(int s=0; s < _numBezierSections; s++){ + _mXVec[s] = mX.col(s); + _mYVec[s] = mY.col(s); + } +} + +//============================================================================== + SmoothSegmentedFunction::SmoothSegmentedFunction(): + _x0(NAN),_x1(NAN), + _y0(NAN),_y1(NAN), + _dydx0(NAN),_dydx1(NAN),_name("NOT_YET_SET") + { + //_arraySplineUX.resize(0); + _mXVec.resize(0); + _mYVec.resize(0); + //_splineYintX = SimTK::Spline(); + _numBezierSections = (int)NAN; + } + +//============================================================================== +void SmoothSegmentedFunction:: + updSmoothSegmentedFunction( + const RigidBodyDynamics::Math::MatrixNd& mX, + const RigidBodyDynamics::Math::MatrixNd& mY, + double x0, double x1, + double y0, double y1, + double dydx0, double dydx1, + const std::string& name) +{ + + if(mX.rows() != 6 || mY.rows() != 6 || mX.cols() != mY.cols() ){ + cerr<<"SmoothSegmentedFunction::updSmoothSegmentedFunction " + << _name.c_str() + <<": matrices mX and mY must have 6 rows, and the same" + <<" number of columns." + << endl; + assert(0); + abort(); + } + + _x0 =x0; + _x1 =x1; + _y0 =y0; + _y1 =y1; + _dydx0=dydx0; + _dydx1=dydx1; + + if(mX.cols() != _mXVec.size()){ + _mXVec.resize(mX.cols()); + _mYVec.resize(mY.cols()); + } + + _numBezierSections = mX.cols(); + for(int s=0; s < mX.cols(); s++){ + _mXVec[s] = mX.col(s); + _mYVec[s] = mY.col(s); + } + + _name = name; +} +//============================================================================== +void SmoothSegmentedFunction::shift(double xShift, double yShift) +{ + _x0 += xShift; + _x1 += xShift; + _y0 += yShift; + _y1 += yShift; + + for(int i=0; i<_mXVec.size();++i){ + for(int j=0; j<_mXVec.at(i).rows();++j){ + _mXVec.at(i)[j] += xShift; + _mYVec.at(i)[j] += yShift; + } + } + +} + +void SmoothSegmentedFunction::scale(double xScale, double yScale) +{ + + if( abs( xScale ) <= SQRTEPS){ + cerr<<"SmoothSegmentedFunction::scale " + << _name.c_str() + <<": xScale must be greater than sqrt(eps). Setting xScale to such" + <<" a small value will cause the slope of the curve to approach " + <<" infinity, or become undefined." + << endl; + assert(0); + abort(); + } + + _x0 *= xScale; + _x1 *= xScale; + _y0 *= yScale; + _y1 *= yScale; + _dydx0 *= yScale/xScale; + _dydx1 *= yScale/xScale; + + for(int i=0; i<_mXVec.size();++i){ + for(int j=0; j<_mXVec.at(i).rows();++j){ + _mXVec.at(i)[j] *= xScale; + _mYVec.at(i)[j] *= yScale; + } + } + +} + + +//============================================================================== + + + /*Detailed Computational Costs + ________________________________________________________________________ + If x is in the Bezier Curve + Name Comp. Div. Mult. Add. Assign. +_______________________________________________________________________ + SegmentedQuinticBezierToolkit:: + calcIndex 3*m+2 1*m 3 + *calcU 15 2 82 42 60 + calcQuinticBezierCurveVal 21 20 13 + total 15+3*m+2 2 103 62+1*m 76 + + *Approximate. Uses iteration +________________________________________________________________________ +If x is in the linear region + + Name Comp. Div. Mult. Add. Assign. + 1 1 2 1 +________________________________________________________________________ + + */ + +double SmoothSegmentedFunction::calcValue(double x) const +{ + double yVal = 0; + if(x >= _x0 && x <= _x1 ) + { + int idx = SegmentedQuinticBezierToolkit::calcIndex(x,_mXVec); + double u = SegmentedQuinticBezierToolkit:: + calcU(x,_mXVec[idx], UTOL, MAXITER); + yVal = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveVal(u,_mYVec[idx]); + }else{ + if(x < _x0){ + yVal = _y0 + _dydx0*(x-_x0); + }else{ + yVal = _y1 + _dydx1*(x-_x1); + } + } + + return yVal; +} + +double SmoothSegmentedFunction::calcValue( + const RigidBodyDynamics::Math::VectorNd& ax) const +{ + + if( !(ax.size() == 1) ){ + cerr<<"SmoothSegmentedFunction::calcValue " << _name.c_str() + <<": Argument x must have only 1 element, as this function is " + << "designed only for 1D functions, but a function with " + << ax.size() << " elements was entered" + << endl; + assert(0); + abort(); + + } + + return calcValue(ax[0]); +} + +/*Detailed Computational Costs +________________________________________________________________________ +If x is in the Bezier Curve, and dy/dx is being evaluated + Name Comp. Div. Mult. Add. Assign. +_______________________________________________________________________ +Overhead: + SegmentedQuinticBezierToolkit:: + calcIndex 3*m+2 1*m 3 + *calcU 15 2 82 42 60 + Derivative Evaluation: + **calcQuinticBezierCurveDYDX 21 20 13 + dy/du 20 19 11 + dx/du 20 19 11 + dy/dx 1 + + total 17+3*m 3 143 m+100 98 + +*Approximate. Uses iteration +**Higher order derivatives cost more +________________________________________________________________________ +If x is in the linear region + + Name Comp. Div. Mult. Add. Assign. + 1 1 +________________________________________________________________________ + */ + +double SmoothSegmentedFunction::calcDerivative(double x, int order) const +{ + //return calcDerivative( SimTK::Array_(order,0), + // RigidBodyDynamics::Math::VectorNd(1,x)); + double yVal = 0; + + //QUINTIC SPLINE + + + if(order==0){ + yVal = calcValue(x); + }else{ + if(x >= _x0 && x <= _x1){ + int idx = SegmentedQuinticBezierToolkit::calcIndex(x,_mXVec); + double u = SegmentedQuinticBezierToolkit:: + calcU(x,_mXVec[idx],UTOL,MAXITER); + yVal = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(u, _mXVec[idx], + _mYVec[idx], order); +/* + std::cout << _mX(3, idx) << std::endl; + std::cout << _mX(idx) << std::endl;*/ + }else{ + if(order == 1){ + if(x < _x0){ + yVal = _dydx0; + }else{ + yVal = _dydx1;} + }else{ + yVal = 0;} + } + } + + return yVal; +} + + + +double SmoothSegmentedFunction:: + calcDerivative( const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& ax) const +{ + /* + for(int i=0; i < (signed)derivComponents.size(); i++){ + SimTK_ERRCHK2_ALWAYS( derivComponents[i] == 0, + "SmoothSegmentedFunction::calcDerivative", + "%s: derivComponents can only be populated with 0's because " + "SmoothSegmentedFunction is only valid for a 1D function, but " + "derivComponents had a value of %i in it", + _name.c_str(), derivComponents[i]); + } + SimTK_ERRCHK2_ALWAYS( derivComponents.size() <= 6, + "SmoothSegmentedFunction::calcDerivative", + "%s: calcDerivative is only valid up to a 6th order derivative" + " but derivComponents had a size of %i", + _name.c_str(), derivComponents.size()); + + SimTK_ERRCHK2_ALWAYS( ax.size() == 1, + "SmoothSegmentedFunction::calcValue", + "%s: Argument x must have only 1 element, as this function is " + "designed only for 1D functions, but ax had a size of %i", + _name.c_str(), ax.size()); + */ + + for(int i=0; i < (signed)derivComponents.size(); i++){ + if( !(derivComponents[i] == 0)){ + cerr << "SmoothSegmentedFunction::calcDerivative " + << _name.c_str() + <<": derivComponents can only be populated with 0's because " + << "SmoothSegmentedFunction is only valid for a 1D function," + << " but derivComponents had a value of " + << derivComponents[i] << " in it" + << endl; + assert(0); + abort(); + + } + } + + if( !(derivComponents.size() <= 6)){ + cerr << "SmoothSegmentedFunction::calcDerivative " << _name.c_str() + << ": calcDerivative is only valid up to a 6th order derivative" + << " but derivComponents had a size of " + << derivComponents.size() + << endl; + assert(0); + abort(); + } + + if( !(ax.size() == 1) ){ + cerr << "SmoothSegmentedFunction::calcValue " << _name.c_str() + << ": Argument x must have only 1 element, as this function is " + << "designed only for 1D functions, but ax had a size of " + << ax.size() + << endl; + assert(0); + abort(); + } + + + return calcDerivative(ax[0], derivComponents.size()); +} + +/*Detailed Computational Costs +________________________________________________________________________ +If x is in the Bezier Curve, and dy/dx is being evaluated + Name Comp. Div. Mult. Add. Assign. +_______________________________________________________________________ + *spline.calcValue 7 2 3 1 + +*Approximate cost of evaluating a cubic spline with 100 knots, where +the bisection method is used to find the correct index +________________________________________________________________________ +If x is in the linear region + + Name Comp. Div. Mult. Add. Assign. + *spline.calcValue 1 2 3 1 + integral eval 2 4 5 1 + total 3 6 8 2 + +*Approximate cost of evaluating a cubic spline at its last knot point +________________________________________________________________________ + +*/ +/* +double SmoothSegmentedFunction::calcIntegral(double x) const +{ + SimTK_ERRCHK1_ALWAYS(_computeIntegral, + "SmoothSegmentedFunction::calcIntegral", + "%s: This curve was not constructed with its integral because" + "computeIntegral was false",_name.c_str()); + + double yVal = 0; + if(x >= _x0 && x <= _x1){ + yVal = _splineYintX.calcValue(RigidBodyDynamics::Math::VectorNd(1,x)); + }else{ + //LINEAR EXTRAPOLATION + if(x < _x0){ + RigidBodyDynamics::Math::VectorNd tmp(1); + tmp(0) = _x0; + double ic = _splineYintX.calcValue(tmp); + if(_intx0x1){//Integrating left to right + yVal = _y0*(x-_x0) + + _dydx0*(x-_x0)*(x-_x0)*0.5 + + ic; + }else{//Integrating right to left + yVal = -_y0*(x-_x0) + - _dydx0*(x-_x0)*(x-_x0)*0.5 + + ic; + } + }else{ + RigidBodyDynamics::Math::VectorNd tmp(1); + tmp(0) = _x1; + double ic = _splineYintX.calcValue(tmp); + if(_intx0x1){ + yVal = _y1*(x-_x1) + + _dydx1*(x-_x1)*(x-_x1)*0.5 + + ic; + }else{ + yVal = -_y1*(x-_x1) + - _dydx1*(x-_x1)*(x-_x1)*0.5 + + ic; + } + } + } + + return yVal; +} +*/ +/* +bool SmoothSegmentedFunction::isIntegralAvailable() const +{ + return _computeIntegral; +} +*/ + +/* +bool SmoothSegmentedFunction::isIntegralComputedLeftToRight() const +{ + return _intx0x1; +} +*/ + +int SmoothSegmentedFunction::getArgumentSize() const +{ + return 1; +} + +int SmoothSegmentedFunction::getMaxDerivativeOrder() const +{ + return 6; +} + +std::string SmoothSegmentedFunction::getName() const +{ + return _name; +} + +void SmoothSegmentedFunction::setName(const std::string &name) +{ + _name = name; +} + +RigidBodyDynamics::Math::VectorNd + SmoothSegmentedFunction::getCurveDomain() const +{ + RigidBodyDynamics::Math::VectorNd xrange(2); + + xrange[0] = 0; + xrange[1] = 0; + if (!_mXVec.empty()) { + xrange[0] = _mXVec[0][0]; + xrange[1] = _mXVec[_mXVec.size()-1][_mXVec[0].size()-1]; + } + return xrange; +} + +/////////////////////////////////////////////////////////////////////////////// +// Utility functions +/////////////////////////////////////////////////////////////////////////////// + +/*Detailed Computational Costs + +_______________________________________________________________________ + Name Comp. Div. Mult. Add. Assign. +_______________________________________________________________________ + + *overhead (17+3*m 2 82 42+m 63)*7 + 119+21*m 14 574 294+7m 441 + + calcValue 21 20 13 + calcDerivative: dy/dx 1 40 38 22 + : d2y/dx2 2 78 73 23 + : d3y/dx3 4 118 105 58 + : d4y/dx4 5 168 137 71 + : d5y/dx5 7 236 170 88 + : d6y/dx6 9 334 209 106 + + **calcIntegral 7 2 3 1 + + total per point 126+21*m 42 1571 1049 823 + total per elbow 126k+21k*m 42k 1571k 1049k 823k + + *Approximate. Overhead associated with finding the correct Bezier + spline section, and evaluating u(x). + Assumes 2 Newton iterations in calcU + + **Approximate. Includes estimated cost of evaluating a cubic spline + with 100 knots +*/ +RigidBodyDynamics::Math::MatrixNd + SmoothSegmentedFunction::calcSampledCurve(int maxOrder, + double domainMin, + double domainMax) const{ + + int pts = 1; //Number of points between each of the spline points used + //to fit u(x), and also the integral spline + if( !(maxOrder <= getMaxDerivativeOrder()) ){ + cerr << "SmoothSegmentedFunction::calcSampledCurve " + << "Derivative order past the maximum computed order requested" + << endl; + assert(0); + abort(); + } + + double x0,x1,delta; + //y,dy,d1y,d2y,d3y,d4y,d5y,d6y,iy + RigidBodyDynamics::Math::VectorNd + midX(NUM_SAMPLE_PTS*_numBezierSections-(_numBezierSections-1)); + RigidBodyDynamics::Math::VectorNd x(NUM_SAMPLE_PTS); + + //Generate a sample of X values inside of the curve that is denser where + //the curve is more curvy. + double u; + int idx = 0; + for(int s=0; s < _numBezierSections; s++){ + //Sample the local set for u and x + for(int i=0;i 1){ + //Skip the last point of a set that has another set of points + //after it. Why? The last point and the starting point of the + //next set are identical in value. + if(i<(NUM_SAMPLE_PTS-1) || s == (_numBezierSections-1)){ + midX[idx] = x[i]; + idx++; + } + }else{ + midX[idx] = x[i]; + idx++; + } + } + } + + + RigidBodyDynamics::Math::VectorNd xsmpl(pts*(midX.size()-1)+2*10*pts); + + RigidBodyDynamics::Math::MatrixNd results; + + /* + if(_computeIntegral){ + results.resize(pts*(midX.size()-1)+2*10*pts,maxOrder+2+1); + }else{ + + } + */ + results.resize(pts*(midX.size()-1)+2*10*pts,maxOrder+2); + + //Array initialization is so ugly ... + std::vector d1y(1),d2y(2),d3y(3),d4y(4),d5y(5),d6y(6); + d1y[0]=0; + d2y[0] = 0; + d2y[1] = 0; + for(int i=0;i<3;i++) + d3y[i]=0; + for(int i=0;i<4;i++) + d4y[i]=0; + for(int i=0;i<5;i++) + d5y[i]=0; + for(int i=0;i<6;i++) + d6y[i]=0; + + //generate some sample points in the extrapolated region + idx = 0; + x0 = _x0 - 0.1*(_x1-_x0); + if(domainMin < x0) + x0 = domainMin; + + x1 = _x0; + delta = (0.1)*(x1-x0)/(pts); + + for(int j=0; j x1) + x1 = domainMax; + + delta = (1.0/9.0)*(x1-x0)/(pts); + + for(int j=0;j=1) + results(i,2) = calcDerivative(d1y,ax); + + if(maxOrder>=2) + results(i,3) = calcDerivative(d2y,ax); + + if(maxOrder>=3) + results(i,4) = calcDerivative(d3y,ax); + + if(maxOrder>=4) + results(i,5) = calcDerivative(d4y,ax); + + if(maxOrder>=5) + results(i,6) = calcDerivative(d5y,ax); + + if(maxOrder>=6) + results(i,7) = calcDerivative(d6y,ax); + + /* + if(_computeIntegral){ + results(i,maxOrder+2) = calcIntegral(ax(0)); + } + */ + } + return results; +} + +void SmoothSegmentedFunction::getXControlPoints( + RigidBodyDynamics::Math::MatrixNd& mat) const +{ + mat.resize(_mXVec.size(), _mXVec.at(0).size()); + + for(int i=0; i<_mXVec.size();++i){ + for(int j=0; j<_mXVec.size();++j){ + mat(i,j) = _mXVec.at(i)[j]; + } + } + +} +void SmoothSegmentedFunction::getYControlPoints( + RigidBodyDynamics::Math::MatrixNd& mat) const +{ +mat.resize(_mYVec.size(), _mYVec.at(0).size()); + +for(int i=0; i<_mYVec.size();++i){ + for(int j=0; j<_mYVec.size();++j){ + mat(i,j) = _mYVec.at(i)[j]; + } +} + +} + + +/*Detailed Computational Costs + +_______________________________________________________________________ + Name Comp. Div. Mult. Add. Assign. +_______________________________________________________________________ + + *overhead (17+3*m 2 82 42+m 63)*3 + 51+9m 6 246 126+3m 189 + + calcValue 21 20 13 + calcDerivative : dy/dx 1 40 38 22 + : d2y/dx2 2 78 73 23 + + **calcIntegral 7 2 3 1 + + total per point 58+9m 9 387 260+3m 248 + total per elbow 5.8k+900m 900 38.7k 26k+300m 24.8k + + *Approximate. Overhead associated with finding the correct Bezier + spline section, and evaluating u(x). + Assumes 2 Newton iterations in calcU + + **Approximate. Includes estimated cost of evaluating a cubic spline + with 100 knots +*/ +void SmoothSegmentedFunction::printCurveToCSVFile( + const std::string& path, + const std::string& fileNameWithoutExtension, + double domainMin, + double domainMax) const +{ + //Only compute up to the 2nd derivative + RigidBodyDynamics::Math::MatrixNd results = + calcSampledCurve(2,domainMin,domainMax); + std::vector colNames(results.cols()); + colNames[0] = "x"; + colNames[1] = "y"; + colNames[2] = "dy/dx"; + colNames[3] = "d2y/dx2"; + /* + if(results.cols() == 5){ + colNames[4] = "int_y(x)"; + } + */ + std::string fname = fileNameWithoutExtension; + fname.append(".csv"); + printMatrixToFile(results,colNames,path,fname); +} +/* +This function will print cvs file of the column vector col0 and the matrix data +*/ +void SmoothSegmentedFunction:: + printMatrixToFile( RigidBodyDynamics::Math::MatrixNd& data, + std::vector& colNames, + const std::string& path, + const std::string& filename) const +{ + + ofstream datafile; + std::string fullpath = path; + + if(fullpath.length() > 0) + fullpath.append("/"); + + fullpath.append(filename); + + datafile.open(fullpath.c_str(),std::ios::out); + + if(!datafile){ + datafile.close(); + cerr << "SmoothSegmentedFunction::printMatrixToFile " + << _name.c_str() << ": Failed to open the file path: " + << fullpath.c_str() + << endl; + assert(0); + abort(); + } + + + for(int i = 0; i < (signed)colNames.size(); i++){ + if(i < (signed)colNames.size()-1) + datafile << colNames[i] << ","; + else + datafile << colNames[i] << "\n"; + } + + for(int i = 0; i < data.rows(); i++){ + for(int j = 0; j < data.cols(); j++){ + if(j +#include + + + + /** + This class contains the quintic Bezier curves, x(u) and y(u), that have been + created by SmoothSegmentedFunctionFactory to follow a physiologically + meaningful muscle characteristic. A SmoothSegmentedFunction cannot be + created directly,you must use SmoothSegmentedFunctionFactory to create the + muscle curve of interest. + + Computational Cost Details + All computational costs assume the following operation costs: + + \verbatim + Operation Type : #flops + +,-,=,Boolean Op : 1 + / : 10 + sqrt: 20 + trig: 40 + \endverbatim + + These relative weightings will vary processor to processor, and so any + of the quoted computational costs are approximate. + + + RBDL Port Notes +The port of this code from OpenSim has been accompanied by a few changes: + +1. The 'calcIntegral' method has been removed. Why? This function + relied on having access to a variable-step error controlled + integrator. There is no such integrator built into RBDL. Rather + than add a dependency (by using Boost perhaps) this functionality + has been removed. + +2. The function name .printMuscleCurveToFile(...) has been changed + to .printCurveToFile(). + + + @author Matt Millard + @version 0.0 + + + */ + +namespace RigidBodyDynamics { + namespace Addons { + namespace Geometry{ + + class SmoothSegmentedFunction : public Function_ + { + + + public: + + ///The default constructor, which populates the member data fields with + ///NaN's + SmoothSegmentedFunction(); + + //SmoothSegmentedFunction(); + /** + Creates a set of quintic Bezier curves. + + @param mX The matrix of quintic Bezier x point locations (6xn). + Each column vector is the 6 control points required + for each quintic Bezier curve. For C0 continuity + adjacent columns must share the last and first control + points. For C1 continuity the last 2 and first two + control points of adjacent curves should be on the same + curve. + + @param mY The matrix of quintic Bezier y point locations (6xn). + + @param x0 The minimum x value. This is used for the linear + extrapolation of the Bezier curve. This parameter is + explicitly asked for, rather than computed, to prevent + rounding error from reducing the accuracy of the + linear extrapolation. + @param x1 The maximum x value. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param y0 The value of y(x) at x=x0. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param y1 The value of y(x) at x=x1. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param dydx0 The value of dy/dx at x=x0. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param dydx1 The value of dy/dx at x=x1. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param name The name of the data this SmoothSegmentedFunction. This name + is used to make human-readable error messages and to + generate sensible names when printing the curve to file. + + Computational Costs + Generating the integral curve is not cheap, and so should only be used + when if it will be evaluated during a simulation. + \verbatim + Computatonal Cost Per Bezier Section: + Without Integral : 4,100 flops + \endverbatim + + */ + SmoothSegmentedFunction( + const RigidBodyDynamics::Math::MatrixNd& mX, + const RigidBodyDynamics::Math::MatrixNd& mY, + double x0, double x1, + double y0, double y1, + double dydx0, double dydx1, + const std::string& name); + + + /** + Updates the Bezier curve conrol points. The updated curve must have + the same number of control points as the original curve. + + @param mX The matrix of quintic Bezier x point locations (6xn). + Each column vector is the 6 control points required + for each quintic Bezier curve. For C0 continuity + adjacent columns must share the last and first control + points. For C1 continuity the last 2 and first two + control points of adjacent curves should be on the same + curve. + + @param mY The matrix of quintic Bezier y point locations (6xn). + + @param x0 The minimum x value. This is used for the linear + extrapolation of the Bezier curve. This parameter is + explicitly asked for, rather than computed, to prevent + rounding error from reducing the accuracy of the + linear extrapolation. + @param x1 The maximum x value. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param y0 The value of y(x) at x=x0. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param y1 The value of y(x) at x=x1. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param dydx0 The value of dy/dx at x=x0. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param dydx1 The value of dy/dx at x=x1. This is used for the linear + extrapolation and is required for the same reasons + as x0. + @param name The name of the data this SmoothSegmentedFunction. This name + is used to make human-readable error messages and to + generate sensible names when printing the curve to file. + + */ + void updSmoothSegmentedFunction( + const RigidBodyDynamics::Math::MatrixNd& mX, + const RigidBodyDynamics::Math::MatrixNd& mY, + double x0, double x1, + double y0, double y1, + double dydx0, double dydx1, + const std::string& name); + + /** + This function will shift the entire SmoothSegmentedFunction by xShift + and yShift. Setting xShift = yShift = 0.0 will leave the curve unchanged. + + @param xShift - the amount to shift the curve in the x-direction. + @param yShift - the amount to shift the curve in the y-direction + */ + void shift(double xShift, double yShift); + + /** + This function will scale the curve in the x and y directions. Setting + xScale=yScale=1.0 will leave the curve unchanged. + + \b aborts \b + -If abs(xScale) < sqrt(eps) + + @param xScale: the amount to scale the curve in the x direction + @param yScale: the amount to scale the curve in the y direction + */ + void scale(double xScale, double yScale); + + + + /**Calculates the value of the curve this object represents. + + @param x The domain point of interest + + \b aborts \b + -If ax does not have a size of 1 + @returns The value of the curve + + + The curve is parameterized as a set of Bezier curves. If x is within the + domain of these Bezier curves they will be evaluated. If x is outside + of the domain of these Bezier curves a linear extrapolation will be + evalulated + + + Computational Costs + \verbatim + x in curve domain : ~282 flops + x in linear section: ~5 flops + \endverbatim + */ + double calcValue(double x) const; + + + + /**Calculates the value of the derivative of the curve this object + represents. + + @param x The domain point of interest. + + @param order The order of the derivative to compute. Note that order must + be between 0 and 2. Calling 0 just calls calcValue. + + \b aborts \b + -If anything but 0's are stored in derivComponents + -If more than the 6th derivative is asked for + -If ax has a size other than 1 + + @return The value of the d^ny/dx^n th derivative evaluated at x + + + + Computational Costs + \verbatim + x in curve domain : ~391 flops + x in linear section: ~2 flops + \endverbatim + + */ + double calcDerivative(double x, int order) const; + + + + + /*This will return the value of the integral of this objects curve + evaluated at x. + + @param x the domain point of interest + + \b aborts \b + -If the function does not have a pre-computed integral + @return the value of the functions integral evaluated at x + + The integral is approximate, though its errors are small. + The integral is computed by numerically integrating the function when + the constructor for this class is called (if computeIntegral is true) and + then splining the result, thus the regions between the knot points may + have some error in them. A very fine mesh of points is used to create the + spline so the errors will be small + + Computational Costs + \verbatim + x in curve domain : ~13 flops + x in linear section: ~19 flops + \endverbatim + + */ + //double calcIntegral(double x) const; + + /* + Returns a bool that indicates if the integral curve has been computed. + + @return true if the integral of this function is available, false if + it has not been computed. + */ + //bool isIntegralAvailable() const; + + /* + Returns a bool that indicates if the integral computed is compuated left + to right, or right to left. + + @return true if the integral was computed left to right, and false if the + integral was computed right to left. Note that the output of + this function is only valid if isIntegralAvailable() returns + true. + */ + //bool isIntegralComputedLeftToRight() const; + + /** + Returns a string that is the name for this curve, which is set at the + time of construction and cannot be changed after construction. + + @return The string name this object was given during construction*/ + std::string getName() const; + + /** + Sets the name of the SmoothSegmentedFunction object. + + @param name The name of the data this SmoothSegmentedFunction. This name + is used to make human-readable error messages and to + generate sensible names when printing the curve to file. + */ + void setName(const std::string &name); + + /** + This function returns a SimTK::Vec2 that contains in its 0th element + the lowest value of the curve domain, and in its 1st element the highest + value in the curve domain of the curve. Outside of this domain the curve + is approximated using linear extrapolation. + + @return The minimum and maximum value of the domain, x, of the curve + y(x). Within this range y(x) is a curve, outside of this range + the function y(x) is a C2 (continuous to the second + derivative) linear extrapolation*/ + RigidBodyDynamics::Math::VectorNd getCurveDomain() const; + + /**This function will generate a csv file (of 'name_curveName.csv', where + name is the one used in the constructor) of the muscle curve, and + 'curveName' corresponds to the function that was called from + SmoothSegmentedFunctionFactory to create the curve. + + @param path The full path to the location. Note '/' slashes must be used, + and do not put a '/' after the last folder. + @param fileNameWithoutExtension The name of the file to write, not + including the file extension + @param domainMin + the left most domain point of the curve to print. The curve + will extend to at least this point. + @param domainMax + the right most domain point of the curve to print. The + printed curve will extend at least to this point, perhaps + beyond. + \b aborts \b + -If the filename is empty + + For example the tendon + curve for a muscle named 'glutmax' will be: + + 'glutmax_tendonForceLengthCurve.csv' + + The file will contain the following columns: + + \verbatim + Col# 1, 2, 3, 4, 5 + x, y, dy/dx, d2y/dx2, iy + \endverbatim + + Where iy is the integral of y(x). If the curve has been set not to have + an integral, this column will not exist. + + The curve will be sampled from its linear extrapolation region, through + the curve, out to the other linear extrapolation region. The width of + each linear extrapolation region is 10% of the entire range of x, or + 0.1*(x1-x0). + + The number of rows used will vary from curve to curve. Each quintic + Bezier curve section will have 100 samples. Each linearily extrapolated + region will have 10 samples each. Some muscle curves (the tendon, + parallel elements, compressive elements) consist of only 1 elbow, and so + these matrices will have only 100+20 rows. The force velocity curve is + made up of 2 elbows and will have 200+20 rows. The active force length + curve has 5 elbows, and so its sampled matrix will have 500+20 rows + + Computational Costs + This varies depending on the curve (as mentioned above). + \verbatim + ~97,400 to 487,000 flops + \endverbatim + + Example + To read the csv file with a header in from Matlab, you need to use + csvread set so that it will ignore the header row. This is accomplished + by using the extra two numerical arguments for csvread to tell the + function to begin reading from the 1st row, and the 0th index (csvread + is 0 indexed). + \verbatim + data=csvread('test_tendonForceLengthCurve.csv',1,0); + \endverbatim + + */ + void printCurveToCSVFile(const std::string& path, + const std::string& fileNameWithoutExtension, + double domainMin, + double domainMax) const; + + + /** + @param maxOrder The maximum derivative order to compute + @param domainMin + @param domainMax + \b aborts \b + -If the requested derivatve order is greater than + getMaxDerivativeOrder() + @returns a matrix populated with x,y,dy/dx ... d^ny/dx^n,iy + + + This function will generate a RigidBodyDynamics::Math::MatrixNd populated + with samples of the muscle curves values, derivatives (up to 6) and its + first integral (if available). The matrix has the following columns: + + \verbatim + Col# 1, 2, 3, 4, 5, 6, 7, 8, 9, + x, y, dy/dx, d2y/dx2, d3y/dx3, d4y/dx4, d5y/dx5, d6y/dx6, iy + \endverbatim + + Where iy is the integral of y(x). If the curve has been set not to have + an integral, this column will not exist. + + The curve will be sampled from its + linear extrapolation region, through the curve, out to the other linear + extrapolation region. The width of each linear extrapolation region is + 10% of the entire range of x, or 0.1*(x1-x0). + + The rows used will vary from curve to curve. Each quintic Bezier curve + section will have 100 samples + 20 samples for the linear extrapolation + region. Some muscle curves (the tendon, parallel elements, compressive + elements) consist of only 1 elbow, and so these matrices will have only + 100+20 rows. The force velocity curve is made up of 2 elbows and will + have 200+20 rows. The active force length curve has 5 elbows, and so its + sampled matrix will have 500+20 rows + + */ + RigidBodyDynamics::Math::MatrixNd calcSampledCurve(int maxOrder, + double domainMin, + double domainMax) const; + + + void getXControlPoints(RigidBodyDynamics::Math::MatrixNd& mat) const; + void getYControlPoints(RigidBodyDynamics::Math::MatrixNd& mat) const; + + private: + + + /**Nov 2015: Not needed in the RBDL port. + Array of spline fit functions X(u) for each Bezier elbow*/ + //Nov 2015: Not needed in the RBDL port. + //SimTK::Array_ _arraySplineUX; + /**Nov 2015: Not included in the RBDL port (RBDL doesn't have an + error controlled integrator to generate this curve) + Spline fit of the integral of the curve y(x)*/ + //SimTK::Spline _splineYintX; + + /**Bezier X1,...,Xn control point locations. Control points are + stored in 6x1 vectors in the order above*/ + std::vector _mXVec; + /**Bezier Y1,...,Yn control point locations. Control points are + stored in 6x1 vectors in the order above*/ + std::vector _mYVec; + + /**The number of quintic Bezier curves that describe the relation*/ + int _numBezierSections; + + /**The minimum value of the domain*/ + double _x0; + /**The maximum value of the domain*/ + double _x1; + /**The minimum value of the range*/ + double _y0; + /**The maximum value of the range*/ + double _y1; + /**The slope at _x0*/ + double _dydx0; + /**The slope at _x1*/ + double _dydx1; + /*This is the users */ + //bool _computeIntegral; + + /*This variable, when true, indicates that the user wants the integral + from left to right (x0 to x1). If it is false, the integral from right + to left (x1 to x0) is computed*/ + //bool _intx0x1; + /**The name of the function**/ + std::string _name; + + /**No human should be constructing a SmoothSegmentedFunction, so the + constructor is made private so that mere mortals cannot look at it. + SmoothSegmentedFunctionFactory should be used to create + MuscleCurveFunctions and that's why its a friend*/ + friend class SmoothSegmentedFunctionFactory; + + + + + + /** + This function will print cvs file of the column vector col0 and the + matrix data + + @param data A matrix of data + @param colnames Array of column headings + @param path The desired path to the folder to write the file + @param filename The name of the file to print + \b aborts \b + -If the desired file cannot be created and openened, perhaps + because the path doesn't exist. + */ + void printMatrixToFile( RigidBodyDynamics::Math::MatrixNd& data, + std::vector& colnames, + const std::string& path, + const std::string& filename) const; + + /** + Refer to the documentation for calcValue(double x) + because this function is identical in function to + calcValue(double x), but requires different inputs. + This is a required virtual function required because this class extends + the Function interface. + */ + double calcValue(const RigidBodyDynamics::Math::VectorNd& x) const; + /*virtual*/ + + /** Refer to the documentation for calcDerivative(double x, int order) + because this function is identical in function to + calcDerivative(double x, int order), but requires different inputs. + This is a required virtual function required because this class extends + the Function interface.*/ + double calcDerivative(const std::vector& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const; + /*virtual*/ + + /**This will return the size of the vector that the + calcValue(const RigidBodyDynamics::Math::VectorNd& x) require. This is a + required virtual function required because this class extends the + Function interface, though is only needed if you call + + double calcValue(const RigidBodyDynamics::Math::VectorNd& x) const; + + or + + double calcDerivative( const SimTK::Array_& derivComponents, + const RigidBodyDynamics::Math::VectorNd& x) const; + + Since this class is implementing strictly scalar functions you can use + the simplified versions of calcValue(double x) and + calcDerivative(double x, int order) instead. + + */ + int getArgumentSize() const; /*virtual*/ + + /**@return The maximum order derivative that this object is capable of + returning*/ + /*virtual*/ + int getMaxDerivativeOrder() const; + + + }; + +} +} +} + + + +#endif diff --git a/3rdparty/rbdl/addons/geometry/geometry.h b/3rdparty/rbdl/addons/geometry/geometry.h new file mode 100644 index 0000000..702c2fb --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/geometry.h @@ -0,0 +1,16 @@ +/* + * + * Copyright (c) 2016 Matthew Millard + * + * Licensed under the zlib license. See LICENSE for more details. + */ + + +#ifndef GEOMETRY_H_ +#define GEOMETRY_H_ + +#include "Function.h" +#include "SegmentedQuinticBezierToolkit.h" +#include "SmoothSegmentedFunction.h" + +#endif diff --git a/3rdparty/rbdl/addons/geometry/tests/CMakeLists.txt b/3rdparty/rbdl/addons/geometry/tests/CMakeLists.txt new file mode 100644 index 0000000..45931fa --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/tests/CMakeLists.txt @@ -0,0 +1,67 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 2.6) + +CMAKE_POLICY(SET CMP0048 NEW) +CMAKE_POLICY(SET CMP0040 NEW) + +SET ( RBDL_ADDON_GEOMETRY_TESTS_VERSION_MAJOR 1 ) +SET ( RBDL_ADDON_GEOMETRY_TESTS_VERSION_MINOR 0 ) +SET ( RBDL_ADDON_GEOMETRY_TESTS_VERSION_PATCH 0 ) + +SET ( RBDL_ADDON_GEOMETRY_TESTS_VERSION + ${RBDL_ADDON_GEOMETRY_TESTS_VERSION_MAJOR}.${RBDL_ADDON_GEOMETRY_TESTS_VERSION_MINOR}.${RBDL_ADDON_GEOMETRY_TESTS_VERSION_PATCH} +) + + +PROJECT (RBDL_GEOMETRY_TESTS VERSION ${RBDL_ADDON_GEOMETRY_TESTS_VERSION}) + +# Needed for UnitTest++ +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../CMake ) + +# Look for unittest++ +FIND_PACKAGE (UnitTest++ REQUIRED) +INCLUDE_DIRECTORIES (${UNITTEST++_INCLUDE_DIR}) + +SET ( GEOMETRY_TESTS_SRCS + testSmoothSegmentedFunction.cc + numericalTestFunctions.cc + numericalTestFunctions.h + ../geometry.h + ../SegmentedQuinticBezierToolkit.h + ../SmoothSegmentedFunction.h + ../SegmentedQuinticBezierToolkit.cc + ../SmoothSegmentedFunction.cc + ) + +INCLUDE_DIRECTORIES ( ../ ) + + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX +) + +ADD_EXECUTABLE ( rbdl_geometry_tests ${GEOMETRY_TESTS_SRCS} ) + +SET_TARGET_PROPERTIES ( rbdl_geometry_tests PROPERTIES + LINKER_LANGUAGE CXX + OUTPUT_NAME runGeometryTests + ) + +SET (RBDL_LIBRARY rbdl) +IF (RBDL_BUILD_STATIC) + SET (RBDL_LIBRARY rbdl-static) +ENDIF (RBDL_BUILD_STATIC) + +TARGET_LINK_LIBRARIES ( rbdl_geometry_tests + ${UNITTEST++_LIBRARY} + ${RBDL_LIBRARY} + ) + +OPTION (RUN_AUTOMATIC_TESTS "Perform automatic tests after compilation?" OFF) + +IF (RUN_AUTOMATIC_TESTS) +ADD_CUSTOM_COMMAND (TARGET rbdl_geometry_tests + POST_BUILD + COMMAND ./runGeometryTests + COMMENT "Running automated addon geometry tests..." + ) +ENDIF (RUN_AUTOMATIC_TESTS) \ No newline at end of file diff --git a/3rdparty/rbdl/addons/geometry/tests/numericalTestFunctions.cc b/3rdparty/rbdl/addons/geometry/tests/numericalTestFunctions.cc new file mode 100644 index 0000000..6afe038 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/tests/numericalTestFunctions.cc @@ -0,0 +1,534 @@ +/* -------------------------------------------------------------------------- * + * OpenSim: testSmoothSegmentedFunctionFactory.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ +/* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + This also includes additional curves (the Anderson2007 curves) + which are not presently in OpenSim. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ +/* +Below is a basic bench mark simulation for the SmoothSegmentedFunctionFactory +class, a class that enables the easy generation of C2 continuous curves +that define the various characteristic curves required in a muscle model + */ + +// Author: Matthew Millard + +//============================================================================== +// INCLUDES +//============================================================================== + + +#include "numericalTestFunctions.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace RigidBodyDynamics::Addons::Geometry; +using namespace std; + + +void printMatrixToFile( + const RigidBodyDynamics::Math::VectorNd& col0, + const RigidBodyDynamics::Math::MatrixNd& data, + string& filename) +{ + + ofstream datafile; + datafile.open(filename.c_str()); + + for(int i = 0; i < data.rows(); i++){ + datafile << col0[i] << ","; + for(int j = 0; j < data.cols(); j++){ + if(j y_C3max){ + c3 = 1; + y = y_C3max; + } + + h = pow( ( (EPSILON*y*2.0)/(c3) ) , 1.0/3.0); + + //Now check that h to the left and right are at least similar + //If not, take the smallest one. + xL = x[i]-h/2; + xR = x[i]+h/2; + + fL = mcf.calcDerivative(xL, order-1); + fR = mcf.calcDerivative(xR, order-1); + + //Just for convenience checking ... + dyNUM = (fR-fL)/h; + dy = mcf.calcDerivative(x[i],order); + err = abs(dy-dyNUM); + + /*if(err > tol && abs(dy) > rootEPS && order <= 2){ + err = err/abs(dy); + if(err > tol) + cout << "rel tol exceeded" << endl; + }*/ + + dyV[i] = dyNUM; + + } + + + return dyV; +} + +bool isFunctionContinuous( RigidBodyDynamics::Math::VectorNd& xV, + SmoothSegmentedFunction& yV, + int order, + double minValueSecondDerivative, + double taylorErrorMult) +{ + bool flag_continuous = true; + + double xL = 0; // left shoulder point + double xR = 0; // right shoulder point + double yL = 0; // left shoulder point function value + double yR = 0; // right shoulder point function value + double dydxL = 0; // left shoulder point derivative value + double dydxR = 0; // right shoulder point derivative value + + double xVal = 0; //x value to test + double yVal = 0; //Y(x) value to test + + double yValEL = 0; //Extrapolation to yVal from the left + double yValER = 0; //Extrapolation to yVal from the right + + double errL = 0; + double errR = 0; + + double errLMX = 0; + double errRMX = 0; + + + for(int i =1; i < xV.size()-1; i++){ + xVal = xV[i]; + yVal = yV.calcDerivative(xVal, order); + + xL = 0.5*(xV[i]+xV[i-1]); + xR = 0.5*(xV[i]+xV[i+1]); + + yL = yV.calcDerivative(xL,order); + yR = yV.calcDerivative(xR,order); + + dydxL = yV.calcDerivative(xL,order+1); + dydxR = yV.calcDerivative(xR,order+1); + + + yValEL = yL + dydxL*(xVal-xL); + yValER = yR - dydxR*(xR-xVal); + + errL = abs(yValEL-yVal); + errR = abs(yValER-yVal); + + errLMX = abs(yV.calcDerivative(xL,order+2)*0.5*(xVal-xL)*(xVal-xL)); + errRMX = abs(yV.calcDerivative(xR,order+2)*0.5*(xR-xVal)*(xR-xVal)); + + errLMX*=taylorErrorMult; + errRMX*=taylorErrorMult; + + if(errLMX < minValueSecondDerivative) + errLMX = minValueSecondDerivative; + + if(errRMX < minValueSecondDerivative) + errRMX = minValueSecondDerivative; // to accomodate numerical + //error in errL + + if(errL > errLMX || errR > errRMX){ + flag_continuous = false; + } + } + + return flag_continuous; +} + +bool isVectorMonotonic( RigidBodyDynamics::Math::VectorNd& y, + int multEPS) +{ + double dir = y[y.size()-1]-y[0]; + bool isMonotonic = true; + + if(dir < 0){ + for(int i =1; i y[i-1]+EPSILON*multEPS){ + isMonotonic = false; + //printf("Monotonicity broken at idx %i, since %fe-16 > %fe-16\n", + // i,y(i)*1e16,y(i-1)*1e16); + printf("Monotonicity broken at idx %i, since " + "y(i)-y(i-1) < tol, (%f*EPSILON < EPSILON*%i) \n", + i,((y[i]-y[i-1])/EPSILON), multEPS); + } + } + } + if(dir > 0){ + for(int i =1; i = 0; i=i-1){ + width = abs(x[i]-x[i+1]); + inty[i] = inty[i+1] + width*(0.5)*(y[i]+y[i+1]); + } + } + + + return inty; +} + + +double calcMaximumVectorError(RigidBodyDynamics::Math::VectorNd& a, + RigidBodyDynamics::Math::VectorNd& b) +{ + double error = 0; + double cerror=0; + for(int i = 0; i< a.size(); i++) + { + cerror = abs(a[i]-b[i]); + if(cerror > error){ + error = cerror; + } + } + return error; +} + + + +bool isCurveC2Continuous(SmoothSegmentedFunction& mcf, + RigidBodyDynamics::Math::MatrixNd& mcfSample, + double continuityTol) +{ + //cout << " TEST: C2 Continuity " << endl; + + int multC0 = 5; + int multC1 = 50; + int multC2 = 200; + + RigidBodyDynamics::Math::VectorNd fcnSample = + RigidBodyDynamics::Math::VectorNd::Zero(mcfSample.rows()); + + for(int i=0; i < mcfSample.rows(); i++){ + fcnSample[i] = mcfSample(i,0); + } + + + bool c0 = isFunctionContinuous(fcnSample, mcf, 0, continuityTol, multC0); + bool c1 = isFunctionContinuous(fcnSample, mcf, 1, continuityTol, multC1); + bool c2 = isFunctionContinuous(fcnSample, mcf, 2, continuityTol, multC2); + + + + return (c0 && c1 && c2); + //printf( " passed: C2 continuity established to a multiple\n" + // " of the next Taylor series error term.\n " + // " C0,C1, and C2 multiples: %i,%i and %i\n", + // multC0,multC1,multC2); + //cout << endl; +} + + +bool isCurveMontonic(RigidBodyDynamics::Math::MatrixNd mcfSample) +{ + //cout << " TEST: Monotonicity " << endl; + int multEps = 10; + + RigidBodyDynamics::Math::VectorNd fcnSample = + RigidBodyDynamics::Math::VectorNd::Zero(mcfSample.rows()); + + for(int i=0; i < mcfSample.rows(); i++){ + fcnSample[i] = mcfSample(i,1); + } + + bool monotonic = isVectorMonotonic(fcnSample,10); + return monotonic; + //printf(" passed: curve is monotonic to %i*EPSILON",multEps); + //cout << endl; +} + + +bool areCurveDerivativesCloseToNumericDerivatives( + SmoothSegmentedFunction& mcf, + RigidBodyDynamics::Math::MatrixNd& mcfSample, + double tol) +{ + //cout << " TEST: Derivative correctness " << endl; + int maxDer = 4;//mcf.getMaxDerivativeOrder() - 2; + + RigidBodyDynamics::Math::MatrixNd numSample(mcfSample.rows(),maxDer); + RigidBodyDynamics::Math::MatrixNd relError(mcfSample.rows(),maxDer); + + + RigidBodyDynamics::Math::VectorNd domainX = + RigidBodyDynamics::Math::VectorNd::Zero(mcfSample.rows()); + + for(int j=0; j tol){ + relError(j,i) = relError(j,i)/mcfSample(j,i+2); + } + } + + } + + RigidBodyDynamics::Math::VectorNd errRelMax = + RigidBodyDynamics::Math::VectorNd::Zero(6); + RigidBodyDynamics::Math::VectorNd errAbsMax = + RigidBodyDynamics::Math::VectorNd::Zero(6); + + double absTol = 5*tol; + + bool flagError12=false; + RigidBodyDynamics::Math::VectorNd tolExceeded12V = + RigidBodyDynamics::Math::VectorNd::Zero(mcfSample.rows()); + + int tolExceeded12 = 0; + int tolExceeded34 = 0; + for(int j=0;j tol && mcfSample(i,j+2) > tol){ + if(j <= 1){ + tolExceeded12++; + tolExceeded12V[i]=1; + flagError12=true; + } + if(j>=2) + tolExceeded34++; + } + if(mcfSample(i,j+2) > tol) + if(errRelMax[j] < abs(relError(i,j))) + errRelMax[j] = abs(relError(i,j)); + + //This is a harder test: here we're comparing absolute error + //so the tolerance margin is a little higher + if(relError(i,j) > absTol && mcfSample(i,j+2) <= tol){ + if(j <= 1){ + tolExceeded12++; + tolExceeded12V[i]=1; + flagError12=true; + } + if(j>=2) + tolExceeded34++; + } + + if(mcfSample(i,j+2) < tol) + if(errAbsMax[j] < abs(relError(i,j))) + errAbsMax[j] = abs(relError(i,j)); + + + } + + /* + if(flagError12 == true){ + printf("Derivative %i Rel Error Exceeded:\n",j); + printf("x dx_relErr dx_calcVal dx_sample" + " dx2_relErr dx2_calcVal dx2_sample\n"); + for(int i=0; i +#include +#include +#include +#include +#include +#include + +using namespace RigidBodyDynamics::Addons::Geometry; + +using namespace std; + +static double EPSILON = numeric_limits::epsilon(); +static double SQRTEPSILON = sqrt(EPSILON); +static double TOL = std::numeric_limits::epsilon()*1e4; + +static bool FLAG_PLOT_CURVES = false; +static string FILE_PATH = ""; +static double TOL_DX = 5e-3; +static double TOL_DX_BIG = 1e-2; +static double TOL_BIG = 1e-6; +static double TOL_SMALL = 1e-12; + +/** +This function will print cvs file of the column vector col0 and the matrix + data + +@params col0: A vector that must have the same number of rows as the data matrix + This column vector is printed as the first column +@params data: A matrix of data +@params filename: The name of the file to print +*/ +void printMatrixToFile( + const RigidBodyDynamics::Math::VectorNd& col0, + const RigidBodyDynamics::Math::MatrixNd& data, + string& filename); + + +/** +This function will print cvs file of the matrix + data + +@params data: A matrix of data +@params filename: The name of the file to print +*/ +void printMatrixToFile( + const RigidBodyDynamics::Math::MatrixNd& data, + string& filename); + + +/** + This function computes a standard central difference dy/dx. If + extrap_endpoints is set to 1, then the derivative at the end points is + estimated by linearly extrapolating the dy/dx values beside the end points + + @param x domain vector + @param y range vector + @param extrap_endpoints: (false) Endpoints of the returned vector will be + zero, because a central difference + is undefined at these endpoints + (true) Endpoints are computed by linearly + extrapolating using a first difference from + the neighboring 2 points + @returns dy/dx computed using central differences +*/ +RigidBodyDynamics::Math::VectorNd + calcCentralDifference( RigidBodyDynamics::Math::VectorNd& x, + RigidBodyDynamics::Math::VectorNd& y, + bool extrap_endpoints); + +/** + This function computes a standard central difference dy/dx at each point in + a vector x, for a SmoothSegmentedFunction mcf, to a desired tolerance. This + function will take the best step size at each point to minimize the + error caused by taking a numerical derivative, and the error caused by + numerical rounding error: + + For a step size of h/2 to the left and to the right of the point of + interest the error is + error = 1/4*h^2*c3 + r*f(x)/h, (1) + + Where c3 is the coefficient of the 3rd order Taylor series expansion + about point x. Thus c3 can be computed if the order + 2 derivative is + known + + c3 = (d^3f(x)/dx^3)/(6) (2) + + And r*f(x)/h is the rounding error that occurs due to the central + difference. + + Taking a first derivative of 1 and solving for h yields + + h = (r*f(x)*2/c3)^(1/3) + + Where r is EPSILON + + @param x domain vector + @param mcf the SmoothSegmentedFunction of interest + @param order the order of the numerical derivative + @param tolerance desired tolerance on the returned result + @returns dy/dx computed using central differences +*/ +RigidBodyDynamics::Math::VectorNd +calcCentralDifference( RigidBodyDynamics::Math::VectorNd& x, + SmoothSegmentedFunction& mcf, + double tol, int order); + +/** + This function tests numerically for continuity of a curve. The test is + performed by taking a point on the curve, and then two points (called the + shoulder points) to the left and right of the point in question. The + shoulder points are located half way between the sample points in xV. + + The value of the function's derivative is evaluated at each of the shoulder + points and used to linearly extrapolate from the shoulder points back to the + original point. If the original point and the linear extrapolations of each + of the shoulder points agree within a tolerance, then the curve is assumed + to be continuous. This tolerance is evaluated to be the smaller of the 2nd + derivative times a multiplier (taylorErrorMult) or minValueSecondDerivative + + + @param x Values to test for continuity + + @param yx The SmoothSegmentedFunction to test + + @param order The order of the curve of SmoothSegmentedFunction to test + for continuity + + @param minValueSecondDerivative the minimum value allowed for the 2nd + term in the Taylor series. Here we need to define a minimum because + this 2nd term is used to define a point specific tolerance for the + continuity test. If the 2nd derivative happens to go to zero, we + still cannot let the minimum error tolerance to go to zero - else + the test will fail on otherwise good curves. + + @param taylorErrorMult This scales the error tolerance. The default error + tolerance is the the 2nd order Taylor series term. This term is + dependent on the size of the higher-order-terms and the sample + spacing used for xV (since the shoulder points are picked half- + way between the sampled points) +*/ +bool isFunctionContinuous( RigidBodyDynamics::Math::VectorNd& xV, + SmoothSegmentedFunction& yV, + int order, + double minValueSecondDerivative, + double taylorErrorMult); + + +/** +This function will scan through a vector and determine if it is monotonic or +not + +@param y the vector of interest +@param multEPS The tolerance on the monotoncity check, expressed as a scaling of + EPSILON +@return true if the vector is monotonic, false if it is not +*/ +bool isVectorMonotonic( RigidBodyDynamics::Math::VectorNd& y, + int multEPS); + + +/** +This function will compute the numerical integral of y(x) using the trapezoidal +method + +@param x the domain vector +@param y the range vector, of y(x), evaluated at x +@param flag_TrueIntForward_FalseIntBackward + When this flag is set to true, the integral of y(x) will be evaluated from + left to right, starting with int(y(0)) = 0. When this flag is false, then + y(x) will be evaluated from right to left with int(y(n)) = 0, where n is + the maximum number of elements. +@return the integral of y(x) +*/ +RigidBodyDynamics::Math::VectorNd calcTrapzIntegral( + RigidBodyDynamics::Math::VectorNd& x, + RigidBodyDynamics::Math::VectorNd& y, + bool flag_TrueIntForward_FalseIntBackward); + + +/** + @param a The first vector + @param b The second vector + @return Returns the maximum absolute difference between vectors a and b +*/ +double calcMaximumVectorError(RigidBodyDynamics::Math::VectorNd& a, + RigidBodyDynamics::Math::VectorNd& b); + + +/* +This function tests the SmoothSegmentedFunction to see if it is C2 continuous. +This function works by using the applying the function isFunctionContinuous +multiple times. For details of the method used please refer to the doxygen for +isFunctionContinuous. + +@param mcf - a SmoothSegmentedFunction +@param mcfSample: + A n-by-m matrix of values where the first column is the domain (x) of interest + and the remaining columns are the curve value (y), and its derivatives (dy/dx, + d2y/dx2, d3y/dx3, etc). This matrix is returned by the function + calcSampledCurve in SmoothSegmented Function. +@param continuityTol +@return bool: true if the curve is C2 continuous to the desired tolernace + +*/ +bool isCurveC2Continuous(SmoothSegmentedFunction& mcf, + RigidBodyDynamics::Math::MatrixNd& mcfSample, + double continuityTol); + +/* + 4. The MuscleCurveFunctions which are supposed to be monotonic will be + tested for monotonicity. +*/ +bool isCurveMontonic(RigidBodyDynamics::Math::MatrixNd mcfSample); + +/** +This function compares the i^th derivative return by a SmoothSegmented function +against a numerical derivative computed using a central difference applied to +the (i-1)^th derivative of the function. This function first checks the +1st derivative and continues checking derivatives until the 4th derivative. + +@param mcf - a SmoothSegmentedFunction +@param mcfSample: + A n-by-m matrix of values where the first column is the domain (x) of interest + and the remaining columns are the curve value (y), and its derivatives (dy/dx, + d2y/dx2, d3y/dx3, etc). This matrix is returned by the function + calcSampledCurve in SmoothSegmented Function. + +@param tol : the tolerance used to assess the relative error between the + numerically computed derivatives and the derivatives returned by + the SmoothSegmentedFunction +@return bool: true if all of the derivatives up to the 4th (hard coded) are + within a relative tolerance of tol w.r.t. to the numerically + computed derivatives +*/ +bool areCurveDerivativesCloseToNumericDerivatives( + SmoothSegmentedFunction& mcf, + RigidBodyDynamics::Math::MatrixNd& mcfSample, + double tol); diff --git a/3rdparty/rbdl/addons/geometry/tests/testSmoothSegmentedFunction.cc b/3rdparty/rbdl/addons/geometry/tests/testSmoothSegmentedFunction.cc new file mode 100644 index 0000000..e9596e4 --- /dev/null +++ b/3rdparty/rbdl/addons/geometry/tests/testSmoothSegmentedFunction.cc @@ -0,0 +1,483 @@ +/* -------------------------------------------------------------------------- * + * OpenSim: testSmoothSegmentedFunctionFactory.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ +/* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + This also includes additional curves (the Anderson2007 curves) + which are not presently in OpenSim. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ +/* +Below is a basic bench mark simulation for the SmoothSegmentedFunctionFactory +class, a class that enables the easy generation of C2 continuous curves +that define the various characteristic curves required in a muscle model + */ + +// Author: Matthew Millard + +//============================================================================== +// INCLUDES +//============================================================================== + +#include "geometry.h" +#include "numericalTestFunctions.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace RigidBodyDynamics::Addons::Geometry; + +using namespace std; + + + + +/** + This function will create a quintic Bezier curve y(x) and sample it, its + first derivative w.r.t. U (dx(u)/du and dy(u)/du), and its first derivative + w.r.t. to X and print it to the screen. +*/ +void testSegmentedQuinticBezierDerivatives( + int maximumNumberOfToleranceViolations) +{ + //cout <<"**************************************************"<tol) + //printf("Error > Tol: (%i,%i): %f > %f\n",j,i,errorDer(j,i),tol); + } + } + //errorDer.cwiseAbs(); + //cout << errorDer << endl; + + //printf("...absolute tolerance of %f met\n", tol); + + //cout << " TEST: Bezier Curve Derivative DYDX to d6y/dx6" << endl; + RigidBodyDynamics::Math::MatrixNd numericDerXY(analyticDerXU.rows(), 6); + RigidBodyDynamics::Math::MatrixNd analyticDerXY(analyticDerXU.rows(),6); + + for(int i=0; i< analyticDerXU.rows(); i++) + { + analyticDerXY(i,0) = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(uV[i],xPts,yPts,1); + analyticDerXY(i,1) = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(uV[i],xPts,yPts,2); + analyticDerXY(i,2) = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(uV[i],xPts,yPts,3); + analyticDerXY(i,3) = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(uV[i],xPts,yPts,4); + analyticDerXY(i,4) = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(uV[i],xPts,yPts,5); + analyticDerXY(i,5) = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCurveDerivDYDX(uV[i],xPts,yPts,6); + } + + + for(int j=0; jrelTol){ + //printf("Error > Tol: (%i,%i): %f > %f\n",i,j, + // errorDerXY(i,j),relTol); + relTolExceeded++; + } + } + } + //cout << relTolExceeded << endl; + + //The relative tolerance gets exceeded occasionally in locations of + //rapid change in the curve. Provided there are only a few locations + //where the relative tolerance of 5% is broken, the curves should be + //regarded as being good. Ten errors out of a possible 100*6 data points + //seems relatively small. + CHECK(relTolExceeded < maximumNumberOfToleranceViolations); + + + //std::string fname = "analyticDerXY.csv"; + //printMatrixToFile(analyticDerXY,fname); + //fname = "numericDerXY.csv"; + //printMatrixToFile(numericDerXY,fname); + //fname = "errorDerXY.csv"; + //printMatrixToFile(errorDerXY,fname); + //printf(" ...relative tolerance of %f not exceeded more than %i times\n" + // " across all 6 derivatives, with 100 samples each\n", + // relTol, 10); + //cout <<"**************************************************"< + +DESCRIPTION + +rbdl_luamodel allows to load models that are specified as Lua scripts. Lua +is an open-source light-weight scripting language (http://www.lua.org). + +This addon comes with a standalone utility that can show various +information of a lua model such as degree of freedom overview or model +hierarchy. It is located in addons/luamodel/rbdl_luamodel_test. Use the -h +switch to see available options. + +Note: this addon is not even remotely as thoroughly tested as the RBDL +itself so please use it with some suspicion. + +DOCUMENTATION + +The documentation for this addon is built with the RBDL documentation. You +can find it in Modules -> Addon: rbdl_luamodel. + +LICENSING + +This code is published under the zlib license, however some parts of the +CMake scripts are taken from other projects and are licensed under +different terms. + +Full license text: + +------- +rbdl_luamodel - load RBDL models from Lua files +Copyright (c) 2011-2012 Martin Felis + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/3rdparty/rbdl/addons/luamodel/luamodel.cc b/3rdparty/rbdl/addons/luamodel/luamodel.cc new file mode 100644 index 0000000..cc82b5c --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/luamodel.cc @@ -0,0 +1,508 @@ +#include "rbdl/rbdl.h" +#include "luamodel.h" + +#include +#include + +#include "luatables.h" + +extern "C" +{ +#include +#include +#include +} + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +template<> +Vector3d LuaTableNode::getDefault(const Vector3d &default_value) { + Vector3d result = default_value; + + if (stackQueryValue()) { + LuaTable vector_table = LuaTable::fromLuaState (luaTable->L); + + if (vector_table.length() != 3) { + cerr << "LuaModel Error: invalid 3d vector!" << endl; + abort(); + } + + result[0] = vector_table[1]; + result[1] = vector_table[2]; + result[2] = vector_table[3]; + } + + stackRestore(); + + return result; +} + +template<> +SpatialVector LuaTableNode::getDefault( + const SpatialVector &default_value +) { + SpatialVector result = default_value; + + if (stackQueryValue()) { + LuaTable vector_table = LuaTable::fromLuaState (luaTable->L); + + if (vector_table.length() != 6) { + cerr << "LuaModel Error: invalid 6d vector!" << endl; + abort(); + } + result[0] = vector_table[1]; + result[1] = vector_table[2]; + result[2] = vector_table[3]; + result[3] = vector_table[4]; + result[4] = vector_table[5]; + result[5] = vector_table[6]; + } + + stackRestore(); + + return result; +} + +template<> +Matrix3d LuaTableNode::getDefault(const Matrix3d &default_value) { + Matrix3d result = default_value; + + if (stackQueryValue()) { + LuaTable vector_table = LuaTable::fromLuaState (luaTable->L); + + if (vector_table.length() != 3) { + cerr << "LuaModel Error: invalid 3d matrix!" << endl; + abort(); + } + + if (vector_table[1].length() != 3 + || vector_table[2].length() != 3 + || vector_table[3].length() != 3) { + cerr << "LuaModel Error: invalid 3d matrix!" << endl; + abort(); + } + + result(0,0) = vector_table[1][1]; + result(0,1) = vector_table[1][2]; + result(0,2) = vector_table[1][3]; + + result(1,0) = vector_table[2][1]; + result(1,1) = vector_table[2][2]; + result(1,2) = vector_table[2][3]; + + result(2,0) = vector_table[3][1]; + result(2,1) = vector_table[3][2]; + result(2,2) = vector_table[3][3]; + } + + stackRestore(); + + return result; +} + +template<> +SpatialTransform LuaTableNode::getDefault( + const SpatialTransform &default_value +) { + SpatialTransform result = default_value; + + if (stackQueryValue()) { + LuaTable vector_table = LuaTable::fromLuaState (luaTable->L); + + result.r = vector_table["r"].getDefault(Vector3d::Zero(3)); + result.E = vector_table["E"].getDefault(Matrix3d::Identity (3,3)); + } + + stackRestore(); + + return result; +} + +template<> +Joint LuaTableNode::getDefault(const Joint &default_value) { + Joint result = default_value; + + if (stackQueryValue()) { + LuaTable vector_table = LuaTable::fromLuaState (luaTable->L); + + int joint_dofs = vector_table.length(); + + if (joint_dofs == 1) { + string dof_string = vector_table[1].getDefault(""); + if (dof_string == "JointTypeSpherical") { + stackRestore(); + return Joint(JointTypeSpherical); + } else if (dof_string == "JointTypeEulerZYX") { + stackRestore(); + return Joint(JointTypeEulerZYX); + } + if (dof_string == "JointTypeEulerXYZ") { + stackRestore(); + return Joint(JointTypeEulerXYZ); + } + if (dof_string == "JointTypeEulerYXZ") { + stackRestore(); + return Joint(JointTypeEulerYXZ); + } + if (dof_string == "JointTypeTranslationXYZ") { + stackRestore(); + return Joint(JointTypeTranslationXYZ); + } + } + + if (joint_dofs > 0) { + if (vector_table[1].length() != 6) { + cerr << "LuaModel Error: invalid joint motion subspace description at " + << this->keyStackToString() << endl; + abort(); + } + } + + switch (joint_dofs) { + case 0: + result = Joint(JointTypeFixed); + break; + case 1: + result = Joint (vector_table[1].get()); + break; + case 2: + result = Joint( + vector_table[1].get(), + vector_table[2].get() + ); + break; + case 3: + result = Joint( + vector_table[1].get(), + vector_table[2].get(), + vector_table[3].get() + ); + break; + case 4: + result = Joint( + vector_table[1].get(), + vector_table[2].get(), + vector_table[3].get(), + vector_table[4].get() + ); + break; + case 5: + result = Joint( + vector_table[1].get(), + vector_table[2].get(), + vector_table[3].get(), + vector_table[4].get(), + vector_table[5].get() + ); + break; + case 6: + result = Joint( + vector_table[1].get(), + vector_table[2].get(), + vector_table[3].get(), + vector_table[4].get(), + vector_table[5].get(), + vector_table[6].get() + ); + break; + default: + cerr << "Invalid number of DOFs for joint." << endl; + abort(); + break; + } + } + + stackRestore(); + + return result; +} + +template<> +Body LuaTableNode::getDefault(const Body &default_value) { + Body result = default_value; + + if (stackQueryValue()) { + LuaTable vector_table = LuaTable::fromLuaState (luaTable->L); + + double mass = 0.; + Vector3d com (Vector3d::Zero(3)); + Matrix3d inertia (Matrix3d::Identity(3,3)); + + mass = vector_table["mass"]; + com = vector_table["com"].getDefault(com); + inertia = vector_table["inertia"].getDefault(inertia); + + result = Body (mass, com, inertia); + } + + stackRestore(); + + return result; +} + +namespace RigidBodyDynamics { + +namespace Addons { + +bool LuaModelReadFromTable (LuaTable &model_table, Model *model, bool verbose); +bool LuaModelReadConstraintsFromTable ( + LuaTable &model_table, + Model *model, + std::vector& constraint_sets, + const std::vector& constraint_set_names, + bool verbose +); + +typedef map StringIntMap; +StringIntMap body_table_id_map; + +RBDL_DLLAPI +bool LuaModelReadFromLuaState (lua_State* L, Model* model, bool verbose) { + assert (model); + + LuaTable model_table = LuaTable::fromLuaState (L); + + return LuaModelReadFromTable (model_table, model, verbose); +} + +RBDL_DLLAPI +bool LuaModelReadFromFile (const char* filename, Model* model, bool verbose) { + if(!model) { + std::cerr << "Model not provided." << std::endl; + assert(false); + abort(); + } + + LuaTable model_table = LuaTable::fromFile (filename); + + return LuaModelReadFromTable (model_table, model, verbose); +} + + +RBDL_DLLAPI +bool LuaModelReadFromFileWithConstraints ( + const char* filename, + Model* model, + std::vector& constraint_sets, + const std::vector& constraint_set_names, + bool verbose +) { + if(!model) { + std::cerr << "Model not provided." << std::endl; + assert(false); + abort(); + } + if(constraint_sets.size() != constraint_set_names.size()) { + std::cerr << "Number of constraint sets different from the number of \ + constraint set names." << std::endl; + assert(false); + abort(); + } + + LuaTable model_table = LuaTable::fromFile (filename); + bool modelLoaded = LuaModelReadFromTable (model_table, model, verbose); + bool constraintsLoaded = LuaModelReadConstraintsFromTable (model_table, model + , constraint_sets, constraint_set_names, verbose); + for(size_t i = 0; i < constraint_sets.size(); ++i) { + constraint_sets[i].Bind(*model); + } + + return modelLoaded && constraintsLoaded; +} + + +bool LuaModelReadFromTable (LuaTable &model_table, Model* model, bool verbose) { + if (model_table["gravity"].exists()) { + model->gravity = model_table["gravity"].get(); + + if (verbose) + cout << "gravity = " << model->gravity.transpose() << endl; + } + + int frame_count = model_table["frames"].length(); + + body_table_id_map["ROOT"] = 0; + + for (int i = 1; i <= frame_count; i++) { + if (!model_table["frames"][i]["parent"].exists()) { + cerr << "Parent not defined for frame " << i << "." << endl; + abort(); + } + + string body_name = model_table["frames"][i]["name"].getDefault(""); + string parent_name = model_table["frames"][i]["parent"].get(); + unsigned int parent_id = body_table_id_map[parent_name]; + + SpatialTransform joint_frame + = model_table["frames"][i]["joint_frame"].getDefault(SpatialTransform()); + Joint joint + = model_table["frames"][i]["joint"].getDefault(Joint(JointTypeFixed)); + Body body = model_table["frames"][i]["body"].getDefault(Body()); + + unsigned int body_id + = model->AddBody (parent_id, joint_frame, joint, body, body_name); + body_table_id_map[body_name] = body_id; + + if (verbose) { + cout << "==== Added Body ====" << endl; + cout << " body_name : " << body_name << endl; + cout << " body id : " << body_id << endl; + cout << " parent_id : " << parent_id << endl; + cout << " joint dofs : " << joint.mDoFCount << endl; + for (unsigned int j = 0; j < joint.mDoFCount; j++) { + cout << " " << j << ": " << joint.mJointAxes[j].transpose() << endl; + } + cout << " joint_frame: " << joint_frame << endl; + } + } + + return true; +} + +bool LuaModelReadConstraintsFromTable ( + LuaTable &model_table, + Model *model, + std::vector& constraint_sets, + const std::vector& constraint_set_names, + bool verbose +) { + for(size_t i = 0; i < constraint_set_names.size(); ++i) { + + if(!model_table["constraint_sets"][constraint_set_names[i].c_str()] + .exists()) { + cerr << "Constraint set not existing: " << constraint_set_names[i] << "." + << endl; + assert(false); + abort(); + } + + size_t nConstraints = model_table["constraint_sets"] + [constraint_set_names[i].c_str()] + .length(); + + for(size_t ci = 0; ci < nConstraints; ++ci) { + if(!model_table["constraint_sets"] + [constraint_set_names[i].c_str()][ci + 1]["constraint_type"].exists()) { + cerr << "constraint_type not specified." << endl; + assert(false); + abort(); + } + string constraintType = model_table["constraint_sets"] + [constraint_set_names[i].c_str()][ci + 1]["constraint_type"] + .getDefault(""); + if(constraintType == "contact") { + if(!model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["body"].exists()) { + cerr << "body not specified." << endl; + assert(false); + abort(); + } + constraint_sets[i].AddContactConstraint + (model->GetBodyId(model_table["constraint_sets"] + [constraint_set_names[i].c_str()][ci + 1]["body"] + .getDefault("").c_str()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["point"].getDefault(Vector3d::Zero()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["normal"].getDefault(Vector3d::Zero()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["name"].getDefault("").c_str() + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["normal_acceleration"].getDefault(0.)); + if(verbose) { + cout << "==== Added Constraint from '" << constraint_set_names[i] + << "' ====" << endl; + cout << " type = contact" << endl; + cout << " body = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["body"].getDefault("") << endl; + cout << " body point = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["point"].getDefault(Vector3d::Zero()).transpose() + << endl; + cout << " world normal = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["normal"].getDefault(Vector3d::Zero()).transpose() + << endl; + cout << " constraint name = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["name"].getDefault("") << endl; + cout << " normal acceleration = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["normal_acceleration"].getDefault(0.) << endl; + } + } + else if(constraintType == "loop") { + if(!model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["predecessor_body"].exists()) { + cerr << "predecessor_body not specified." << endl; + assert(false); + abort(); + } + if(!model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["successor_body"].exists()) { + cerr << "successor_body not specified." << endl; + assert(false); + abort(); + } + constraint_sets[i].AddLoopConstraint(model->GetBodyId + (model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["predecessor_body"].getDefault("").c_str()) + , model->GetBodyId(model_table["constraint_sets"] + [constraint_set_names[i].c_str()][ci + 1]["successor_body"] + .getDefault("").c_str()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["predecessor_transform"].getDefault(SpatialTransform()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["successor_transform"].getDefault(SpatialTransform()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["axis"].getDefault(SpatialVector::Zero()) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["stabilization_coefficient"].getDefault(1.) + , model_table["constraint_sets"][constraint_set_names[i].c_str()][ci + 1] + ["name"].getDefault("").c_str()); + if(verbose) { + cout << "==== Added Constraint from '" << constraint_set_names[i] + << "' ====" << endl; + cout << " type = loop" << endl; + cout << " predecessor body = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["predecessor_body"].getDefault("") << endl; + cout << " successor body = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["successor_body"].getDefault("") << endl; + cout << " predecessor body transform = " << endl + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["predecessor_transform"] + .getDefault(SpatialTransform()) << endl; + cout << " successor body transform = " << endl + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["successor_transform"] + .getDefault(SpatialTransform()) << endl; + cout << " constraint axis (in predecessor frame) = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["axis"].getDefault(SpatialVector::Zero()) + .transpose() << endl; + cout << " stabilization coefficient = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["stabilization_coefficient"].getDefault(1.) << endl; + cout << " constraint name = " + << model_table["constraint_sets"][constraint_set_names[i].c_str()] + [ci + 1]["name"].getDefault("").c_str() << endl; + } + } + else { + cerr << "Invalid constraint type: " << constraintType << endl; + abort(); + } + } + } + + return true; +} + +} + +} diff --git a/3rdparty/rbdl/addons/luamodel/luamodel.h b/3rdparty/rbdl/addons/luamodel/luamodel.h new file mode 100644 index 0000000..776a4dd --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/luamodel.h @@ -0,0 +1,309 @@ +#ifndef _RBDL_LUAMODEL_H +#define _RBDL_LUAMODEL_H + +#include +#include +#include + +extern "C" { + struct lua_State; +}; + +namespace RigidBodyDynamics { + +struct Model; +struct ConstraintSet; + +namespace Addons { + +/** \page addon_luamodel_page Addon: rbdl_luamodel +* @{ +* +* \section luamodel_introduction Lua Models +* +* The Addon LuaModel allows to load \link RigidBodyDynamics::Model Models\endlink +* that are specified as Lua scripts. Lua is +* an open-source light-weight scripting language (http://www.lua.org). +* This addon is not enabled by default and one has to enable it by +* setting BUILD_ADDON_LUAMODEL to true in when configuring the RBDL with +* CMake. +* +* This addon comes with a standalone utility that can show various +* information of a lua model such as degree of freedom overview or model +* hierarchy. It is located in addons/luamodel/rbdl_luamodel_test. Use the -h +* switch to see available options. +* +* Note: this addon is not even remotely as thoroughly tested as the RBDL +* itself so please use it with some suspicion. +* +* \section luamodel_format Format Overview +* +* Models have to be specified as a specially formatted Lua table which must +* be returned by the script, i.e. if the model is specified in the table +* "model = { ... }" the script has to return this when executed. Within the +* returned table, rbdl_luamodel goes through the table "frames" and builds +* the model from the individual Frame Information Tables (see further down +* for more information about those). +* +* A valid file could look like this: +* +* \code +* model = { +* frames = { +* { +* +* }, +* { +* +* } +* } +* } +* +* return model +* \endcode +* +* Apart from the frames you can also specify gravity in the model file. +* +* Example: +* \code +* model = { +* gravity = {0., 0., -9.81} +* +* frames = { +* ... +* } +* } +* \endcode +* +* Finally, several constraint sets can be defined for the model. +* +* Example: +* \code +* model = { +* constraint_sets = { +* constraint_set_1_name = { +* { +* ... +* }, +* { +* ... +* }, +* }, +* constraint_set_2_name = { +* ... +* }, +* }, +* } +* \endcode +* +* \note The table frames must contain all Frame Information Tables as a list +* and individual tables must *not* be specified with a key, i.e. +* \code +* frames = { +* some_frame = { +* ... +* }, +* { +* .. +* } +* } +* \endcode +* is not possible as Lua does not retain the order of the individual +* frames when an explicit key is specified. +* +* \section luamodel_frame_table Frame Information Table +* +* The Frame Information Table is searched for values needed to call +* Model::AddBody(). The following fields are used by rbdl_luamodel +* (everything else is ignored): +* +* \par name (required, type: string): +* Name of the body that is being added. This name must be unique. +* +* \par parent (required, type: string): +* If the value is "ROOT" the parent frame of this body is assumed to be +* the base coordinate system, otherwise it must be the exact same string +* as was used for the "name"-field of the parent frame. +* +* \par body (optional, type: table) +* Specification of the dynamical parameters of the body. It uses the +* values (if existing): +* \code +* mass (scalar value, default 1.), +* com (3-d vector, default: (0., 0., 0.)) +* inertia (3x3 matrix, default: identity matrix) +* \endcode +* \par +* to create a \ref RigidBodyDynamics::Body. +* +* \par joint (optional, type: table) +* Specifies the type of joint, fixed or up to 6 degrees of freedom. Each +* entry in the joint table should be a 6-d that defines the motion +* subspace of a single degree of freedom. +* \par +* Default joint type is a fixed joint that attaches the body rigidly to +* its parent. +* \par +* 3-DoF joints are described by using strings. The following 3-DoF +* joints are available: +* +* - "JointTypeSpherical": for singularity-free spherical joints +* - "JointTypeEulerZYX": Euler ZYX joint +* - "JointTypeEulerXYZ": Euler XYZ joint +* - "JointTypeEulerYXZ": Euler YXZ joint +* - "JointTypeTranslationXYZ": Free translational joint +* +* \par +* Examples: +* \code +* joint_fixed = {} +* joint_translate_x = { {0., 0., 0., 1., 0., 0.} } +* joint_translate_xy = { +* {0., 0., 0., 1., 0., 0.}, +* {0., 0., 0., 0., 1., 0.} +* } +* joint_rotate_zyx = { +* {0., 0., 1., 0., 0., 0.}, +* {0., 1., 0., 0., 0., 0.}, +* {1., 0., 0., 0., 0., 0.}, +* } +* joint_rotate_euler_zyx = { "JointTypeEulerZYX" } +* \endcode +* +* \par joint_frame (optional, type: table) +* Specifies the origin of the joint in the frame of the parent. It uses +* the values (if existing): +* \code +* r (3-d vector, default: (0., 0., 0.)) +* E (3x3 matrix, default: identity matrix) +* \endcode +* \par +* for which r is the translation and E the rotation of the joint frame +* +* \section luamodel_constraint_table Constraint Information Table +* The Constraint Information Table is searched for values needed to call +* ConstraintSet::AddContactConstraint() or ConstraintSet::AddLoopConstraint(). +* The employed fields are defined as follows.. Please note that different fields +* may be used for different constraint types. +* +* \par constraint_type (required, type: string) +* Specifies the type of constraints, either 'contact' or 'loop', other +* values will cause a failure while reading the file. +* +* \par name (optional, type: string) +* Specifies a name for the constraint. +* +* The following fields are used exclusively for contact constraints: +* +* \par body (required, type: string) +* The name of the body involved in the constraint. +* +* \par point(optional, type: table) +* The position of the contact point in body coordinates. Defaults to +* (0, 0, 0). +* +* \par normal(optional, type: table) +* The normal along which the constraint acts in world coordinates. +* Defaults to (0, 0, 0). +* +* \par normal_acceleration(optional, type: number) +* The normal acceleration along the constraint axis. Defaults to 0. +* +* The following fields are used exclusively for loop constraints: +* +* \par predecessor_body (required, type: string) +* The name of the predecessor body involved in the constraint. +* +* \par successor_body (required, type: string) +* The name of the successor body involved in the constraint. +* +* \par predecessor_transform (optional, type: table) +* The transform of the predecessor constrained frame with respect to +* the predecessor body frame. Defaults to the identity transform. +* +* \par sucessor_transform (optional, type: table) +* The transform of the sucessor constrained frame with respect to +* the sucessor body frame. Defaults to the identity transform. +* +* \par axis (optional, type: table) +* The 6d spatial axis along which the constraint acts, in coordinates +* relative to the predecessor constrained frame. Defaults to (0,0,0,0,0,0). +* +* \par stabilization_coefficient(optional, type: number) +* The stabilization coefficient for Baumgarte stabilization. Defaults to 1. +* +* \section luamodel_example Example +* Here is an example of a model: +* \include samplemodel.lua +* +* \section luamodel_example_constraints Example with constraints +* Here is an example of a model including constraints: +* \include sampleconstrainedmodel.lua +* +*/ + +/** \brief Reads a model from a Lua file. + * + * \param filename the name of the Lua file. + * \param model a pointer to the output Model structure. + * \param verbose specifies wether information on the model should be printed + * (default: true). + * + * \returns true if the model was read successfully. + * + * \note See \ref luamodel_introduction for information on how to define the + * Lua model. + */ +RBDL_DLLAPI +bool LuaModelReadFromFile ( + const char* filename, + Model* model, + bool verbose = false); + +/** \brief Reads a model and constraint sets from a Lua file. + * + * \param filename the name of the Lua file. + * \param model a pointer to the output Model structure. + * \param constraint_sets reference to a std::vector of ConstraintSet structures + * in which to save the information read from the file. + * \param constraint_set_names reference to a std::vector of std::string + * specifying the names of the constraint sets to be read from the Lua file. + * \param verbose specifies wether information on the model should be printed + * (default: true). + * + * \returns true if the model and constraint sets were read successfully. + * + * \note constraint_sets and constraint_set_names are required to have the same + * size. See \ref luamodel_introduction for information on how to define the + * Lua model. + */ +RBDL_DLLAPI +bool LuaModelReadFromFileWithConstraints ( + const char* filename, + Model* model, + std::vector& constraint_sets, + const std::vector& constraint_set_names, + bool verbose = false); + +/** \brief Reads a model from a lua_State. + * + * \param L a pointer to the lua_State. + * \param model a pointer to the output Model structure. + * \param verbose specifies wether information on the model should be printed + * (default: true). + * + * \returns true if the model was read successfully. + */ +RBDL_DLLAPI +bool LuaModelReadFromLuaState ( + lua_State* L, + Model* model, + bool verbose = false); + +/** @} */ +} + +} + +/* _RBDL_LUAMODEL_H */ +#endif diff --git a/3rdparty/rbdl/addons/luamodel/luatables.cc b/3rdparty/rbdl/addons/luamodel/luatables.cc new file mode 100644 index 0000000..a829a5f --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/luatables.cc @@ -0,0 +1,932 @@ +/* + * LuaTables++ + * Copyright (c) 2013-2014 Martin Felis . + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "luatables.h" + +#include +#include +#include +#include +#include +#include + +extern "C" +{ + #include + #include + #include +} + +#include /* defines FILENAME_MAX */ + +#if defined(WIN32) || defined (_WIN32) + #include + #define get_current_dir _getcwd + #define DIRECTORY_SEPARATOR "\\" +#elif defined(linux) || defined (__linux) || defined(__linux__) || defined(__APPLE__) + #include + #define get_current_dir getcwd + #define DIRECTORY_SEPARATOR "/" +#else + #error Platform not supported! +#endif + +using namespace std; + +std::string get_file_directory (const char* filename) { + string name (filename); + string result = name.substr(0, name.find_last_of (DIRECTORY_SEPARATOR) + 1); + + if (result == "") + result = "./"; +#if defined (WIN32) || defined (_WIN32) + else if (result.substr(1,2) != ":\\") + result = string(".\\") + result; +#else + else if (result.substr(0,string(DIRECTORY_SEPARATOR).size()) != DIRECTORY_SEPARATOR && result[0] != '.') + result = string("./") + result; +#endif + + return result; +} + +// char encoded serialize function that is available in plaintext in +// utils/serialize.lua. Converted using lua auto.lua serialize.lua +const char serialize_lua[] = { + + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x6c, + 0x69, 0x73, 0x74, 0x20, 0x28, 0x74, 0x29, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x3d, + 0x20, 0x30, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, + 0x20, 0x6e, 0x69, 0x6c, 0x0a, + 0x09, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x2c, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x28, + 0x74, 0x29, 0x20, 0x64, 0x6f, 0x0a, + 0x09, 0x09, 0x69, 0x74, 0x65, 0x6d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x69, 0x74, 0x65, 0x6d, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x2b, 0x20, 0x31, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x3d, 0x20, + 0x6e, 0x69, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x79, 0x70, + 0x65, 0x28, 0x76, 0x29, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, 0x7e, 0x3d, 0x20, 0x6c, 0x61, + 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x28, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, + 0x29, 0x20, 0x7e, 0x3d, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, 0x7e, 0x3d, 0x20, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x22, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, 0x7e, 0x3d, 0x20, 0x22, + 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x22, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, + 0x76, 0x29, 0x20, 0x7e, 0x3d, 0x20, 0x22, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, + 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x0a, + 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x28, 0x76, 0x29, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x69, 0x74, 0x65, 0x6d, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x7e, 0x3d, 0x20, 0x23, + 0x74, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6d, 0x70, + 0x5f, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x20, 0x28, 0x61, 0x2c, 0x20, + 0x62, 0x29, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x28, 0x74, 0x79, 0x70, 0x65, 0x28, 0x61, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x62, 0x29, + 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x28, 0x61, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x62, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x22, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x61, 0x20, 0x3c, 0x20, 0x62, 0x0a, + 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x61, 0x29, 0x20, 0x3c, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x62, 0x29, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x67, 0x65, 0x6e, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x20, 0x28, + 0x74, 0x29, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, + 0x69, 0x63, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a, + 0x09, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x20, 0x28, 0x74, + 0x29, 0x20, 0x64, 0x6f, 0x0a, + 0x09, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x28, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x2c, 0x20, 0x6b, 0x29, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x73, 0x6f, 0x72, 0x74, 0x20, 0x28, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x2c, 0x20, 0x63, 0x6d, 0x70, 0x5f, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x29, 0x0a, + 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x6e, + 0x64, 0x69, 0x63, 0x65, 0x73, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x65, 0x64, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x20, 0x28, 0x74, 0x2c, 0x20, 0x73, 0x74, 0x61, 0x74, + 0x65, 0x29, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, 0x3d, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x20, 0x74, + 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x74, 0x2e, 0x5f, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x69, 0x63, + 0x65, 0x73, 0x20, 0x3d, 0x20, 0x67, 0x65, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x69, + 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x20, 0x28, 0x74, 0x29, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x20, 0x74, 0x2e, 0x5f, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x5b, 0x31, 0x5d, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x2c, 0x20, 0x74, 0x5b, 0x6b, 0x65, + 0x79, 0x5d, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, + 0x09, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x20, 0x3d, 0x20, 0x31, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x2e, + 0x67, 0x65, 0x74, 0x6e, 0x28, 0x74, 0x2e, 0x5f, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, + 0x64, 0x69, 0x63, 0x65, 0x73, 0x29, 0x20, 0x64, 0x6f, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x74, 0x2e, 0x5f, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, + 0x64, 0x69, 0x63, 0x65, 0x73, 0x5b, 0x69, 0x5d, 0x20, 0x3d, 0x3d, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x6b, 0x65, 0x79, 0x20, 0x3d, 0x20, 0x74, 0x2e, 0x5f, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, + 0x65, 0x64, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, 0x73, 0x5b, 0x69, 0x20, 0x2b, 0x20, 0x31, 0x5d, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6b, 0x65, 0x79, 0x2c, 0x20, 0x74, 0x5b, 0x6b, 0x65, + 0x79, 0x5d, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x74, 0x2e, 0x5f, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x65, + 0x73, 0x20, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x0a, + 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, + 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x20, 0x28, 0x74, 0x29, 0x0a, + 0x09, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x6e, 0x65, + 0x78, 0x74, 0x2c, 0x20, 0x74, 0x2c, 0x20, 0x6e, 0x69, 0x6c, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, + 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x20, 0x28, 0x6f, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x73, 0x2c, 0x20, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x29, 0x0a, + 0x20, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x22, + 0x22, 0x0a, + 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x20, + 0x3d, 0x20, 0x70, 0x61, 0x69, 0x72, 0x73, 0x0a, + 0x09, 0x69, 0x66, 0x20, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x70, 0x61, 0x69, 0x72, 0x73, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x20, 0x3d, 0x20, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x0a, + 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x3d, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x20, 0x74, + 0x68, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x3d, 0x20, 0x22, 0x22, 0x0a, + 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6f, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x6f, 0x29, 0x0a, + 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6f, 0x29, 0x20, 0x3d, + 0x3d, 0x20, 0x22, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x6f, 0x29, 0x0a, + 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6f, 0x29, 0x20, 0x3d, + 0x3d, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, + 0x2e, 0x2e, 0x20, 0x22, 0x5c, 0x22, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x6f, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x5c, + 0x22, 0x22, 0x0a, + 0x09, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6f, 0x29, 0x20, 0x3d, 0x3d, + 0x20, 0x22, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x73, 0x6c, 0x69, 0x73, + 0x74, 0x28, 0x6f, 0x29, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, + 0x2e, 0x2e, 0x20, 0x22, 0x7b, 0x22, 0x0a, + 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x77, 0x61, 0x73, 0x5f, 0x73, + 0x75, 0x62, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x2c, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x69, 0x70, 0x61, 0x69, 0x72, + 0x73, 0x28, 0x6f, 0x29, 0x20, 0x64, 0x6f, 0x0a, + 0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x77, 0x61, 0x73, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x22, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x77, 0x61, 0x73, 0x5f, 0x73, 0x75, 0x62, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x5c, 0x6e, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, + 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x20, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x20, 0x28, 0x76, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x20, + 0x22, 0x2c, 0x20, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x2c, 0x22, 0x0a, + 0x09, 0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, + 0x3d, 0x3d, 0x20, 0x22, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x5c, 0x22, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x76, 0x20, 0x2e, 0x2e, + 0x20, 0x22, 0x5c, 0x22, 0x2c, 0x22, 0x0a, + 0x09, 0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x20, 0x28, 0x76, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x2c, 0x22, 0x0a, + 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x77, 0x61, 0x73, 0x5f, 0x73, 0x75, 0x62, 0x74, + 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, + 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x5c, 0x6e, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x61, 0x62, 0x73, 0x0a, + 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, + 0x2e, 0x2e, 0x20, 0x22, 0x7d, 0x22, 0x0a, + 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6f, 0x29, 0x20, 0x3d, + 0x3d, 0x20, 0x22, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x6f, 0x2e, 0x64, 0x6f, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x5f, 0x6d, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x22, 0x7b, 0x7d, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x7b, 0x5c, 0x6e, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6b, 0x2c, 0x76, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x61, 0x69, + 0x72, 0x73, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x28, 0x6f, 0x29, 0x20, 0x64, 0x6f, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, 0x7e, + 0x3d, 0x20, 0x22, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2d, 0x2d, 0x20, 0x6d, 0x61, 0x6b, 0x65, 0x20, 0x73, 0x75, + 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x65, 0x64, 0x20, 0x6b, + 0x65, 0x79, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x6c, 0x79, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x69, 0x66, 0x69, 0x65, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6b, 0x29, + 0x20, 0x3d, 0x3d, 0x20, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x20, 0x20, 0x69, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x76, 0x29, 0x20, 0x3d, + 0x3d, 0x20, 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x20, + 0x5b, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x6b, 0x29, 0x20, + 0x2e, 0x2e, 0x20, 0x22, 0x5d, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x28, 0x76, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x2c, 0x5c, 0x6e, 0x22, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x20, + 0x5b, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x6b, 0x29, 0x20, + 0x2e, 0x2e, 0x20, 0x22, 0x5d, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, + 0x6c, 0x69, 0x7a, 0x65, 0x20, 0x28, 0x76, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, + 0x20, 0x20, 0x22, 0x2c, 0x20, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x2c, + 0x5c, 0x6e, 0x22, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x20, 0x22, + 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x6f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x28, 0x6b, 0x29, 0x20, 0x2e, + 0x2e, 0x20, 0x22, 0x20, 0x3d, 0x20, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, + 0x7a, 0x65, 0x28, 0x76, 0x2c, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x20, 0x20, 0x22, + 0x2c, 0x20, 0x73, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x29, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x2c, 0x5c, 0x6e, 0x22, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x61, 0x62, 0x73, 0x20, 0x2e, 0x2e, 0x20, 0x22, 0x7d, 0x22, 0x0a, + 0x20, 0x20, 0x65, 0x6c, 0x73, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x20, 0x28, 0x22, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, + 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x22, 0x20, 0x2e, 0x2e, 0x20, 0x74, 0x79, 0x70, 0x65, 0x28, 0x6f, 0x29, + 0x20, 0x29, 0x0a, + 0x20, 0x20, 0x65, 0x6e, 0x64, 0x0a, + 0x20, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x0a, + 0x65, 0x6e, 0x64, 0x0a, + 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x0a, +}; + +// +// Lua Helper Functions +// +void bail(lua_State *L, const char *msg){ + std::cerr << msg << lua_tostring(L, -1) << endl; + abort(); +} + +void stack_print (const char *file, int line, lua_State *L) { + cout << file << ":" << line << ": stack size: " << lua_gettop(L) << endl;; + for (int i = 1; i < lua_gettop(L) + 1; i++) { + cout << file << ":" << line << ": "; + cout << i << ": "; + if (lua_istable (L, i)) + cout << " table: " << lua_topointer (L, i) << endl; + else if (lua_isnumber (L, i)) + cout << " number: " << lua_tonumber (L, i) << endl; + else if (lua_isuserdata (L, i)) { + void* userdata = (void*) lua_touserdata (L, i); + cout << " userdata (" << userdata << ")" << endl; + } else if (lua_isstring (L, i)) + cout << " string: " << lua_tostring(L, i) << endl; + else if (lua_isfunction (L, i)) + cout << " function" << endl; + else if (lua_isnil (L, i)) + cout << " nil" << endl; + else + cout << " unknown: " << lua_typename (L, lua_type (L, i)) << endl; + } +} + +void l_push_LuaKey (lua_State *L, const LuaKey &key) { + if (key.type == LuaKey::Integer) + lua_pushnumber (L, key.int_value); + else + lua_pushstring(L, key.string_value.c_str()); +} + +bool query_key_stack (lua_State *L, std::vector key_stack) { + for (int i = key_stack.size() - 1; i >= 0; i--) { + // get the global value when the result of a lua expression was not + // pushed onto the stack via the return statement. + if (lua_gettop(L) == 0) { + lua_getglobal (L, key_stack[key_stack.size() - 1].string_value.c_str()); + + if (lua_isnil(L, -1)) { + return false; + } + + continue; + } + + l_push_LuaKey (L, key_stack[i]); + + lua_gettable (L, -2); + + // return if key is not found + if (lua_isnil(L, -1)) { + return false; + } + } + + return true; +} + +void create_key_stack (lua_State *L, std::vector key_stack) { + for (int i = key_stack.size() - 1; i > 0; i--) { + // get the global value when the result of a lua expression was not + // pushed onto the stack via the return statement. + if (lua_gettop(L) == 0) { + lua_getglobal (L, key_stack[key_stack.size() - 1].string_value.c_str()); + + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + lua_newtable(L); + lua_pushvalue(L, -1); + lua_setglobal(L, key_stack[key_stack.size() - 1].string_value.c_str()); + } + + continue; + } + + l_push_LuaKey (L, key_stack[i]); + + lua_pushvalue (L, -1); + lua_gettable (L, -3); + + if (lua_isnil(L, -1)) { + // parent, key, nil + lua_pop(L, 1); // parent, key + lua_newtable(L); // parent, key, table + lua_insert(L, -2); // parent, table, key + lua_pushvalue(L, -2); // parent, table, key, table + lua_settable (L, -4); // parent, table + } + } +} + +// +// LuaTableNode +// +std::vector LuaTableNode::getKeyStack() { + std::vector result; + + const LuaTableNode *node_ptr = this; + + do { + result.push_back (node_ptr->key); + node_ptr = node_ptr->parent; + } while (node_ptr != NULL); + + return result; +} + +std::string LuaTableNode::keyStackToString() { + std::vector key_stack = getKeyStack(); + + ostringstream result_stream (""); + for (int i = key_stack.size() - 1; i >= 0; i--) { + if (key_stack[i].type == LuaKey::String) + result_stream << "[\"" << key_stack[i].string_value << "\"]"; + else + result_stream << "[" << key_stack[i].int_value << "]"; + } + + return result_stream.str(); +} + +bool LuaTableNode::stackQueryValue() { + luaTable->pushRef(); + + lua_State *L = luaTable->L; + stackTop = lua_gettop(L); + + std::vector key_stack = getKeyStack(); + + return query_key_stack (L, key_stack); +} + +void LuaTableNode::stackCreateValue() { + luaTable->pushRef(); + + lua_State *L = luaTable->L; + stackTop = lua_gettop(L); + + std::vector key_stack = getKeyStack(); + + create_key_stack (L, key_stack); +} + +LuaTable LuaTableNode::stackQueryTable() { + luaTable->pushRef(); + + lua_State *L = luaTable->L; + stackTop = lua_gettop(L); + + std::vector key_stack = getKeyStack(); + + if (!query_key_stack (L, key_stack)) { + std::cerr << "Error: could not query table " << key << "." << std::endl; + abort(); + } + + return LuaTable::fromLuaState (L); +} + +LuaTable LuaTableNode::stackCreateLuaTable() { + luaTable->pushRef(); + + lua_State *L = luaTable->L; + stackTop = lua_gettop(L); + + std::vector key_stack = getKeyStack(); + + create_key_stack (L, key_stack); + + // create new table for the CustomType + lua_newtable(luaTable->L); // parent, CustomTable + // add table of CustomType to the parent + stackPushKey(); // parent, CustomTable, key + lua_pushvalue(luaTable->L, -2); // parent, CustomTable, key, CustomTable + lua_settable(luaTable->L, -4); + + LuaTable result; + result.luaStateRef = luaTable->luaStateRef->acquire(); + lua_pushvalue (result.luaStateRef->L, -1); + result.luaRef = luaL_ref (result.luaStateRef->L, LUA_REGISTRYINDEX); + + lua_pop (luaTable->luaStateRef->L, 2); + + return result; +} + +void LuaTableNode::stackPushKey() { + l_push_LuaKey (luaTable->L, key); +} + +void LuaTableNode::stackRestore() { + lua_pop (luaTable->L, lua_gettop(luaTable->L) - stackTop); + + luaTable->popRef(); +} + +bool LuaTableNode::exists() { + bool result = true; + + if (!stackQueryValue()) + result = false; + + stackRestore(); + + return result; +} + +void LuaTableNode::remove() { + if (stackQueryValue()) { + lua_pop(luaTable->L, 1); + + if (lua_gettop(luaTable->L) != 0) { + l_push_LuaKey (luaTable->L, key); + lua_pushnil (luaTable->L); + lua_settable (luaTable->L, -3); + } else { + lua_pushnil (luaTable->L); + lua_setglobal (luaTable->L, key.string_value.c_str()); + } + } + + stackRestore(); +} + +size_t LuaTableNode::length() { + size_t result = 0; + + if (stackQueryValue()) { +#if LUA_VERSION_NUM == 501 + result = lua_objlen(luaTable->L, -1); +#elif LUA_VERSION_NUM >= 502 + result = lua_rawlen(luaTable->L, -1); +#endif + } + + stackRestore(); + + return result; +} + +std::vector LuaTableNode::keys() { + std::vector result; + + if (stackQueryValue()) { + // loop over all keys + lua_pushnil(luaTable->L); + while (lua_next(luaTable->L, -2) != 0) { + if (lua_isnumber(luaTable->L, -2)) { + double number = lua_tonumber (luaTable->L, -2); + double frac; + if (modf (number, &frac) == 0) { + LuaKey key (static_cast(number)); + result.push_back (key); + } + } else if (lua_isstring (luaTable->L, -2)) { + LuaKey key (lua_tostring(luaTable->L, -2)); + result.push_back (key); + } else { + cerr << "Warning: invalid LuaKey type for key " << lua_typename(luaTable->L, lua_type(luaTable->L, -2)) << "!" << endl; + } + + lua_pop(luaTable->L, 1); + } + } + + stackRestore(); + + return result; +} + + +template<> bool LuaTableNode::getDefault(const bool &default_value) { + bool result = default_value; + + if (stackQueryValue()) { + result = lua_toboolean (luaTable->L, -1); + } + + stackRestore(); + + return result; +} + +template<> float LuaTableNode::getDefault(const float &default_value) { + float result = default_value; + + if (stackQueryValue()) { + result = static_cast(lua_tonumber (luaTable->L, -1)); + } + + stackRestore(); + + return result; +} + +template<> double LuaTableNode::getDefault(const double &default_value) { + double result = default_value; + + if (stackQueryValue()) { + result = lua_tonumber (luaTable->L, -1); + } + + stackRestore(); + + return result; +} + +template<> std::string LuaTableNode::getDefault(const std::string &default_value) { + std::string result = default_value; + + if (stackQueryValue() && lua_isstring(luaTable->L, -1)) { + result = lua_tostring (luaTable->L, -1); + } + + stackRestore(); + + return result; +} + +template<> void LuaTableNode::set(const bool &value) { + stackCreateValue(); + + l_push_LuaKey (luaTable->L, key); + lua_pushboolean(luaTable->L, value); + // stack: parent, key, value + lua_settable (luaTable->L, -3); + + stackRestore(); +} + +template<> void LuaTableNode::set(const float &value) { + stackCreateValue(); + + l_push_LuaKey (luaTable->L, key); + lua_pushnumber(luaTable->L, static_cast(value)); + // stack: parent, key, value + lua_settable (luaTable->L, -3); + + stackRestore(); +} + +template<> void LuaTableNode::set(const double &value) { + stackCreateValue(); + + l_push_LuaKey (luaTable->L, key); + lua_pushnumber(luaTable->L, value); + // stack: parent, key, value + lua_settable (luaTable->L, -3); + + stackRestore(); +} + +template<> void LuaTableNode::set(const std::string &value) { + stackCreateValue(); + + l_push_LuaKey (luaTable->L, key); + lua_pushstring(luaTable->L, value.c_str()); + // stack: parent, key, value + lua_settable (luaTable->L, -3); + + stackRestore(); +} + +// +// LuaTable +// +LuaTable::LuaTable (const LuaTable &other) : + filename (other.filename), + referencesGlobal (other.referencesGlobal) { + if (other.luaStateRef) { + luaStateRef = other.luaStateRef->acquire(); + + if (!referencesGlobal) { + lua_rawgeti(luaStateRef->L, LUA_REGISTRYINDEX, other.luaRef); + luaRef = luaL_ref (luaStateRef->L, LUA_REGISTRYINDEX); + } + } +} + +LuaTable& LuaTable::operator= (const LuaTable &other) { + if (&other != this) { + if (luaStateRef) { + // cleanup any existing reference + luaL_unref (luaStateRef->L, LUA_REGISTRYINDEX, luaRef); + + // if this is the last, delete the Lua state + int ref_count = luaStateRef->release(); + + if (ref_count == 0) { + if (luaStateRef->freeOnZeroRefs) { + lua_close (luaStateRef->L); + } + delete luaStateRef; + luaStateRef = NULL; + } + } + + filename = other.filename; + luaStateRef = other.luaStateRef; + referencesGlobal = other.referencesGlobal; + + if (other.luaStateRef) { + luaStateRef = other.luaStateRef->acquire(); + + if (!referencesGlobal) { + lua_rawgeti(luaStateRef->L, LUA_REGISTRYINDEX, other.luaRef); + luaRef = luaL_ref (luaStateRef->L, LUA_REGISTRYINDEX); + } + } + } + + return *this; +} + +LuaTable::~LuaTable() { + if (luaRef != -1) { + luaL_unref (luaStateRef->L, LUA_REGISTRYINDEX, luaRef); + } + + if (luaStateRef) { + int ref_count = luaStateRef->release(); + + if (ref_count == 0) { + if (luaStateRef->freeOnZeroRefs) { + lua_close (luaStateRef->L); + } + delete luaStateRef; + luaStateRef = NULL; + } + } +} + +int LuaTable::length() { + pushRef(); + + if ((lua_gettop(L) == 0) || (lua_type (L, -1) != LUA_TTABLE)) { + cerr << "Error: cannot query table length. No table on stack!" << endl; + abort(); + } + size_t result = 0; + +#if LUA_VERSION_NUM == 501 + result = lua_objlen(L, -1); +#elif LUA_VERSION_NUM >= 502 + result = lua_rawlen(L, -1); +#endif + + popRef(); + + return result; +} + +void LuaTable::pushRef() { + assert (luaStateRef); + assert (luaStateRef->L); + + if (!referencesGlobal) { + lua_rawgeti(luaStateRef->L, LUA_REGISTRYINDEX, luaRef); + } + L = luaStateRef->L; +} + +void LuaTable::popRef() { + if (!referencesGlobal) { + lua_pop (luaStateRef->L, 1); + } + L = NULL; +} + +LuaTable LuaTable::fromFile (const char* _filename) { + LuaTable result; + + result.filename = _filename; + result.luaStateRef = new LuaStateRef(); + result.luaStateRef->L = luaL_newstate(); + result.luaStateRef->count = 1; + luaL_openlibs (result.luaStateRef->L); + + // Add the directory of _filename to package.path + result.addSearchPath(get_file_directory (_filename).c_str()); + + // run the file we + if (luaL_dofile (result.luaStateRef->L, _filename)) { + bail (result.luaStateRef->L, "Error running file: "); + } + + result.luaRef = luaL_ref (result.luaStateRef->L, LUA_REGISTRYINDEX); + + return result; +} + +LuaTable LuaTable::fromLuaExpression (const char* lua_expr) { + LuaTable result; + + result.luaStateRef = new LuaStateRef(); + result.luaStateRef->L = luaL_newstate(); + result.luaStateRef->count = 1; + luaL_openlibs (result.luaStateRef->L); + + if (luaL_loadstring (result.luaStateRef->L, lua_expr)) { + bail (result.luaStateRef->L, "Error compiling expression!"); + } + + if (lua_pcall (result.luaStateRef->L, 0, LUA_MULTRET, 0)) { + bail (result.luaStateRef->L, "Error running expression!"); + } + + if (lua_gettop(result.luaStateRef->L) != 0) { + result.luaRef = luaL_ref (result.luaStateRef->L, LUA_REGISTRYINDEX); + } else { + result.referencesGlobal = true; + } + + return result; +} + +LuaTable LuaTable::fromLuaState (lua_State* L) { + LuaTable result; + + result.luaStateRef = new LuaStateRef(); + result.luaStateRef->L = L; + result.luaStateRef->count = 1; + result.luaStateRef->freeOnZeroRefs = false; + + lua_pushvalue (result.luaStateRef->L, -1); + result.luaRef = luaL_ref (result.luaStateRef->L, LUA_REGISTRYINDEX); + + return result; +} + +void LuaTable::addSearchPath(const char* path) { + if (luaStateRef->L == NULL) { + cerr << "Error: Cannot add search path: Lua state is not initialized!" << endl; + abort(); + } + + lua_getglobal(luaStateRef->L, "package"); + lua_getfield (luaStateRef->L, -1, "path"); + if (lua_type(luaStateRef->L, -1) != LUA_TSTRING) { + cerr << "Error: could not get package.path!" << endl; + abort(); + } + + string package_path = lua_tostring (luaStateRef->L, -1); + package_path = package_path + string (";") + string(path) + "?.lua;"; + + lua_pushstring(luaStateRef->L, package_path.c_str()); + lua_setfield (luaStateRef->L, -3, "path"); + + lua_pop(luaStateRef->L, 2); +} + +std::string LuaTable::serialize() { + pushRef(); + + std::string result; + + int current_top = lua_gettop(L); + if (lua_gettop(L) != 0) { + if (luaL_loadstring(L, serialize_lua)) { + bail (L, "Error loading serialization function: "); + } + + if (lua_pcall(L, 0, 0, 0)) { + bail (L, "Error compiling serialization function: " ); + } + + lua_getglobal (L, "serialize"); + assert (lua_isfunction (L, -1)); + lua_pushvalue (L, -2); + if (lua_pcall (L, 1, 1, 0)) { + bail (L, "Error while serializing: "); + } + result = string("return ") + lua_tostring (L, -1); + } else { + cerr << "Cannot serialize global Lua state!" << endl; + abort(); + } + + lua_pop (L, lua_gettop(L) - current_top); + + popRef(); + + return result; +} + +std::string LuaTable::orderedSerialize() { + pushRef(); + + std::string result; + + int current_top = lua_gettop(L); + if (lua_gettop(L) != 0) { + if (luaL_loadstring(L, serialize_lua)) { + bail (L, "Error loading serialization function: "); + } + + if (lua_pcall(L, 0, 0, 0)) { + bail (L, "Error compiling serialization function: " ); + } + + lua_getglobal (L, "serialize"); + assert (lua_isfunction (L, -1)); + lua_pushvalue (L, -2); + lua_pushstring (L, ""); + lua_pushboolean (L, true); + if (lua_pcall (L, 3, 1, 0)) { + bail (L, "Error while serializing: "); + } + result = string("return ") + lua_tostring (L, -1); + } else { + cerr << "Cannot serialize global Lua state!" << endl; + abort(); + } + + lua_pop (L, lua_gettop(L) - current_top); + + popRef(); + + return result; +} diff --git a/3rdparty/rbdl/addons/luamodel/luatables.h b/3rdparty/rbdl/addons/luamodel/luatables.h new file mode 100644 index 0000000..b02b180 --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/luatables.h @@ -0,0 +1,261 @@ +/* + * LuaTables++ + * Copyright (c) 2013-2014 Martin Felis . + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef LUATABLES_H +#define LUATABLES_H + +#include +#include +#include +#include +#include + +#include + +// Forward declaration for Lua +extern "C" { +struct lua_State; +} + +struct RBDL_DLLAPI LuaKey { + enum Type { + String, + Integer + }; + + Type type; + int int_value; + std::string string_value; + + bool operator<( const LuaKey& rhs ) const { + if (type == String && rhs.type == Integer) { + return false; + } else if (type == Integer && rhs.type == String) { + return true; + } else if (type == Integer && rhs.type == Integer) { + return int_value < rhs.int_value; + } + + return string_value < rhs.string_value; + } + + LuaKey (const char* key_value) : + type (String), + int_value (0), + string_value (key_value) { } + LuaKey (int key_value) : + type (Integer), + int_value (key_value), + string_value ("") {} +}; + +inline std::ostream& operator<<(std::ostream& output, const LuaKey &key) { + if (key.type == LuaKey::Integer) + output << key.int_value << "(int)"; + else + output << key.string_value << "(string)"; + return output; +} +struct RBDL_DLLAPI LuaTable; +struct RBDL_DLLAPI LuaTableNode; + +struct RBDL_DLLAPI LuaTableNode { + LuaTableNode() : + parent (NULL), + luaTable (NULL), + key("") + { } + LuaTableNode operator[](const char *child_str) { + LuaTableNode child_node; + + child_node.luaTable = luaTable; + child_node.parent = this; + child_node.key = LuaKey (child_str); + + return child_node; + } + LuaTableNode operator[](int child_index) { + LuaTableNode child_node; + + child_node.luaTable = luaTable; + child_node.parent = this; + child_node.key = LuaKey (child_index); + + return child_node; + } + bool stackQueryValue(); + void stackPushKey(); + void stackCreateValue(); + void stackRestore(); + LuaTable stackQueryTable(); + LuaTable stackCreateLuaTable(); + + std::vector getKeyStack(); + std::string keyStackToString(); + + bool exists(); + void remove(); + size_t length(); + std::vector keys(); + + // Templates for setters and getters. Can be specialized for custom + // types. + template + void set (const T &value); + template + T getDefault (const T &default_value); + + template + T get() { + if (!exists()) { + std::cerr << "Error: could not find value " << keyStackToString() << "." << std::endl; + abort(); + } + return getDefault (T()); + } + + // convenience operators (assignment, conversion, comparison) + template + void operator=(const T &value) { + set(value); + } + template + operator T() { + return get(); + } + template + bool operator==(T value) { + return value == get(); + } + template + bool operator!=(T value) { + return value != get(); + } + + LuaTableNode *parent; + LuaTable *luaTable; + LuaKey key; + int stackTop; +}; + +template +bool operator==(T value, LuaTableNode node) { + return value == (T) node; +} +template +bool operator!=(T value, LuaTableNode node) { + return value != (T) node; +} + +template<> bool LuaTableNode::getDefault(const bool &default_value); +template<> double LuaTableNode::getDefault(const double &default_value); +template<> float LuaTableNode::getDefault(const float &default_value); +template<> std::string LuaTableNode::getDefault(const std::string &default_value); + +template<> void LuaTableNode::set(const bool &value); +template<> void LuaTableNode::set(const float &value); +template<> void LuaTableNode::set(const double &value); +template<> void LuaTableNode::set(const std::string &value); + +/// Reference counting Lua state +struct RBDL_DLLAPI LuaStateRef { + LuaStateRef () : + L (NULL), + count (0), + freeOnZeroRefs(true) + {} + + LuaStateRef* acquire() { + count = count + 1; + return this; + } + + int release() { + count = count - 1; + return count; + } + + lua_State *L; + unsigned int count; + bool freeOnZeroRefs; +}; + +struct RBDL_DLLAPI LuaTable { + LuaTable () : + filename (""), + luaStateRef (NULL), + luaRef(-1), + L (NULL), + referencesGlobal (false) + {} + LuaTable (const LuaTable &other); + LuaTable& operator= (const LuaTable &other); + ~LuaTable(); + + LuaTableNode operator[] (const char* key) { + LuaTableNode root_node; + root_node.key = LuaKey (key); + root_node.parent = NULL; + root_node.luaTable = this; + + return root_node; + } + LuaTableNode operator[] (int key) { + LuaTableNode root_node; + root_node.key = LuaKey (key); + root_node.parent = NULL; + root_node.luaTable = this; + + return root_node; + } + int length(); + void addSearchPath (const char* path); + std::string serialize (); + + /// Serializes the data in a predictable ordering. + std::string orderedSerialize (); + + /// Pushes the Lua table onto the stack of the internal Lua state. + // I.e. makes the Lua table active that is associated with this + // LuaTable. + void pushRef(); + /// Pops the Lua table from the stack of the internal Lua state. + // Cleans up a previous pushRef() + void popRef(); + + static LuaTable fromFile (const char *_filename); + static LuaTable fromLuaExpression (const char* lua_expr); + static LuaTable fromLuaState (lua_State *L); + + std::string filename; + LuaStateRef *luaStateRef; + int luaRef; + lua_State *L; + + bool referencesGlobal; +}; + +/* LUATABLES_H */ +#endif diff --git a/3rdparty/rbdl/addons/luamodel/rbdl_luamodel_util.cc b/3rdparty/rbdl/addons/luamodel/rbdl_luamodel_util.cc new file mode 100644 index 0000000..4c85cb9 --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/rbdl_luamodel_util.cc @@ -0,0 +1,100 @@ +#include "rbdl/rbdl.h" +#include "rbdl/rbdl_utils.h" +#include "luamodel.h" + +#include +#include +#include + +using namespace std; + +using namespace RigidBodyDynamics::Math; + +void usage (const char* argv_0) { + cerr << "Usage: " << argv_0 << "[-v] [-m] [-d] " << endl; + cerr << " -v | --verbose enable additional output" << endl; + cerr << " -d | --dof-overview print an overview of the degress of freedom" << endl; + cerr << " -m | --model-hierarchy print the hierarchy of the model" << endl; + cerr << " -o | --body-origins print the origins of all bodies that have names" << endl; + cerr << " -c | --center_of_mass print center of mass for bodies and full model" << endl; + cerr << " -h | --help print this help" << endl; + exit (1); +} + +int main (int argc, char *argv[]) { + if (argc < 2 || argc > 4) { + usage(argv[0]); + } + + bool verbose = false; + bool dof_overview = false; + bool model_hierarchy = false; + bool body_origins = false; + bool center_of_mass = false; + + string filename = argv[1]; + + for (int i = 1; i < argc; i++) { + if (string(argv[i]) == "-v" || string (argv[i]) == "--verbose") + verbose = true; + else if (string(argv[i]) == "-d" || string (argv[i]) == "--dof-overview") + dof_overview = true; + else if (string(argv[i]) == "-m" || string (argv[i]) == "--model-hierarchy") + model_hierarchy = true; + else if (string(argv[i]) == "-o" || string (argv[i]) == "--body-origins") + body_origins = true; + else if (string(argv[i]) == "-c" || string (argv[i]) == "--center-of-mass") + center_of_mass = true; + else if (string(argv[i]) == "-h" || string (argv[i]) == "--help") + usage(argv[0]); + else + filename = argv[i]; + } + + RigidBodyDynamics::Model model; + + if (!RigidBodyDynamics::Addons::LuaModelReadFromFile(filename.c_str(), &model, verbose)) { + cerr << "Loading of lua model failed!" << endl; + return -1; + } + + cout << "Model loading successful!" << endl; + + if (dof_overview) { + cout << "Degree of freedom overview:" << endl; + cout << RigidBodyDynamics::Utils::GetModelDOFOverview(model); + } + + if (model_hierarchy) { + cout << "Model Hierarchy:" << endl; + cout << RigidBodyDynamics::Utils::GetModelHierarchy (model); + } + + if (body_origins) { + cout << "Body Origins:" << endl; + cout << RigidBodyDynamics::Utils::GetNamedBodyOriginsOverview(model); + } + + if (center_of_mass) { + VectorNd q_zero (VectorNd::Zero (model.q_size)); + VectorNd qdot_zero (VectorNd::Zero (model.qdot_size)); + RigidBodyDynamics::UpdateKinematics (model, q_zero, qdot_zero, qdot_zero); + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + if (model.mBodies[i].mIsVirtual) + continue; + + SpatialRigidBodyInertia rbi_base = model.X_base[i].apply(model.I[i]); + Vector3d body_com = rbi_base.h / rbi_base.m; + cout << setw(12) << model.GetBodyName (i) << ": " << setw(10) << body_com.transpose() << endl; + } + + Vector3d model_com; + double mass; + RigidBodyDynamics::Utils::CalcCenterOfMass (model, q_zero, qdot_zero, mass, model_com); + cout << setw(14) << "Model COM: " << setw(10) << model_com.transpose() << endl; + cout << setw(14) << "Model mass: " << mass << endl; + } + + return 0; +} diff --git a/3rdparty/rbdl/addons/luamodel/sampleconstrainedmodel.lua b/3rdparty/rbdl/addons/luamodel/sampleconstrainedmodel.lua new file mode 100644 index 0000000..b93186e --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/sampleconstrainedmodel.lua @@ -0,0 +1,267 @@ +-- 5b3d.lua +-- Copyright (c) 2016 Davide Corradi + +-- Parameters + +m1 = 2 +l1 = 2 +r1 = 0.2 +Izz1 = m1 * l1 * l1 / 3 + +m2 = 2 +l2 = 2 +r2 = 0.2 +Izz2 = m2 * l2 * l2 / 3 + +bodies = { + + virtual = { + mass = 0, + com = {0, 0, 0}, + inertia = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + }, + }, + + link1 = { + mass = m1, + com = {l1/2, 0, 0}, + inertia = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, Izz1}, + }, + }, + + link2 = { + mass = m2, + com = {l2/2, 0, 0}, + inertia = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, Izz2}, + }, + }, + +} + +joints = { + + revZ = { + {0, 0, 1, 0, 0, 0}, + }, + + trnXYZ = { + {0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 1, 0}, + {0, 0, 0, 0, 0, 1}, + }, + +} + +meshes = { + link1 = { + name = 'link1', + dimensions = {l1, r1, r1}, + color = {1, 0, 0}, + src = 'unit_cylinder_medres_z.obj', + mesh_center = {l1/2, 0, 0}, + }, + link2 = { + name = 'link2', + dimensions = {l2, r2, r2}, + color = {0, 1, 0}, + src = 'unit_cylinder_medres_z.obj', + mesh_center = {l2/2, 0, 0}, + }, +} + +model = { + + gravity = {0, 0, 0}, + + configuration = { + axis_front = { 1., 0., 0.}, + axis_right = { 0., -1., 0.}, + axis_up = { 0., 0., 1.}, + }, + + frames = { + + { + name = 'base', + parent = 'ROOT', + body = bodies.virtual, + joint = joints.trnXYZ, + }, + { + name = 'l11', + parent = 'base', + body = bodies.link1, + joint = joints.revZ, + visuals = { meshes.link1 }, + }, + { + name = 'l12', + parent = 'l11', + body = bodies.link2, + joint = joints.revZ, + joint_frame = { + r = {l1, 0, 0}, + }, + visuals = { meshes.link2 }, + }, + { + name = 'l21', + parent = 'base', + body = bodies.link1, + joint = joints.revZ, + visuals = { meshes.link1 }, + }, + { + name = 'l22', + parent = 'l21', + body = bodies.link2, + joint = joints.revZ, + joint_frame = { + r = {l1, 0, 0}, + }, + visuals = { meshes.link2 }, + }, + + }, + + constraint_sets = { + loop_constraints = { + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 1, 0, 0}, + stabilization_coefficient = 0.1, + name = 'linkTX', + }, + + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 0, 1, 0}, + stabilization_coefficient = 0.1, + name = 'linkTY', + }, + }, + + all_constraints = { + { + constraint_type = 'contact', + body = 'base', + point = {0, 0, 0}, + normal = {1, 0, 0}, + name = 'baseTX', + normal_acceleration = 0, + }, + + { + constraint_type = 'contact', + body = 'base', + normal = {0, 1, 0}, + name = 'baseTY', + }, + + { + constraint_type = 'contact', + body = 'base', + normal = {0, 0, 1}, + name = 'baseTZ', + }, + + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 1, 0, 0}, + stabilization_coefficient = 0.1, + name = 'linkTX', + }, + + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 0, 1, 0}, + stabilization_coefficient = 0.1, + name = 'linkTY', + }, + }, + }, + +} + +return model \ No newline at end of file diff --git a/3rdparty/rbdl/addons/luamodel/samplemodel.lua b/3rdparty/rbdl/addons/luamodel/samplemodel.lua new file mode 100644 index 0000000..d2d501a --- /dev/null +++ b/3rdparty/rbdl/addons/luamodel/samplemodel.lua @@ -0,0 +1,87 @@ +inertia = { + {1.1, 0.1, 0.2}, + {0.3, 1.2, 0.4}, + {0.5, 0.6, 1.3} +} + +pelvis = { mass = 9.3, com = { 1.1, 1.2, 1.3}, inertia = inertia } +thigh = { mass = 4.2, com = { 1.1, 1.2, 1.3}, inertia = inertia } +shank = { mass = 4.1, com = { 1.1, 1.2, 1.3}, inertia = inertia } +foot = { mass = 1.1, com = { 1.1, 1.2, 1.3}, inertia = inertia } + +bodies = { + pelvis = pelvis, + thigh_right = thigh, + shank_right = shank, + thigh_left = thigh, + shank_left = shank +} + +joints = { + freeflyer = { + { 0., 0., 0., 1., 0., 0.}, + { 0., 0., 0., 0., 1., 0.}, + { 0., 0., 0., 0., 0., 1.}, + { 0., 0., 1., 0., 0., 0.}, + { 0., 1., 0., 0., 0., 0.}, + { 1., 0., 0., 0., 0., 0.} + }, + spherical_zyx = { + { 0., 0., 1., 0., 0., 0.}, + { 0., 1., 0., 0., 0., 0.}, + { 1., 0., 0., 0., 0., 0.} + }, + rotational_y = { + { 0., 1., 0., 0., 0., 0.} + }, + fixed = {} +} + +model = { + frames = { + { + name = "pelvis", + parent = "ROOT", + body = bodies.pelvis, + joint = joints.freeflyer, + }, + { + name = "thigh_right", + parent = "pelvis", + body = bodies.thigh_right, + joint = joints.spherical_zyx, + }, + { + name = "shank_right", + parent = "thigh_right", + body = bodies.thigh_right, + joint = joints.rotational_y + }, + { + name = "foot_right", + parent = "shank_right", + body = bodies.thigh_right, + joint = joints.fixed + }, + { + name = "thigh_left", + parent = "pelvis", + body = bodies.thigh_left, + joint = joints.spherical_zyx + }, + { + name = "shank_left", + parent = "thigh_left", + body = bodies.thigh_left, + joint = joints.rotational_y + }, + { + name = "foot_left", + parent = "shank_left", + body = bodies.thigh_left, + joint = joints.fixed + }, + } +} + +return model diff --git a/3rdparty/rbdl/addons/muscle/CMakeLists.txt b/3rdparty/rbdl/addons/muscle/CMakeLists.txt new file mode 100644 index 0000000..fbc5317 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/CMakeLists.txt @@ -0,0 +1,89 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +CMAKE_POLICY(SET CMP0048 NEW) +CMAKE_POLICY(SET CMP0040 NEW) + +SET ( RBDL_ADDON_MUSCLE_VERSION_MAJOR 1 ) +SET ( RBDL_ADDON_MUSCLE_VERSION_MINOR 0 ) +SET ( RBDL_ADDON_MUSCLE_VERSION_PATCH 0 ) + +SET ( RBDL_ADDON_MUSCLE_VERSION + ${RBDL_ADDON_MUSCLE_VERSION_MAJOR}.${RBDL_ADDON_MUSCLE_VERSION_MINOR}.${RBDL_ADDON_MUSCLE_VERSION_PATCH} +) + + +PROJECT (RBDL_ADDON_MUSCLE VERSION ${RBDL_ADDON_MUSCLE_VERSION}) + + +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake ) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX + ) + +INCLUDE_DIRECTORIES ( + ${CMAKE_CURRENT_BINARY_DIR}/include/rbdl +) + + +SET(MUSCLE_SOURCES + muscle.h + Millard2016TorqueMuscle.h + Millard2016TorqueMuscle.cc + MuscleFunctionFactory.h + MuscleFunctionFactory.cc + TorqueMuscleFunctionFactory.h + TorqueMuscleFunctionFactory.cc + csvtools.h + csvtools.cc +) + +SET(MUSCLE_HEADERS + muscle.h + Millard2016TorqueMuscle.h + MuscleFunctionFactory.h + TorqueMuscleFunctionFactory.h + csvtools.h +) + +IF (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl_muscle-static STATIC ${MUSCLE_SOURCES} ) + SET_TARGET_PROPERTIES ( rbdl_muscle-static PROPERTIES PREFIX "lib") + SET_TARGET_PROPERTIES ( rbdl_muscle-static PROPERTIES OUTPUT_NAME "rbdl_muscle") + TARGET_LINK_LIBRARIES (rbdl_muscle-static + rbdl_geometry-static + rbdl-static + ) + + + INSTALL (TARGETS rbdl_muscle-static + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ELSE (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl_muscle SHARED ${MUSCLE_SOURCES} ) + SET_TARGET_PROPERTIES ( rbdl_muscle PROPERTIES + VERSION ${RBDL_VERSION} + SOVERSION ${RBDL_SO_VERSION} + ) + + TARGET_LINK_LIBRARIES (rbdl_muscle + rbdl_geometry + rbdl + ) + + + INSTALL (TARGETS rbdl_muscle + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ENDIF (RBDL_BUILD_STATIC) + +FILE ( GLOB headers + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + ) + +INSTALL ( FILES ${MUSCLE_HEADERS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/rbdl/addons/muscle + ) diff --git a/3rdparty/rbdl/addons/muscle/LICENSE b/3rdparty/rbdl/addons/muscle/LICENSE new file mode 100644 index 0000000..a25f3e9 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/LICENSE @@ -0,0 +1,23 @@ +Rigid Body Dynamics Library Muscle Addon - +Copyright (c) 2016 Matthew Millard + +(zlib license) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/3rdparty/rbdl/addons/muscle/LICENSE_APACHE-2.0.txt b/3rdparty/rbdl/addons/muscle/LICENSE_APACHE-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/LICENSE_APACHE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/3rdparty/rbdl/addons/muscle/Millard2016TorqueMuscle.cc b/3rdparty/rbdl/addons/muscle/Millard2016TorqueMuscle.cc new file mode 100644 index 0000000..304561d --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/Millard2016TorqueMuscle.cc @@ -0,0 +1,1445 @@ +//============================================================================== +/* + * RBDL - Rigid Body Dynamics Library: Addon : muscle + * Copyright (c) 2016 Matthew Millard + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include "Millard2016TorqueMuscle.h" +#include "TorqueMuscleFunctionFactory.h" +#include "csvtools.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +static double EPSILON = std::numeric_limits::epsilon(); +static double SQRTEPSILON = sqrt(EPSILON); + +using namespace RigidBodyDynamics::Math; +using namespace RigidBodyDynamics::Addons::Muscle; +using namespace RigidBodyDynamics::Addons::Geometry; +using namespace std; + +/************************************************************* + Table Access Structure Names +*************************************************************/ +const double gravity = 9.81; //Needed for the strength scaling used + //by Anderson et al. + +const char* DataSet::names[] = { "Anderson2007", + "Gymnast"}; + +const char* GenderSet::names[] = {"Male", + "Female"}; + +const char* AgeGroupSet::names[] = {"Young18To25", + "Middle55To65", + "SeniorOver65"}; + +const char* JointTorqueSet::names[] = { "HipExtension" , + "HipFlexion" , + "KneeExtension" , + "KneeFlexion" , + "AnkleExtension" , + "AnkleFlexion" , + "ElbowExtension" , + "ElbowFlexion" , + "ShoulderExtension" , + "ShoulderFlexion" , + "WristExtension" , + "WristFlexion" , + "ShoulderHorizontalAdduction", + "ShoulderHorizontalAbduction", + "ShoulderInternalRotation" , + "ShoulderExternalRotation" , + "WristUlnarDeviation" , + "WristRadialDeviation" , + "WristPronation" , + "WristSupination" , + "LumbarExtension" , + "LumbarFlexion" }; + +const char* Anderson2007::GenderNames[] = {"Male","Female"}; + +const char* Anderson2007::AgeGroupNames[] = { "Young18To25", + "Middle55To65", + "SeniorOver65"}; + +const char* Anderson2007::JointTorqueNames[] = {"HipExtension" , + "HipFlexion" , + "KneeExtension" , + "KneeFlexion" , + "AnkleExtension", + "AnkleFlexion" }; + +const char* Gymnast::GenderNames[] = {"Male"}; +const char* Gymnast::AgeGroupNames[] = {"Young18To25"}; +const char* Gymnast::JointTorqueNames[] = + { "HipExtension" , + "HipFlexion" , + "KneeExtension" , + "KneeFlexion" , + "AnkleExtension" , + "AnkleFlexion" , + "ElbowExtension" , + "ElbowFlexion" , + "ShoulderExtension" , + "ShoulderFlexion" , + "WristExtension" , + "WristFlexion" , + "ShoulderHorizontalAdduction", + "ShoulderHorizontalAbduction", + "ShoulderInternalRotation" , + "ShoulderExternalRotation" , + "WristUlnarDeviation" , + "WristRadialDeviation" , + "WristPronation" , + "WristSupination" , + "LumbarExtension" , + "LumbarFlexion"}; + +/************************************************************* + Coefficient Tables +*************************************************************/ + +/* +This data is taken from Table 3 of + +Anderson, D. E., Madigan, M. L., & Nussbaum, M. A. (2007). +Maximum voluntary joint torque as a function of joint angle +and angular velocity: model development and application to +the lower limb. Journal of biomechanics, 40(14), 3105-3113. + +Each row contains the coefficients for the active and +passive torque characteristics for a specific joint, +direction, gender and age group. Each row corresponds +to a single block taken from Table 3, as read from +left to right top to bottom. The first 4 columns have +been added to describe the joint, direction, gender +and age group. + +Column labels: +Parameter Set Meta Data + 0: joint: hip0_knee1_ankle2, + 1: direction: ext0_flex1, + 2: gender: male0_female1, + 3: age: age18to25_0_55to65_1_g65_2, + +Active Torque-Angle and Torque-Velocity Curves + 4: c1, + 5: c2, + 6: c3, + 7: c4, + 8: c5, + 9: c6, +Passive Torque-Angle Curves + 10: b1, + 11: k1, + 12: b2, + 13: k2, +*/ +double const Millard2016TorqueMuscle::Anderson2007Table3Mean[36][14] = { +{0,0,0,0,0.161,0.958,0.932,1.578,3.19,0.242,-1.21,-6.351,0.476,5.91 }, +{0,0,1,0,0.181,0.697,1.242,1.567,3.164,0.164,-1.753,-6.358,0.239,3.872 }, +{0,0,0,1,0.171,0.922,1.176,1.601,3.236,0.32,-2.16,-8.073,0.108,4.593 }, +{0,0,1,1,0.14,0.83,1.241,1.444,2.919,0.317,-1.361,-7.128,0.013,6.479 }, +{0,0,0,2,0.144,0.896,1.125,1.561,3.152,0.477,-2.671,-7.85,0.092,5.192 }, +{0,0,1,2,0.138,0.707,1.542,1.613,3.256,0.36,-0.758,-7.545,0.018,6.061 }, +{0,1,0,0,0.113,0.738,-0.214,2.095,4.267,0.218,1.21,-6.351,-0.476,5.91 }, +{0,1,1,0,0.127,0.65,-0.35,2.136,4.349,0.156,1.753,-6.358,-0.239,3.872 }, +{0,1,0,1,0.107,0.712,-0.192,2.038,4.145,0.206,2.16,-8.073,-0.108,4.593 }, +{0,1,1,1,0.091,0.812,-0.196,2.145,4.366,0.186,1.361,-7.128,-0.013,6.479 }, +{0,1,0,2,0.101,0.762,-0.269,1.875,3.819,0.296,2.671,-7.85,-0.092,5.192 }, +{0,1,1,2,0.081,0.625,-0.422,2.084,4.245,0.196,0.758,-7.545,-0.018,6.061 }, +{1,0,0,0,0.163,1.258,1.133,1.517,3.952,0.095,0,0,-6.25,-4.521 }, +{1,0,1,0,0.159,1.187,1.274,1.393,3.623,0.173,0,0,-8.033,-5.25 }, +{1,0,0,1,0.156,1.225,1.173,1.518,3.954,0.266,0,0,-12.83,-5.127 }, +{1,0,1,1,0.128,1.286,1.141,1.332,3.469,0.233,0,0,-6.576,-4.466 }, +{1,0,0,2,0.137,1.31,1.067,1.141,3.152,0.386,0,0,-10.519,-5.662 }, +{1,0,1,2,0.124,1.347,1.14,1.066,2.855,0.464,0,0,-8.8,-6.763 }, +{1,1,0,0,0.087,0.869,0.522,2.008,5.233,0.304,0,0,6.25,-4.521 }, +{1,1,1,0,0.08,0.873,0.635,1.698,4.412,0.175,0,0,8.033,-5.25 }, +{1,1,0,1,0.081,0.986,0.523,1.83,4.777,0.23,0,0,12.83,-5.127 }, +{1,1,1,1,0.06,0.967,0.402,1.693,4.41,0.349,0,0,6.576,-4.466 }, +{1,1,0,2,0.069,0.838,0.437,1.718,4.476,0.414,0,0,10.519,-5.662 }, +{1,1,1,2,0.06,0.897,0.445,1.121,2.922,0.389,0,0,8.8,-6.763 }, +{2,0,0,0,0.095,1.391,0.408,0.987,3.558,0.295,-0.0005781,-5.819,0.967,6.09 }, +{2,0,1,0,0.104,1.399,0.424,0.862,3.109,0.189,-0.005218,-4.875,0.47,6.425 }, +{2,0,0,1,0.114,1.444,0.551,0.593,2.128,0.35,-0.001311,-10.943,0.377,8.916 }, +{2,0,1,1,0.093,1.504,0.381,0.86,3.126,0.349,-2.888e-05,-17.189,0.523,7.888 }, +{2,0,0,2,0.106,1.465,0.498,0.49,1.767,0.571,-5.693e-05,-21.088,0.488,7.309 }, +{2,0,1,2,0.125,1.299,0.58,0.587,1.819,0.348,-2.35e-05,-12.567,0.331,6.629 }, +{2,1,0,0,0.033,1.51,-0.187,0.699,1.94,0.828,0.0005781,-5.819,-0.967,6.09 }, +{2,1,1,0,0.027,1.079,-0.302,0.864,2.399,0.771,0.005218,-4.875,-0.47,6.425 }, +{2,1,0,1,0.028,1.293,-0.284,0.634,1.759,0.999,0.001311,-10.943,-0.377,8.916 }, +{2,1,1,1,0.024,1.308,-0.254,0.596,1.654,1.006,2.888e-05,-17.189,-0.523,7.888}, +{2,1,0,2,0.029,1.419,-0.174,0.561,1.558,1.198,5.693e-05,-21.088,-0.488,7.309}, +{2,1,1,2,0.022,1.096,-0.369,0.458,1.242,1.401,2.35e-05,-12.567,-0.331,6.629 }}; + + +//See the description for the mean data. This table constains the +//parameter standard deviations +double const Millard2016TorqueMuscle::Anderson2007Table3Std[36][14] = { +{0,0,0,0,0.049,0.201,0.358,0.286,0.586,0.272,0.66,0.97,0.547,4.955 }, +{0,0,1,0,0.047,0.13,0.418,0.268,0.542,0.175,1.93,2.828,0.292,1.895 }, +{0,0,0,1,0.043,0.155,0.195,0.306,0.622,0.189,1.297,2.701,0.091,0.854 }, +{0,0,1,1,0.032,0.246,0.365,0.223,0.45,0.14,1.294,2.541,0.02,2.924 }, +{0,0,0,2,0.039,0.124,0.077,0.184,0.372,0.368,0.271,3.402,0.111,1.691 }, +{0,0,1,2,0.003,0.173,0.279,0.135,0.273,0.237,0.613,0.741,0.031,2.265 }, +{0,1,0,0,0.025,0.217,0.245,0.489,0.995,0.225,0.66,0.97,0.547,4.955 }, +{0,1,1,0,0.033,0.178,0.232,0.345,0.702,0.179,1.93,2.828,0.292,1.895 }, +{0,1,0,1,0.02,0.248,0.274,0.318,0.652,0.088,1.297,2.701,0.091,0.854 }, +{0,1,1,1,0.016,0.244,0.209,0.375,0.765,0.262,1.294,2.541,0.02,2.924 }, +{0,1,0,2,0.025,0.151,0.234,0.164,0.335,0.102,0.271,3.402,0.111,1.691 }, +{0,1,1,2,0.008,0.062,0.214,0.321,0.654,0.28,0.613,0.741,0.031,2.265 }, +{1,0,0,0,0.04,0.073,0.073,0.593,1.546,0.171,0,0,2.617,0.553 }, +{1,0,1,0,0.028,0.084,0.181,0.38,0.989,0.27,0,0,3.696,1.512 }, +{1,0,0,1,0.031,0.063,0.048,0.363,0.947,0.06,0,0,2.541,2.148 }, +{1,0,1,1,0.016,0.094,0.077,0.319,0.832,0.133,0,0,1.958,1.63 }, +{1,0,0,2,0.017,0.127,0.024,0.046,0.04,0.124,0,0,1.896,1.517 }, +{1,0,1,2,0.018,0.044,0.124,0.128,0.221,0.129,0,0,6.141,0.742 }, +{1,1,0,0,0.015,0.163,0.317,1.364,3.554,0.598,0,0,2.617,0.553 }, +{1,1,1,0,0.015,0.191,0.287,0.825,2.139,0.319,0,0,3.696,1.512 }, +{1,1,0,1,0.017,0.138,0.212,0.795,2.067,0.094,0,0,2.541,2.148 }, +{1,1,1,1,0.015,0.21,0.273,0.718,1.871,0.143,0,0,1.958,1.63 }, +{1,1,0,2,0.022,0.084,0.357,0.716,1.866,0.201,0,0,1.896,1.517 }, +{1,1,1,2,0.005,0.145,0.21,0.052,0.135,0.078,0,0,6.141,0.742 }, +{2,0,0,0,0.022,0.089,0.083,0.595,2.144,0.214,0.001193,7.384,0.323,1.196 }, +{2,0,1,0,0.034,0.19,0.186,0.487,1.76,0.213,0.01135,6.77,0.328,1.177 }, +{2,0,0,1,0.029,0.136,0.103,0.165,0.578,0.133,0.003331,10.291,0.403,3.119 }, +{2,0,1,1,0.026,0.235,0.143,0.448,1.613,0.27,3.562e-05,7.848,0.394,1.141 }, +{2,0,0,2,0.035,0.136,0.132,0.262,0.944,0.313,3.164e-05,1.786,0.258,0.902 }, +{2,0,1,2,0.006,0.095,0.115,0.258,0.423,0.158,2.535e-05,10.885,0.247,2.186}, +{2,1,0,0,0.005,0.19,0.067,0.108,0.301,0.134,0.001193,7.384,0.323,1.196 }, +{2,1,1,0,0.006,0.271,0.171,0.446,1.236,0.206,0.01135,6.77,0.328,1.177 }, +{2,1,0,1,0.005,0.479,0.178,0.216,0.601,0.214,0.003331,10.291,0.403,3.119 }, +{2,1,1,1,0.002,0.339,0.133,0.148,0.41,0.284,3.562e-05,7.848,0.394,1.141 }, +{2,1,0,2,0.002,0.195,0.056,0.188,0.521,0.29,3.164e-05,1.786,0.258,0.902 }, +{2,1,1,2,0.003,0.297,0.109,0.089,0.213,0.427,2.535e-05,10.885,0.247,2.186}}; + +/* + This table contains parameters for the newly made torque muscle curves: + + 1. maxIsometricTorque Nm + 2. maxAngularVelocity rad/s + 3. angleAtOneActiveNormTorque rad + 4. angularWidthActiveTorque rad + 5. tvAtMaxEccentricVelocity Nm/Nm + 6. tvAtHalfMaxConcentricVelocity Nm/Nm + 7. angleAtZeroPassiveTorque rad + 8. angleAtOneNormPassiveTorque rad + +*/ + + + +double const Millard2016TorqueMuscle::GymnastWholeBody[22][12] = +{ {0,0,0,0,175.746, 9.02335, 1.06465, 1.05941, 1.1, 0.163849, 0.79158, 1.5708 }, + {0,1,0,0,157.293, 9.18043, 0.733038, 1.21999, 1.11905, 0.25, -0.0888019,-0.515207 }, + {1,0,0,0,285.619, 19.2161, 0.942478, 0.509636, 1.13292, 0.115304, 2.00713, 2.70526 }, + {1,1,0,0,98.7579, 16.633, 1.02974, 1.11003, 1.12, 0.19746, 0, -0.174533 }, + {2,0,0,0,127.561, 11.7646, 0.408, 0.660752, 1.159, 0.410591, 0.0292126, 0.785398 }, + {2,1,0,0,44.3106, 17.2746, -0.187, 0.60868, 1.2656, 0.112303, -0.523599, -1.0472 }, + {3,0,0,0,127.401, 16.249, 2.14675, 1.83085, 1.1, 0.250134, 2.8291, 3.55835 }, + {3,1,0,0,91.1388, 19.0764, 0.890118, 1.2898, 1.23011, 0.249656, -0.523599, -1.0472 }, + {5,0,0,0,15.5653, 36.5472, 1.55334, 1.38928, 1.16875, 0.115356, 1.0472, 1.5708 }, + {5,1,0,0,39.2252, 36.3901, 0.663225, 1.71042, 1.14, 0.115456, -0.793739, -1.49714 }, + {3,2,0,0,128.496, 18.05, 0.839204, 1.28041, 1.25856, 0.214179, 1.5708, 2.26893 }, + {3,3,0,0,94.6839, 18 , -0.277611, 2.37086, 1.23042, 0.224227, -0.523599, -1.0472 }, + {3,5,0,0,50.522 , 19.47, -1.18761, 2.80524, 1.27634, 0.285399, 1.39626, 1.91986 }, + {3,4,0,0,43.5837, 18, -0.670796, 1.98361, 1.35664, 0.229104, -1.0472, -1.5708 }, + {4,1,0,0,101.384, 18.1, 0.33, 3.62155, 1.37223, 0.189909, 0, -0.174533 }, + {4,0,0,0,69.8728, 18.45, 1.64319, 1.30795, 1.31709, 0.189676, 2.0944, 2.61799 }, + {5,6,0,0,13.5361, 35.45, -0.209204, 1.33735, 1.23945, 0.250544, -0.785398, -1.5708 }, + {5,7,0,0,12.976 , 27.88, -0.212389, 0.991803, 1.3877, 0.207506, 0.785398, 1.5708 }, + {5,9,0,0,31.4217, 18.02, 0.43, 1.47849, 1.34817, 0.196913, 0, -0.523599}, + {5,8,0,0,23.8345, 21.77, -1.14319, 2.56082, 1.31466, 0.2092, 0.349066, 0.872665 }, + {6,0,0,0,687.864, 1.0472, 1.5506, 1.14543, 1.1, 0.45, 0.306223, 1.35342 }, + {6,1,0,0,211.65 , 0.523599, 0, 6.28319, 1.1, 0.45, 0, -0.785398 }}; + +/************************************************************* + Map that goes from a single joint-torque-direction index to + the pair of joint and direction indicies used in the tables +*************************************************************/ + +const static struct JointSet{ + enum item { Hip = 0, + Knee, + Ankle, + Shoulder, + Elbow, + Wrist, + Lumbar, + Last}; + JointSet(){} +} JointSet; + + +struct DirectionSet{ + enum item { + Extension = 0, + Flexion, + HorizontalAdduction, + HorizontalAbduction, + ExternalRotation, + InternalRotation, + Supination, + Pronation, + RadialDeviation, + UlnarDeviation, + Last + }; + DirectionSet(){} +} DirectionSet; + + +const static int JointTorqueMap[22][3] = { + {(int)JointTorqueSet::HipExtension , (int)JointSet::Hip , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::HipFlexion , (int)JointSet::Hip , (int)DirectionSet::Flexion }, + {(int)JointTorqueSet::KneeExtension , (int)JointSet::Knee , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::KneeFlexion , (int)JointSet::Knee , (int)DirectionSet::Flexion }, + {(int)JointTorqueSet::AnkleExtension , (int)JointSet::Ankle , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::AnkleFlexion , (int)JointSet::Ankle , (int)DirectionSet::Flexion }, + {(int)JointTorqueSet::ElbowExtension , (int)JointSet::Elbow , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::ElbowFlexion , (int)JointSet::Elbow , (int)DirectionSet::Flexion }, + {(int)JointTorqueSet::ShoulderExtension , (int)JointSet::Shoulder , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::ShoulderFlexion , (int)JointSet::Shoulder , (int)DirectionSet::Flexion }, + {(int)JointTorqueSet::WristExtension , (int)JointSet::Wrist , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::WristFlexion , (int)JointSet::Wrist , (int)DirectionSet::Flexion }, + {(int)JointTorqueSet::ShoulderHorizontalAdduction , (int)JointSet::Shoulder , (int)DirectionSet::HorizontalAdduction}, + {(int)JointTorqueSet::ShoulderHorizontalAbduction , (int)JointSet::Shoulder , (int)DirectionSet::HorizontalAbduction}, + {(int)JointTorqueSet::ShoulderInternalRotation , (int)JointSet::Shoulder , (int)DirectionSet::InternalRotation }, + {(int)JointTorqueSet::ShoulderExternalRotation , (int)JointSet::Shoulder , (int)DirectionSet::ExternalRotation }, + {(int)JointTorqueSet::WristUlnarDeviation , (int)JointSet::Wrist , (int)DirectionSet::UlnarDeviation }, + {(int)JointTorqueSet::WristRadialDeviation , (int)JointSet::Wrist , (int)DirectionSet::RadialDeviation }, + {(int)JointTorqueSet::WristPronation , (int)JointSet::Wrist , (int)DirectionSet::Pronation }, + {(int)JointTorqueSet::WristSupination , (int)JointSet::Wrist , (int)DirectionSet::Supination }, + {(int)JointTorqueSet::LumbarExtension , (int)JointSet::Lumbar , (int)DirectionSet::Extension }, + {(int)JointTorqueSet::LumbarFlexion , (int)JointSet::Lumbar , (int)DirectionSet::Flexion }}; + + +/************************************************************* + Constructors +*************************************************************/ + +Millard2016TorqueMuscle:: + Millard2016TorqueMuscle( ) + :angleOffset(1.0), + signOfJointAngle(1.0), + signOfJointTorque(1.0), + signOfConcentricAnglularVelocity(1.0), + muscleName("empty") +{ + muscleCurvesAreDirty = true; +} + +Millard2016TorqueMuscle::Millard2016TorqueMuscle( + DataSet::item dataSet, + const SubjectInformation &subjectInfo, + int jointTorque, + double jointAngleOffsetRelativeToDoxygenFigures, + double signOfJointAngleRelativeToDoxygenFigures, + double signOfJointTorque, + const std::string& name + ):angleOffset(jointAngleOffsetRelativeToDoxygenFigures), + signOfJointAngle(signOfJointAngleRelativeToDoxygenFigures), + signOfJointTorque(signOfJointTorque), + signOfConcentricAnglularVelocity(signOfJointTorque), + muscleName(name), + dataSet(dataSet) +{ + + subjectHeightInMeters = subjectInfo.heightInMeters; + subjectMassInKg = subjectInfo.massInKg; + passiveCurveAngleOffset = 0.; + beta = 0.1; + + int gender = (int) subjectInfo.gender; + int ageGroup = (int) subjectInfo.ageGroup; + + int joint = -1; + int jointDirection = -1; + + for(int i=0; i < JointTorqueSet::Last; ++i){ + if(JointTorqueMap[i][0] == jointTorque){ + joint = JointTorqueMap[i][1]; + jointDirection = JointTorqueMap[i][2]; + } + } + + if(joint == -1 || jointDirection == -1){ + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << ": A jointTorqueDirection of " << jointTorque + << " does not exist."; + assert(0); + abort(); + } + + if( abs(abs(signOfJointAngle)-1) > EPSILON){ + + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << ": signOfJointAngleRelativeToAnderson2007 must be [-1,1] not " + << signOfJointAngle; + assert(0); + abort(); + } + + if( abs(abs(signOfConcentricAnglularVelocity)-1) > EPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << ": signOfJointAngularVelocityDuringConcentricContraction " + << "must be [-1,1] not " + << signOfConcentricAnglularVelocity; + assert(0); + abort(); + } + + if( abs(abs(signOfJointTorque)-1) > EPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << ": signOfJointTorque must be [-1,1] not " + << signOfJointTorque; + assert(0); + abort(); + } + + + if(subjectHeightInMeters <= 0){ + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << ": subjectHeightInMeters > 0, but it's " + << subjectHeightInMeters; + assert(0); + abort(); + } + + if(subjectMassInKg <= 0){ + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << ": subjectMassInKg > 0, but it's " + << subjectMassInKg; + assert(0); + abort(); + } + + + + int idx = -1; + int jointIdx = 0; + int dirIdx = 1; + int genderIdx = 2; + int ageIdx = 3; + + switch(dataSet){ + case DataSet::Anderson2007: + { + c1c2c3c4c5c6Anderson2007.resize(6); + b1k1b2k2Anderson2007.resize(4); + + for(int i=0; i<36;++i){ + + if( abs(Anderson2007Table3Mean[i][jointIdx] + -(double)joint) < EPSILON + && abs(Anderson2007Table3Mean[i][dirIdx] + -(double)jointDirection) < EPSILON + && abs(Anderson2007Table3Mean[i][genderIdx] + -(double)gender) < EPSILON + && abs(Anderson2007Table3Mean[i][ageIdx] + -(double)ageGroup) < EPSILON){ + idx = i; + } + } + + if(idx != -1){ + for(int i=0; i<6; ++i){ + c1c2c3c4c5c6Anderson2007[i] = Anderson2007Table3Mean[idx][i+4]; + } + for(int i=0; i<4; ++i){ + b1k1b2k2Anderson2007[i] = Anderson2007Table3Mean[idx][i+10]; + } + } + + } break; + + case DataSet::Gymnast: + { + gymnastParams.resize(8); + for(int i=0; i(this); + mutableThis->updateTorqueMuscleCurves(); + } + + double fiberAngle = calcFiberAngle(jointAngle); + double fiberVelocity = calcFiberAngularVelocity(jointAngularVelocity); + double ta = taCurve.calcValue(fiberAngle); + double tp = tpCurve.calcValue(fiberAngle); + double fiberVelocityNorm = fiberVelocity/omegaMax; + double tv = tvCurve.calcValue(fiberVelocityNorm); + + + double jointTorque = maxActiveIsometricTorque*( + activation*ta*tv + + passiveTorqueScale*tp + - beta*fiberVelocityNorm); + if(jointTorque < 0){ + jointTorque = 0; + } + + return jointTorque*signOfJointTorque; +} + + + +void Millard2016TorqueMuscle:: + calcTorqueMuscleInfo(double jointAngle, + double jointAngularVelocity, + double activation, + TorqueMuscleInfo& tmi) const +{ + if(muscleCurvesAreDirty){ + Millard2016TorqueMuscle* mutableThis = + const_cast(this); + mutableThis->updateTorqueMuscleCurves(); + } + + double fiberAngle = calcFiberAngle(jointAngle); + double fiberAngularVelocity = calcFiberAngularVelocity(jointAngularVelocity); + double ta = taCurve.calcValue(fiberAngle); + double tp = passiveTorqueScale*tpCurve.calcValue(fiberAngle); + + double omegaNorm = fiberAngularVelocity/omegaMax; + double D_wn_w = 1.0/omegaMax; + double tv = tvCurve.calcValue(omegaNorm); + + double betaUpd = beta; + double tb = -betaUpd*omegaNorm; + + //The jointTorque is not allowed to go below 0, as this corresponds + //to the muscle pushing. If the joint torque is going negative, we update + //beta so that it will perfectly zero the joint torque. + if(activation*ta*tv + passiveTorqueScale*tp + tb < 0){ + betaUpd = (activation*ta*tv + passiveTorqueScale*tp)/omegaNorm; + tb = -betaUpd*omegaNorm; + } + + double D_ta_DfiberAngle = taCurve.calcDerivative(fiberAngle,1); + double D_tp_DfiberAngle = passiveTorqueScale*tpCurve.calcDerivative(fiberAngle,1); + double D_tv_DfiberAngularVelocity + = tvCurve.calcDerivative(omegaNorm,1)*D_wn_w; + double D_tb_DfiberAngularVelocity = -betaUpd*D_wn_w; + + double D_fiberAngle_D_jointAngle = signOfJointAngle; + double D_tv_DfiberAngularVelocity_D_jointAngularVelocity = + signOfConcentricAnglularVelocity; + + + tmi.jointAngle = jointAngle; + tmi.jointAngularVelocity = jointAngularVelocity; + tmi.fiberAngle = fiberAngle; + //tmi.tendonAngle = 0.; + tmi.fiberAngularVelocity = fiberAngularVelocity; + //tmi.tendonAngularVelocity = 0.; + + tmi.fiberPassiveTorqueAngleMultiplier = tp; + tmi.fiberActiveTorqueAngleMultiplier = ta; + tmi.fiberActiveTorqueAngularVelocityMultiplier = tv; + //tmi.tendonTorqueAngleMultiplier = activation*ta*tv + tp; + + tmi.activation = activation; + tmi.fiberActiveTorque = maxActiveIsometricTorque*(activation*ta*tv); + tmi.fiberPassiveTorque = maxActiveIsometricTorque*(tb+tp); + tmi.fiberDampingTorque = maxActiveIsometricTorque*(tb); + tmi.fiberNormDampingTorque = tb; + tmi.fiberTorque = tmi.fiberActiveTorque + + tmi.fiberPassiveTorque; + //tmi.tendonTorque = tmi.fiberActiveTorque + // + tmi.fiberPassiveTorque; + + tmi.jointTorque = signOfJointTorque*tmi.fiberTorque; + + tmi.fiberStiffness = maxActiveIsometricTorque*( + activation*D_ta_DfiberAngle*tv + + D_tp_DfiberAngle); + + //tmi.tendonStiffness = std::numeric_limits::infinity(); + tmi.jointStiffness = signOfJointTorque + *tmi.fiberStiffness + *D_fiberAngle_D_jointAngle; + + tmi.fiberActivePower = tmi.fiberActiveTorque + * tmi.fiberAngularVelocity; + tmi.fiberPassivePower = tmi.fiberPassiveTorque + * tmi.fiberAngularVelocity; + tmi.fiberPower = tmi.fiberActivePower + + tmi.fiberPassivePower; + //tmi.tendonPower = 0.; + tmi.jointPower = tmi.jointTorque * jointAngularVelocity; + + + //tau = signTq*tauIso*(a * ta(theta) * tv(thetaDot) + tp(theta) ) + // Dtau_Da = tauMaxIso*(ta(theta) * tv(thetaDot) ) + tmi.DjointTorqueDactivation = + signOfJointTorque + *maxActiveIsometricTorque + *(ta * tv); + + //Dtau_Domega = signTq*tauIso*(a * ta(theta) * Dtv_DthetaDot(thetaDot)* ) + tmi.DjointTorqueDjointAngularVelocity = + signOfJointTorque + * maxActiveIsometricTorque + * ( activation + * ta + * D_tv_DfiberAngularVelocity + * D_tv_DfiberAngularVelocity_D_jointAngularVelocity + + D_tb_DfiberAngularVelocity); + + tmi.DjointTorqueDjointAngle = + signOfJointTorque + * maxActiveIsometricTorque + * ( activation + *D_ta_DfiberAngle + * tv + + D_tp_DfiberAngle + )* D_fiberAngle_D_jointAngle; + +} + + +/************************************************************* + Get / Set Functions +*************************************************************/ + +double Millard2016TorqueMuscle:: + getJointTorqueSign() const +{ + return signOfJointTorque; +} + +double Millard2016TorqueMuscle:: + getJointAngleSign() const +{ + return signOfJointAngle; +} + +double Millard2016TorqueMuscle:: + getJointAngleOffset() const +{ + return angleOffset; +} + + +double Millard2016TorqueMuscle:: + getNormalizedDampingCoefficient() const +{ + return beta; +} + +void Millard2016TorqueMuscle:: + setNormalizedDampingCoefficient(double betaUpd) +{ + if(betaUpd < 0){ + cerr << "Millard2016TorqueMuscle::" + << "setNormalizedDampingCoefficient:" + << muscleName + << "beta is " << betaUpd + << " but beta must be > 0 " + << endl; + assert(0); + abort(); + } + + + beta = betaUpd; +} + + + + +double Millard2016TorqueMuscle:: + getMaximumActiveIsometricTorque() const +{ + return maxActiveIsometricTorque; +} + +double Millard2016TorqueMuscle:: + getMaximumJointAngularVelocity() const +{ + return calcFiberAngularVelocity(omegaMax); +} + +void Millard2016TorqueMuscle:: + setMaximumActiveIsometricTorque(double maxIsoTorque) +{ + muscleCurvesAreDirty = true; + maxActiveIsometricTorque = maxIsoTorque; +} + + +double Millard2016TorqueMuscle:: + getJointAngleAtMaximumActiveIsometricTorque() const +{ + return calcJointAngle(angleAtOneNormActiveTorque); +} + +double Millard2016TorqueMuscle:: + getJointAngleAtOneNormalizedPassiveIsometricTorque() const +{ + if(muscleCurvesAreDirty){ + Millard2016TorqueMuscle* mutableThis = + const_cast(this); + mutableThis->updateTorqueMuscleCurves(); + } + return calcJointAngle(angleAtOneNormPassiveTorque); +} + + +double Millard2016TorqueMuscle:: + getPassiveTorqueScale() const +{ + return passiveTorqueScale; +} + +void Millard2016TorqueMuscle:: + setPassiveTorqueScale(double passiveTorqueScaling) +{ + muscleCurvesAreDirty = true; + passiveTorqueScale = passiveTorqueScaling; +} + + +double Millard2016TorqueMuscle:: + getPassiveCurveAngleOffset() const +{ + return passiveCurveAngleOffset; +} + +void Millard2016TorqueMuscle:: + setPassiveCurveAngleOffset(double passiveCurveAngleOffsetVal) +{ + muscleCurvesAreDirty = true; + passiveCurveAngleOffset = passiveCurveAngleOffsetVal; +} + + +void Millard2016TorqueMuscle:: + fitPassiveCurveAngleOffset(double jointAngleTarget, + double passiveTorqueTarget) +{ + muscleCurvesAreDirty = true; + setPassiveCurveAngleOffset(0.0); + + if(passiveTorqueTarget < SQRTEPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "fitPassiveTorqueScale:" + << muscleName + << ": passiveTorque " << passiveTorqueTarget + << " but it should be greater than sqrt(eps)" + << endl; + assert(0); + abort(); + } + + //Solve for the fiber angle at which the curve develops + //the desired passiveTorque + double normPassiveTorque = passiveTorqueTarget + /maxActiveIsometricTorque; + //Ge a good initial guess + + VectorNd curveDomain = tpCurve.getCurveDomain(); + double angleRange = abs( curveDomain[1]-curveDomain[0]); + double fiberAngle = 0.5*(curveDomain[0]+curveDomain[1]); + double jointAngleCurr = calcJointAngle(fiberAngle); + + TorqueMuscleInfo tqInfo = TorqueMuscleInfo(); + + calcTorqueMuscleInfo(jointAngleCurr,0.,0.,tqInfo); + double err = tqInfo.fiberPassiveTorqueAngleMultiplier-normPassiveTorque; + double jointAngleLeft = 0; + double jointAngleRight = 0; + double errLeft = 0; + double errRight = 0; + + double h = 0.25*angleRange; + + //Get a good initial guess - necessary because these curves + //can be *very* nonlinear. + int iter = 0; + int iterMax = 10; + int tol = sqrt(SQRTEPSILON); + + while(iter < iterMax && abs(err) > tol){ + jointAngleLeft = jointAngleCurr-h; + jointAngleRight = jointAngleCurr+h; + + calcTorqueMuscleInfo(jointAngleLeft,0.,0.,tqInfo); + errLeft = tqInfo.fiberPassiveTorqueAngleMultiplier + - normPassiveTorque; + + calcTorqueMuscleInfo(jointAngleRight,0.,0.,tqInfo); + errRight = tqInfo.fiberPassiveTorqueAngleMultiplier + - normPassiveTorque; + + if(abs(errLeft) SQRTEPSILON && iter < iterMax){ + calcTorqueMuscleInfo(jointAngleCurr,0.,0.,tqInfo); + err = tqInfo.fiberPassiveTorqueAngleMultiplier + -normPassiveTorque; + derr = signOfJointTorque*tqInfo.DjointTorqueDjointAngle + / maxActiveIsometricTorque ; + delta = -err/derr; + jointAngleCurr += delta; + ++iter; + } + + if(abs(err)>SQRTEPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "fitPassiveCurveAngleOffset:" + << muscleName + << ": failed to fit the passive curve offset " + << " such that the curve develops the desired " + << " passive torque at the given joint angle. This" + << " should not be possible - contact the maintainer " + << " of this addon." + << endl; + assert(0); + abort(); + } + double fiberAngleOffset = calcFiberAngle(jointAngleTarget) + - calcFiberAngle(jointAngleCurr); + + setPassiveCurveAngleOffset(fiberAngleOffset); + +} + +void Millard2016TorqueMuscle:: + fitPassiveTorqueScale(double jointAngleTarget, + double passiveTorqueTarget) +{ + muscleCurvesAreDirty = true; + setPassiveTorqueScale(1.0); + + VectorNd curveDomain = tpCurve.getCurveDomain(); + VectorNd curveRange = VectorNd(2); + curveRange[0] = tpCurve.calcValue(curveDomain[0]); + curveRange[1] = tpCurve.calcValue(curveDomain[1]); + double fiberAngle = calcFiberAngle(jointAngleTarget); + + if( (fiberAngle < curveDomain[0] && (curveRange[0] < curveRange[1])) + || (fiberAngle > curveDomain[1] && (curveRange[1] < curveRange[0])) ){ + cerr << "Millard2016TorqueMuscle::" + << "fitPassiveTorqueScale:" + << muscleName + << ": joint angle is " << jointAngleTarget + << " but it should be between " + << calcJointAngle(curveDomain[0]) << " and " + << calcJointAngle(curveDomain[1]) + << endl; + assert(0); + abort(); + } + + if(passiveTorqueTarget < SQRTEPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "fitPassiveTorqueScale:" + << muscleName + << ": passiveTorque " << passiveTorqueTarget + << " but it should be greater than sqrt(eps)" + << endl; + assert(0); + abort(); + } + + double normPassiveTorque = passiveTorqueTarget/maxActiveIsometricTorque; + int iter = 0; + int iterMax = 10; + double err = SQRTEPSILON*2; + double derr = 0; + double delta= 0; + double scale = 1.0; + setPassiveTorqueScale(scale); + TorqueMuscleInfo tqInfo = TorqueMuscleInfo(); + + while(abs(err) > SQRTEPSILON && iter < iterMax){ + setPassiveTorqueScale(scale); + calcTorqueMuscleInfo(jointAngleTarget,0,0,tqInfo); + err = tqInfo.fiberPassiveTorqueAngleMultiplier - normPassiveTorque; + derr= tqInfo.fiberPassiveTorqueAngleMultiplier/scale; + delta = -err/derr; + scale += delta; + + if(scale < SQRTEPSILON){ + scale = SQRTEPSILON; + } + iter++; + } + + if(abs(err) > SQRTEPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "fitPassiveTorqueScale:" + << muscleName + << ": passiveTorqueScale could not be fit to" + << " the data given. See the maintainer of this" + << " addon for help." + << endl; + assert(0); + abort(); + } +} + +/* +const RigidBodyDynamics::Math::VectorNd& + Millard2016TorqueMuscle::getParametersc1c2c3c4c5c6() +{ + return c1c2c3c4c5c6Anderson2007; +} + +const RigidBodyDynamics::Math::VectorNd& + Millard2016TorqueMuscle::getParametersb1k1b2k2() +{ + return b1k1b2k2Anderson2007; +} +*/ + +const SmoothSegmentedFunction& Millard2016TorqueMuscle:: + getActiveTorqueAngleCurve() const +{ + //This must be updated if the parameters have changed + if(muscleCurvesAreDirty){ + Millard2016TorqueMuscle* mutableThis = + const_cast(this); + mutableThis->updateTorqueMuscleCurves(); + } + return taCurve; +} + +const SmoothSegmentedFunction& Millard2016TorqueMuscle:: +getPassiveTorqueAngleCurve() const +{ + //This must be updated if the parameters have changed + if(muscleCurvesAreDirty){ + Millard2016TorqueMuscle* mutableThis = + const_cast(this); + mutableThis->updateTorqueMuscleCurves(); + } + return tpCurve; +} + +const SmoothSegmentedFunction& Millard2016TorqueMuscle:: +getTorqueAngularVelocityCurve() const +{ + //This must be updated if the parameters have changed + if(muscleCurvesAreDirty){ + Millard2016TorqueMuscle* mutableThis = + const_cast(this); + mutableThis->updateTorqueMuscleCurves(); + } + return tvCurve; +} + + +string Millard2016TorqueMuscle::getName(){ + return muscleName; +} + +void Millard2016TorqueMuscle::setName(string &name){ + muscleCurvesAreDirty = true; + muscleName = name; +} + + + +/************************************************************* + Utilities +*************************************************************/ +//fa = signJointAngle*(ja - jaO) +// ja = signJointAngle*fa + ja0 +//dja = signJointAngle*dfa + +double Millard2016TorqueMuscle:: + calcFiberAngle(double jointAngle) const +{ + return signOfJointAngle*(jointAngle-angleOffset); +} + +double Millard2016TorqueMuscle:: + calcJointAngle(double fiberAngle) const +{ + return fiberAngle*signOfJointAngle + angleOffset; +} + + +double Millard2016TorqueMuscle:: + calcFiberAngularVelocity(double jointAngularVelocity) const +{ + return signOfConcentricAnglularVelocity*jointAngularVelocity; +} + +void Millard2016TorqueMuscle::updateTorqueMuscleCurves() +{ + std::string tempName = muscleName; + + switch(dataSet){ + case DataSet::Anderson2007: + { + double c4 = c1c2c3c4c5c6Anderson2007[3]; + double c5 = c1c2c3c4c5c6Anderson2007[4]; + omegaMax = abs( 2.0*c4*c5/(c5-3.0*c4) ); + + scaleFactorAnderson2007 = subjectHeightInMeters + *subjectMassInKg + *gravity; + maxActiveIsometricTorque = scaleFactorAnderson2007 + *c1c2c3c4c5c6Anderson2007[0]; + + angleAtOneNormActiveTorque = c1c2c3c4c5c6Anderson2007[2]; + + TorqueMuscleFunctionFactory:: + createAnderson2007ActiveTorqueAngleCurve( + c1c2c3c4c5c6Anderson2007[1], + c1c2c3c4c5c6Anderson2007[2], + tempName.append("_taCurve"), + taCurve); + + tempName = muscleName; + + TorqueMuscleFunctionFactory:: + createAnderson2007ActiveTorqueVelocityCurve( + c1c2c3c4c5c6Anderson2007[3], + c1c2c3c4c5c6Anderson2007[4], + c1c2c3c4c5c6Anderson2007[5], + 1.1, + 1.4, + tempName.append("_tvCurve"), + tvCurve); + + tempName = muscleName; + + double normMaxActiveIsometricTorque = maxActiveIsometricTorque + /scaleFactorAnderson2007; + + TorqueMuscleFunctionFactory:: + createAnderson2007PassiveTorqueAngleCurve( + scaleFactorAnderson2007, + normMaxActiveIsometricTorque, + b1k1b2k2Anderson2007[0], + b1k1b2k2Anderson2007[1], + b1k1b2k2Anderson2007[2], + b1k1b2k2Anderson2007[3], + tempName.append("_tpCurve"), + tpCurve); + + tpCurve.shift(passiveCurveAngleOffset,0); + + double k = 0; + double b = 0; + + if(b1k1b2k2Anderson2007[0] > 0){ + b = b1k1b2k2Anderson2007[0]; + k = b1k1b2k2Anderson2007[1]; + }else if(b1k1b2k2Anderson2007[2] > 0){ + b = b1k1b2k2Anderson2007[2]; + k = b1k1b2k2Anderson2007[3]; + } + + if(abs(b) > 0 && passiveTorqueScale > SQRTEPSILON){ + angleAtOneNormPassiveTorque = + (1/k)*log(abs(maxActiveIsometricTorque/b)) + + passiveCurveAngleOffset; + }else{ + angleAtOneNormPassiveTorque = + std::numeric_limits::signaling_NaN(); + } + + + //angleAtOneNormPassiveTorque + //gymnastParams[Gymnast::PassiveAngleAtOneNormTorque] + + + } break; + case DataSet::Gymnast: + { + omegaMax = gymnastParams[ + Gymnast::OmegaMax]; + maxActiveIsometricTorque = gymnastParams[ + Gymnast::TauMax]; + angleAtOneNormActiveTorque = gymnastParams[ + Gymnast::ActiveAngleAtOneNormTorque]; + + TorqueMuscleFunctionFactory:: + createGaussianShapedActiveTorqueAngleCurve( + gymnastParams[Gymnast::ActiveAngleAtOneNormTorque], + gymnastParams[Gymnast::ActiveAngularStandardDeviation], + tempName.append("_taCurve"), + taCurve); + + TorqueMuscleFunctionFactory::createPassiveTorqueAngleCurve( + gymnastParams[Gymnast::PassiveAngleAtZeroTorque], + gymnastParams[Gymnast::PassiveAngleAtOneNormTorque], + tempName.append("_tpCurve"), + tpCurve); + + tpCurve.shift(passiveCurveAngleOffset,0); + + TorqueMuscleFunctionFactory::createTorqueVelocityCurve( + gymnastParams[Gymnast::TvAtMaxEccentricVelocity], + gymnastParams[Gymnast::TvAtHalfMaxConcentricVelocity], + tempName.append("_tvCurve"), + tvCurve); + + if(passiveTorqueScale > 0){ + angleAtOneNormPassiveTorque = + gymnastParams[Gymnast::PassiveAngleAtOneNormTorque] + + passiveCurveAngleOffset; + }else{ + angleAtOneNormPassiveTorque = + std::numeric_limits::signaling_NaN(); + } + + } break; + default: + { + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << "dataSet has a value of " << dataSet + << " which is not a valid choice"; + assert(0); + abort(); + } + }; + + + + //If the passiveScale is < 1 and > 0, then we must iterate to + //find the true value of angleAtOneNormPassiveTorque; + + if(isfinite(angleAtOneNormPassiveTorque) + && (passiveTorqueScale > 0 && passiveTorqueScale != 1.0) ){ + int iter = 1; + int iterMax =100; + double err = 10*SQRTEPSILON; + double derr = 0; + double delta = 0; + + while(abs(err) > SQRTEPSILON && iter < iterMax){ + + err = passiveTorqueScale*tpCurve.calcValue(angleAtOneNormPassiveTorque) + - 1.0; + derr = passiveTorqueScale + *tpCurve.calcDerivative(angleAtOneNormPassiveTorque,1); + + delta = -err/derr; + angleAtOneNormPassiveTorque += delta; + ++iterMax; + } + + if(abs(err) > SQRTEPSILON){ + cerr << "Millard2016TorqueMuscle::" + << "Millard2016TorqueMuscle:" + << muscleName + << "Internal Error: failed to solve for " + <<"angleAtOneNormPassiveTorque. Consult the maintainer of this " + <<"addon"; + assert(0); + abort(); + } + + } + + + + + muscleCurvesAreDirty = false; +} + + + +void Millard2016TorqueMuscle::printJointTorqueProfileToFile( + const std::string& path, + const std::string& fileNameWithoutExtension, + int numberOfSamplePoints) +{ + if(muscleCurvesAreDirty){ + updateTorqueMuscleCurves(); + } + + VectorNd activeDomain = taCurve.getCurveDomain(); + VectorNd passiveDomain = tpCurve.getCurveDomain(); + VectorNd velocityDomain= tvCurve.getCurveDomain(); + + double angleMin = activeDomain[0]; + double angleMax = activeDomain[1]; + + if(tpCurve.calcValue(passiveDomain[0]) >= 0.99){ + angleMin = passiveDomain[0]; + } + + if(tpCurve.calcValue(passiveDomain[1]) >= 0.99){ + angleMax = passiveDomain[1]; + } + + double jointMin = signOfJointAngle*angleMin + angleOffset; + double jointMax = signOfJointAngle*angleMax + angleOffset; + + if(jointMin > jointMax){ + double tmp = jointMin; + jointMin=jointMax; + jointMax=tmp; + } + double range = jointMax-jointMin; + jointMin = jointMin -range*0.1; + jointMax = jointMax +range*0.1; + double jointDelta = (jointMax-jointMin) + /((double)numberOfSamplePoints-1.); + + double velMin = omegaMax*signOfConcentricAnglularVelocity*velocityDomain[0]; + double velMax = omegaMax*signOfConcentricAnglularVelocity*velocityDomain[1]; + + if(velMin > velMax){ + double tmp = velMin; + velMin = velMax; + velMax = tmp; + } + double velRange = velMax-velMin; + velMin = velMin-0.1*velRange; + velMax = velMax+0.1*velRange; + double velDelta = (velMax-velMin)/((double)numberOfSamplePoints-1.0); + + double angleAtMaxIsoTorque = angleAtOneNormActiveTorque; + + std::vector< std::vector < double > > matrix; + std::vector < double > row(21); + std::string header("jointAngle," + "jointVelocity," + "activation," + "fiberAngle," + "fiberAngularVelocity," + "passiveTorqueAngleMultiplier," + "activeTorqueAngleMultiplier," + "torqueVelocityMultiplier," + "activeTorque," + "passiveTorque," + "fiberTorque," + "jointTorque," + "fiberStiffness," + "jointStiffness," + "fiberActivePower," + "fiberPassivePower," + "fiberPower," + "jointPower," + "DjointTorqueDactivation," + "DjointTorqueDjointAngularVelocity," + "DjointTorqueDjointAngle"); + + double activation =1.0; + double jointAngle = 0.; + double jointVelocity = 0.; + + + for(int i=0; i + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include +#include "../geometry/SmoothSegmentedFunction.h" + + +namespace RigidBodyDynamics { + namespace Addons { + namespace Muscle{ + + /** + This struct contains an enumerated list of the data sets which + constain torque muscles. Thus far this list includes + + -Anderson2007: Table 3 from Anderson et al. + -Gymnast: A set of torque muscles for the whole body (in progress) + + For details of these data sets please refer to the main description + of this class. + + */ + const static struct DataSet{ + enum item{ + Anderson2007 = 0, + Gymnast, + Last + }; + const static char* names[]; + DataSet(){} + } DataSet; + + + /** + This struct contains an enumerated list of the genders for which + data torque data has been reported. + */ + const static struct GenderSet{ + enum item { + Male = 0, + Female, + Last + }; + const static char* names[]; + GenderSet(){} + } GenderSet; + + /** + This struct contains an enumerated list of the age groups for which + data torque data has been reported. + */ + const static struct AgeGroupSet{ + enum item { + Young18To25 = 0, + Middle55To65, + SeniorOver65, + Last + }; + const static char* names[]; + AgeGroupSet(){} + } AgeGroupSet; + + /** + This struct contains an enumerated list of the joint-torque-directions + for which data torque data has been reported. + */ + const static struct JointTorqueSet{ + enum item{ + HipExtension = 0, + HipFlexion = 1, + KneeExtension = 2, + KneeFlexion = 3, + AnkleExtension = 4, + AnkleFlexion = 5, + ElbowExtension = 6, + ElbowFlexion = 7, + ShoulderExtension = 8, + ShoulderFlexion = 9, + WristExtension = 10, + WristFlexion = 11, + ShoulderHorizontalAdduction = 12, + ShoulderHorizontalAbduction = 13, + ShoulderInternalRotation = 14, + ShoulderExternalRotation = 15, + WristUlnarDeviation = 16, + WristRadialDeviation = 17, + WristPronation = 18, + WristSupination = 19, + LumbarExtension = 20, + LumbarFlexion = 21, + Last = 22 + }; + const static char* names[]; + JointTorqueSet(){} + } JointTorqueSet; + + /** + This struct contains 3 enumerated lists (Gender, AgeGroup, + JointTorque) that lists the genders, age groups, and + joint-torque-directions for which the Anderson2007 data set contains + data. Please refer to the class description for more details about + this data set and how to use it. + */ + const static struct Anderson2007{ + enum Gender { + Male = GenderSet::Male, + Female = GenderSet::Female, + LastGender + }; + enum AgeGroup { + Young18To25 = AgeGroupSet::Young18To25, + Middle55To65 = AgeGroupSet::Middle55To65, + SeniorOver65 = AgeGroupSet::SeniorOver65, + LastAgeGroup + }; + enum JointTorque{ + HipExtension = JointTorqueSet::HipExtension , + HipFlexion = JointTorqueSet::HipFlexion , + KneeExtension = JointTorqueSet::KneeExtension , + KneeFlexion = JointTorqueSet::KneeFlexion , + AnkleExtension = JointTorqueSet::AnkleExtension, + AnkleFlexion = JointTorqueSet::AnkleFlexion, + LastJointTorque + }; + const static char* GenderNames[]; + const static char* AgeGroupNames[]; + const static char* JointTorqueNames[]; + Anderson2007(){} + } Anderson2007; + + /** + This struct contains 3 enumerated lists (Gender, AgeGroup, + JointTorque) that lists the genders, age groups, and + joint-torque-directions for which the Gymnast data set contains + data. At the present time Gymnast data set only contains data + appropriate for a young (18-25) elite male gymnast. Please refer + to the main class description for details on this data set and how + to use it. + */ + const static struct Gymnast{ + enum Gender { + Male = GenderSet::Male, + LastGender + }; + enum AgeGroup { + Young18To25 = AgeGroupSet::Young18To25, + LastAgeGroup + }; + + enum TableIndex { + TauMax = 0, + OmegaMax, + ActiveAngleAtOneNormTorque, + ActiveAngularStandardDeviation, + TvAtMaxEccentricVelocity, + TvAtHalfMaxConcentricVelocity, + PassiveAngleAtZeroTorque, + PassiveAngleAtOneNormTorque, + LastTableIndex + }; + + enum JointTorque { + HipExtension = JointTorqueSet::HipExtension , + HipFlexion = JointTorqueSet::HipFlexion , + KneeExtension = JointTorqueSet::KneeExtension , + KneeFlexion = JointTorqueSet::KneeFlexion , + AnkleExtension = JointTorqueSet::AnkleExtension , + AnkleFlexion = JointTorqueSet::AnkleFlexion , + ElbowExtension = JointTorqueSet::ElbowExtension , + ElbowFlexion = JointTorqueSet::ElbowFlexion , + ShoulderExtension = JointTorqueSet::ShoulderExtension , + ShoulderFlexion = JointTorqueSet::ShoulderFlexion , + WristExtension = JointTorqueSet::WristExtension , + WristFlexion = JointTorqueSet::WristFlexion , + ShoulderHorizontalAdduction = + JointTorqueSet::ShoulderHorizontalAdduction, + ShoulderHorizontalAbduction = + JointTorqueSet::ShoulderHorizontalAbduction, + ShoulderInternalRotation = + JointTorqueSet::ShoulderInternalRotation , + ShoulderExternalRotation = + JointTorqueSet::ShoulderExternalRotation , + WristUlnarDeviation = JointTorqueSet::WristUlnarDeviation , + WristRadialDeviation = JointTorqueSet::WristRadialDeviation, + WristPronation = JointTorqueSet::WristPronation , + WristSupination = JointTorqueSet::WristSupination , + LumbarExtension = JointTorqueSet::LumbarExtension, + LumbarFlexion = JointTorqueSet::LumbarFlexion, + LastJointTorque + }; + const static char* GenderNames[]; + const static char* AgeGroupNames[]; + const static char* JointTorqueNames[]; + Gymnast(){} + } Gymnast; + + + + /** + This is a struct that contains subject-specific information that + does not change for a given subject. + + @param gender + Male/Female. Selecting a gender that + is not present in a data set will result in + the program aborting and an error message + being printed to the terminal. + + @param ageGroup: + Presently the age group options include Young (18-25), + middle aged (55-65), and senior (>65). Selecting an + age group that is not present in a data set will result + in the program aborting and an error message + being printed to the terminal. + + @param subjectHeightInMeters + This parameter is used to scale from the normalized + curves reported by Anderson et al. + See the class description for details. + + @param subjectMassInKg + This parameter is used to scale from the normalized + curves reported by Anderson et al. + See the class description for details. + */ + struct SubjectInformation{ + GenderSet::item gender; + AgeGroupSet::item ageGroup; + double heightInMeters; + double massInKg; + }; + + + + struct TorqueMuscleInfo{ + + /**The angle of the joint (radians)*/ + double jointAngle; + + /**The angular velocity of the joint, where the sign convention + is chosen by the user at the time the torque muscle is created + (radians/sec)*/ + double jointAngularVelocity; + + /**The angle that the muscle fiber spans (radians)*/ + double fiberAngle; + + //The angle that the tendon spans (radians) + //double tendonAngle; + + /**The rate-of-angular-lengthening of the fiber. + A positive sign is for a concentric contraction, + that is where the fibers are shortening. (radians/sec)*/ + double fiberAngularVelocity; + + /*The rate-of-angular-lengthening of the tendon. + A positive sign is for a concentric contraction, + that is where the tendon is shortening. (radians/sec)*/ + //double tendonAngularVelocity; + + /** The normalized value of the passive-torque-angle curve. + Here a value of 1 means 1 maximum-isometric-torque. (Nm/Nm)*/ + double fiberPassiveTorqueAngleMultiplier; + + /**The normalized value of the active-torque-angle curve. + Here a value of 1 means 1 maximum-isometric-torque. (Nm/Nm)*/ + double fiberActiveTorqueAngleMultiplier; + + /**The normalized value of the torque-angular-velocity curve. + Here a value of 1 means 1 maximum-isometric-torque. (Nm/Nm)*/ + double fiberActiveTorqueAngularVelocityMultiplier; + + /*The normalized value of the tendon-torque-angle curve. + Here a value of 1 means 1 maximum-isometric-torque. (Nm/Nm)*/ + //double tendonTorqueAngleMultiplier; + + /**The activation of the muscle*/ + double activation; + + /**The torque generated by the active element of the muscle + fiber (Nm)*/ + double fiberActiveTorque; + + /**The torque generated by the passive element of the muscle + fiber (Nm)*/ + double fiberPassiveTorque; + + /**The torque generated by the damping element (Nm)*/ + double fiberDampingTorque; + + /**The torque generated by the damping element (Nm)*/ + double fiberNormDampingTorque; + + /**The torque generated by the entire fiber (Nm)*/ + double fiberTorque; + + /*The torque transmitted through the tendon across the joint (Nm)*/ + //double tendonTorque; + + /**The joint torque developed by the muscle. This is signed so + that it is consistent with the sign convention of the joint + chosen by the user. (Nm)*/ + double jointTorque; + + /**The stiffness of the fiber (Nm/rad)*/ + double fiberStiffness; + + /*The stiffness of the tendon (Nm/rad)*/ + //double tendonStiffness; + + /** The stiffness of the joint. This is signed so + that it is consistent with the sign convention of the joint + chosen by the user. (Nm/rad)*/ + double jointStiffness; + + /**The power output of the active fiber element. A positive + power means that the fiber is contracting concentrically. + (Watts - Nm/s)*/ + double fiberActivePower; + + /**The power output of the passive fiber element. A positive + power means that the passive element is recoiling concentrically + (Watts - Nm/s)*/ + double fiberPassivePower; + + /**The total power output of the fiber element.(Watts - Nm/s)*/ + double fiberPower; + + /**The partial derivative of joint torque w.r.t the joint angle*/ + double DjointTorqueDjointAngle; + + /**The partial derivative of joint torque w.r.t the joint + angular velocity*/ + double DjointTorqueDjointAngularVelocity; + + /**The partial derivative of joint torque w.r.t activation*/ + double DjointTorqueDactivation; + + /*The power output of the tendon. A positive power means that + the tendon is physically shortening. (Watts - Nm/s)*/ + //double tendonPower; + + /** The power output by this muscle at the joint. This is + signed so that it is consistent with the sign convention of the + joint chosen by the user. (Nm/rad)*/ + double jointPower; + + TorqueMuscleInfo(): + jointAngle(nan("1")), + jointAngularVelocity(nan("1")), + fiberAngle(nan("1")), + fiberAngularVelocity(nan("1")), + fiberPassiveTorqueAngleMultiplier(nan("1")), + fiberActiveTorqueAngleMultiplier(nan("1")), + fiberActiveTorqueAngularVelocityMultiplier(nan("1")), + activation(nan("1")), + fiberActiveTorque(nan("1")), + fiberPassiveTorque(nan("1")), + fiberTorque(nan("1")), + jointTorque(nan("1")), + fiberStiffness(nan("1")), + jointStiffness(nan("1")), + fiberActivePower(nan("1")), + fiberPassivePower(nan("1")), + fiberPower(nan("1")), + jointPower(nan("1")){} + + }; + + /** + This class implements a rigid-tendon torque muscle for a growing + list of joints and torque-directions. This rigid-tendon torque + muscle model provides modeling support for 3 phenomena + + -torque-angle curve (\f$\mathbf{t}_A(\theta)\f$): the variation of active isometric torque in one direction as a function of joint angle + -torque-velocity curve (\f$\mathbf{t}_V(\dot{\theta})\f$): the variation of torque as a function of angular velocity + -passive-torque-angle curve (\f$s_P\mathbf{t}_P(\theta-\theta_S)\f$): the variation of passive torque as a function of joint angle. Here \f$s_P\f$ and \f$\theta_S\f$ are user-defined scaling and shift parameters. + + each of which are represented as smooth normalized curves that + vary between 0 and 1. These three phenomena are used to compute the + torque developed \f$\tau\f$ given the angle of the joint + \f$\theta\f$, the angular-velocity of the joint \f$\dot{\theta}\f$, + and the activation of the muscle \f$\mathbf{a}\f$ (a 0-1 quantity + that defines how much the muscle is turned-on, or activated), and + the maximum-isometric torque \f$\tau_{ISO}\f$ + \f[ + \tau (\mathbf{a}, \theta,\dot{\theta}) = + \begin{cases} + \tau_{ISO} ( \mathbf{a} \, \mathbf{t}_A(\theta) \mathbf{t}_V(\dot{\theta}/\dot{\theta}_{MAX}) + - \beta \dot{\theta}/\dot{\theta}_{MAX} + + s_P \mathbf{t}_P(\theta-\theta_S) \, ) & \text{ if } \,\, \tau (\mathbf{a}, \theta,\dot{\theta}) > 0 \\ + 0 & \text{otherwise} \end{cases} + \f] + The + This model does not yet provide support for the following phenomena + but will in the future. + + -activation dynamics: this is to be decided by the modeler. + -tendon-elasticity + -muscle short-range-stiffness + + All of these characteristic curves are represented using \f$C_2\f$ + continuous \f$5^{th}\f$ order Bezier curves that have been fitted to + the data from data in the literature. In many cases these + curves have been carefully edited so that they fit the curves of + the original papers, but have more desireable numerical properties + for optimal control work. The characterisic curves provided by this + class have been fitted to a growing list of data sets: + -Anderson Data Set: from Anderson et al. 2007 + -Whole-body Gymnast Data Set: from Jackson, Kentel et al., Anderson et al., Dolan et al. and Raschke et al. + + Data Set: Anderson2007 + + This data set uses the mean value of the coefficients published + in Anderson et al. The standard deviation table has also been + entered. However, since it is unclear how to use the standard + deviation in a consistent way across all joints/parameters this + table is not yet accessible through the constructor. This data + set includes coefficients for the following + + -Number of subjects: 34 + -Gender: male and female + -Age: young (18-25, 14 subjects), middle-aged (55-65, 14 subjects), senior (> 65, 6 subjects) + -Joint: hip/knee/ankle + -Direction: extension/flexion + + Notes + -# Angles are plotted using units of degrees for readability. The + actual curves are described in units of radians + -# See Anderson et al. for further details. + + \image html fig_MuscleAddon_Anderson2007AllPositiveSigns.png "Characteristic from Anderson et al. 2007 [1]" + + + Data Set: Gymnast + + This data set is an attempt at making enough torque muscles for a + whole body. Since no single source in the literature comes close to + measuring the characteristics of all of the joints, data from + Jackson et al., Kentel et al., Anderson et al., Dolan et al, and + Raschke et al. + have been combined. Since the subjects used in these various studies + are wildly different (Jackson et al. measured an elite male gymnast; + Kentel et al. measured an elite tennis player; Anderson et al. measured, + in the category of young male, a selection of active undergraduate + students, Dolan et al from 126 women and 23 men, and Raschke from 5 + male subjects) scaling has been used to make the strength of the subject + consistent. Scaling coefficients for the lower body, shoulders and + elbow, and forearm/wrist using measurements that overlapped between + datasets. Presently this data set includes curves for 22 joint and + direction specific torque muscles. + + - Number of subjects: 1 elite gymnast (69.6 kg, 1.732 m) + - Gender: male + - Age: 21 years old + - Joint and Directions available + -# Ankle: flexion/extension (scaled from Anderson) + -# Knee: flexion/extension (from Jackson) + -# Hip: flexion/extension (from Jackson) + -# Lumbar: active extension curves (\f$\mathbf{t}_A\f$ and \f$\mathbf{t}_P\f$) from Raschke et al. passive extension from Dolan et al. + -# Lumbar: active flexion \f$\tau_{ISO}\f$ from Beimborn et al. + -# Shoulder: flexion/extension (from Jackson) + -# Shoulder: horizontal adduction/abduction (from Kentel, scaled to Jackson's subject) + -# Shoulder: internal rotation/external rotation (from Kentel, scaled to Jackson's subject) + -# Elbow: flexion/extension (from Kentel, scaled to Jackson's subject) + -# Wrist: pronation/supination (from Kentel, scaled to Jackson's subject) + -# Wrist: extension/flextion (from Jackson) + -# Wrist: ulnar/radial deviation (from Kentel, scaled to Jackson's subject) + - Missing Joint and directions + -# Ankle inversion/eversion + -# Hip adduction/abduction + -# Hip internal rotation/external rotation + -# Lumbar extension/flexion + -# Lumbar bending + -# Lumbar twisting + -# Shoulder Adduction + -# Shoulder Abduction + -# Scapular elevation/depression + -# Scapular adduction/abduction + -# Scapular upward/downward rotation + + In all cases the curves have been fitted to Bezier curves that + are constructed using functions in TorqueMuscleFunctionFactory. + + Notes + -# Angles are plotted using units of degrees for readability. The actual curves are described in units of radians + -# Hip and Knee characteristics taken from Jackson. Ankle extension is from Anderson et al., scaled using Jackson-to-Anderson hip/knee strength ratios from Jackson ratios + -# Shoulder horizontal adduction/abduction and internal/external rotation is a scaled version of the Kentel. Strength was scaled using the Jackson-to-Kentel shoulder flex/ext ratios. + -# Elbow extension/flexion and forearm pronation/supination. Elbow strength scaled from Kentel using the ratio of maximum isometric shoulder ext/flextion between Kentel and Jackson. Forearm pronation/supination scaled using the maximum torque strength ratio of wrist extension/flextion between Kentel and Jackson + -# Wrist ext/flexion directly from Jackson, while the curves for ulnar and radial deviation have been scaled (using the maximum isometric torque ratios of wrist extension and flexion from both models) from Kentel et al. + -# Lumbar-extension active-torque-angle-curve, and torque-velocity-curve in extension comes from Raschke et al. + -# Lumbar-flexion \f$\tau_{ISO}\f$ comes from Beimborn et al.'s observation that the strength ratio of back extensors to flextors at a flextion angle of zero is most often repored as 1.3:1. The torque velocity curve is a guess (slower than back extesors due to the larger moment arm). + -# Any passive curve that is not accompanied by a curve from the literature (see the plots for details) is an educated guess. + + + \image html fig_MuscleAddon_Gymnast_HipKneeAnkle.png " Hip/Knee/Ankle: from Jackson and Anderson et al. " + \image html fig_MuscleAddon_Gymnast_Lumbar.png " Lumbar Extension/Flexion: from Dolan et al.,Raschke et al., and Beimborn et al." + \image html fig_MuscleAddon_Gymnast_Shoulder3Dof.png " Shoulder 3 DoF torques: from Jackson and Kentel et al. " + \image html fig_MuscleAddon_Gymnast_ElbowForearm.png " Elbow flexion/extension: from Kentel et al." + \image html fig_MuscleAddon_Gymnast_Wrist3Dof.png " Wrist 3 DoF torques: from Jackson and Kentel et al." + + + Parameterized Curves used here vs. Literature + + The curves used in this implementation are 2nd order 2-dimensional + Bezier curves. The curves described in Anderson et al., Jackson, + Kentel were not directly used because they are not continuous to the + second derivative (a requirement for most gradient based optimization + routines). There are some other detailed differences that might be + of interest: + + -# Anderson et al.'s torque-velocity curve tends to large + negative values for fast eccentric contractions. This is + in contrast to the literature which says that at large + eccentric contractions the torque-velocity curve (or the + force-velocity-curve) tends to a value between 1.0 and 1.4. + -# Anderson et al.'s torque-velcity curve for ankle extension + did not cross the x-axis on the concentric side of the curve. + This would endow the plantar flexors with super-human abilities. + This error has been corrected by fitting a Bezier curve to a + Hill-type curve that passes through the point where + \f$\dot{\theta}= \frac{1}{2} \dot{\theta}_{MAX}\f$ + -# Anderson et al.'s, Jackson, and Kentel et al. had discintinuities + in the first derivative of the force velocity curve at + \f$\dot{\theta}=0\f$. While this follows Huxley's famous observations + that the slope does discontinuously change at at \f$\dot{\theta}=0\f$. + This is not a phenomena that is not compatible with most optimal + control formulations and thus this discontinuity is not present in + the force velocity curves used in this model. + -# Anderson et al. and Kentel et al.'s active-torque-angle curves + can achieve negative values - this is obviously undesirable as it + will allow a muscle to push. + -# Kentel et al.'s activation inhibiition function does not always + cross 1.0 for \f$\dot{\theta}=0\f$, which means that \f$\tau_{ISO}\f$ + is not reached. This makes for a confusing model to use. + + + Coordinate Mapping + + Every author chose a particular convention for measuring + the angles of the hip, knee, ankle joint, shoulder, elbow, wrist and + lumbar --- see the figure for details. These conventions have all + been mapped to the one used in the illustrations. You will need to + use the figure, your model, and the constructors appropriately + so that + + -# the joint angle of your model is correctly mapped to the + fiber angle of the Millard2016TorqueMuscle; + -# the sign of the muscle's output torque matches the + sign associated with your model. + + To map from your model's joint coordinates to the joint coordines + used in this model (see the figure in the description) + the followinq equation is used at the torque level + + \f$ jointTorque = signOfJointTorque*fiberTorque \f$ + + where fiberTorque is the torque produced by Anderson et al.'s curves, + which is always positive. At the position level, the angles from your + models joint angle to Anderson et al.'s joint angle (called fiberAngle) + are mapped using + + \f$ fiberAngle = signOfJointAngleRelativeToAnderson2007*(jointAngle-jointAngleOffsetRelativeToAnderson2007). \f$ + + Internally the sign of the fiber velocity follows signOfJointTorque so + that the signs of joint power and muscle power are consistent. + + Strength Scaling + + The leg strength (here we mean \f$\tau_{ISO}\f$) + predicted by Anderson et al.'s curves should be taken + as a good first approximation. While Anderson et al.'s data set is + the most comprehensive in the literature, they only measured torques + from active people: they did not include people at the extremes + (both very weak, and very strong), nor did they include children. + Finally, the torques produced by each subject were normalized by + subjectMassInKg*subjectHeightInM*accelerationDueToGravity. Strength + is a strange phenomena which is not nicely normalized by just these + quantites, and so the strength predicted by Anderson et al.'s curves + might not fit your subject even if they are represented in Anderson + et al.'s data set. + + The strength used in the Gymnast data set is fitted to an elite + male gymnast. It goes without saying that an elite gymnast has + strength proportions, and an absolute strength that are not typical. + In the future it would be nice to have a function that could provide + an educated guess about how to map Gymnast's strengths to that of + another subject. For the moment I have no idea how to do this, nor + am I aware of any works in the literature that can provide insight + of how to do this. For now the whole-body Gymnast model should be + viewed as being a representation of what is possible for a human, + but not a typical human. At the present time the default strength + settings of the Gymnast are not scaled by subject height, nor + weight. + + If you happen to know the maximum-isometric-active-torque (note this + does not include the passive component) that your subject can + produce,you can update the strength of the torque-muscle using the + functions getMaximumActiveIsometricTorque(), and + setMaximumActiveIsometricTorque(). + + Limitations + + This rigid-tendon torque muscle has some limitations that you should + be aware of: + + -# There are no elastic tendons. That means that the mapping between + the mechanical work done by this torque actuator will greatly differ + from the positive mechanical work done by a torque actuator that + includes an elastic tendon. This difference is greatest for those + muscles with long tendons - namely the Achilles tendon. If you are + interested in fiber kinematics, fiber work, or metabolic energy + consumption you cannot use this model especially for muscles that + have long tendons. + -# This model formulation predicts torque well, but does a poor job + of predicting joint stiffness. In this model stiffness is given + by the partial derivative of torque w.r.t. joint angle. Since the + active-torque-angle curve fits a cosine function, it is possible + to construct a torque muscle that has a region of physically + impossible negative stiffness. Real muscle, in constrast, always + has a positive stiffness even on the descending limb of the + active-torque-angle curve (see Rassier et al. for details). + -# Muscles that cross 2 joints (e.g. the hamstrings) produce coupled + torques at both of those joints. In this model there is no coupling + between joints. Furthermore, because of the lack of coupling the + curves used here are only valid for the posture that Anderson et al., + Jackson, and Kentel et al. + used when they made their data collection. If you are interested + in simulating postures that are very different from those described + in by these authors then the results produced by this model should + be treated as very rough. + -# Because this is a joint-torque muscle, none of the joint contact + forces predicted will come close to matching what is produced by + line-type muscles. If you are interested in joint-contact forces you + cannot use this model. + + This simple model is a fast approximate means to constrain the joint + torque developed in the body to something that is physiologically + possible. That is it. + + Units + Although the figure in this description has angles in units + of degrees, this is only to help intuitition: when using + the model, use radians. This model uses MKS: + + -Distance: m + -Angles: radians + -Angular velocity: radians/s + -Mass: kg + -Torque: Nm + -Time: second + -Power: Nm/second + + + References + + -# Anderson, D. E., Madigan, M. L., & Nussbaum, M. A. (2007). + Maximum voluntary joint torque as a function of joint angle + and angular velocity: model development and application to + the lower limb. Journal of biomechanics, 40(14), 3105-3113. + + -# Beimborn, D. S., & Morrissey, M. C. (1988). A review of the + literature related to trunk muscle performance. Spine, 13(6), 655-660. + + + -# Dolan, P., A. F. Mannion, and M. A. Adams. Passive tissues help + the back muscles to generate extensor moments during lifting. + Journal of Biomechanics 27, no. 8 (1994): 1077-1085. + + + -# Jackson, M.I. (2010). The mechanics of the Table Contact + Phase of Gymnastics Vaulting. Doctoral Thesis, Loughborough + University. + + -# Kentel, B.B., King, M.A., & Mitchell, S.R. (2011). + Evaluation of a subject-specific torque-driven computer simulation + model of one-handed tennis backhand ground strokes. Journal of + Applied Biomechanics, 27(4),345-354. + + -# Millard, M., Uchida, T., Seth, A., & Delp, S. L. (2013). + Flexing computational muscle: modeling and simulation of + musculotendon dynamics. Journal of biomechanical engineering, + 135(2), 021005. + + -# Raschke, U., & Chaffin, D. B. (1996). Support for a linear + length-tension relation of the torso extensor muscles: an + investigation of the length and velocity EMG-force relationships. + Journal of biomechanics, 29(12), 1597-1604. + + -# Rassier, D. E., Herzog, W., Wakeling, J., & Syme, D. A. (2003). + Stretch-induced, steady-state force enhancement in single + skeletal muscle fibers exceeds the isometric force at optimum + fiber length. Journal of biomechanics, 36(9), 1309-1316. + + + + */ + class Millard2016TorqueMuscle { + + public: + /** + Default constructor, which for the moment does nothing. + Calling any of the models functions after the default + construction will result in a runtime error. + */ + Millard2016TorqueMuscle(); + + + /** + This constructor allows you to easily access the large + table of built-in torque muscle coefficients to create + a torque muscle that best represents the joint of interest. + + Note: directions + This constructs a single joint-torque muscle: + it can only generate torque in one direction. If you want + to generate a torque in two directions, you need 2 torque + muscles. + + Note: signs and offsets + + All of the angles in these models are defined anatomically. + You will need to set a series of variables to correctly + map from your model's joint coordinates and sign conventions + to that of the models: + jointAngleOffsetRelativeToDoxygenFigures, + signOfJointAngleRelativeToDoxygenFigures, + signOfJointTorqueToDoxygenFigures. Also note that due to + the anatomical angle definitions some left and right handed + joints will require different signs. This will be true + for internal/external rotation at the shoulder, + horizontal adduction/abduction at the shoulder, ulnar/radial + deviation at the wrist, pronation/supination of the wrist, + and others as the list of directions grows. + + + @param dataSet + The desired source of joint torque + coefficients. Use the DataSet structure to choose + the desired data set (e.g. DataSet::Anderson2007, + or DataSet::Gymnast) + + @param subjectInfo + A struct that contains metadata about the subject + which is used to scale the maximum torque of the + torque muscle. + + @param jointTorque + Select the joint and torque direction of interest. + Use the struct for each data set to choose a + joint-torque-direction that is in the set (e.g. + Anderson2007::HipExtension, or + Gymnast::ShoulderHorizontalAdduction) + + @param jointAngleOffsetRelativeToDoxygenFigures + Offset angle between your model's joints and the + reference figures in class description. + + @param signOfJointAngleRelativeToDoxygenFigures + The sign convention that converts your model's joint + angles to the angles used in the reference figures. + + @param signOfJointTorqueToDoxygenFigures + The sign that maps fiberTorque from Anderson's model + (which is always positive) to the correctly signed + joint torque for your model. + + @param name + The name of the muscle. This is needed to do useful + things like provide error messages that are human + readable. + + @throws abort() when + -# The combination of dataSet, gender, joint, and jointDirection does not correspond to a valid entry + -# subjectHeightInMeters <= 0 + -# subjectMassInKg <= 0 + -# abs(signOfJointTorque)-1 > epsilon + + */ + Millard2016TorqueMuscle( + DataSet::item dataSet, + const SubjectInformation &subjectInfo, + int jointTorque, + double jointAngleOffsetRelativeToDoxygenFigures, + double signOfJointAngleRelativeToDoxygenFigures, + double signOfJointTorqueToDoxygenFigures, + const std::string& name + ); + + + /** + Calculates the joint torque developed by the + muscle. + + @param jointAngle (radians) + + @param jointAngularVelocity (radians/sec) + + @param activation: the percentage of the muscle that is + turned on [0-1]. This function allows activations to be + outside [0,1], because this is useful during the + intermediate solutions of an optimization run. However, + you must ensure after the fact that your activations + fall within a bound of [0,1]. + + @returns torque developed by the musce in (Nm). + */ + double calcJointTorque( + double jointAngle, + double jointAngularVelocity, + double activation) const; + + + /** + Calculates a large number of internal quantities of the + torque muscle ranging from the values of the muscle's + components, the stiffness of the muscle, and its power output. + + @param jointAngle (radians) + + @param jointAngularVelocity (radians/sec) + + @param activation: the percentage of the muscle that is + turned on [0-1]. This function allows activations to be + outside [0,1], because this is useful during the + intermediate solutions of an optimization run. However, + you must ensure after the fact that your activations + fall within a bound of [0,1]. + + @param torqueMuscleInfoStruct: A torque muscle struct + */ + void calcTorqueMuscleInfo( + double jointAngle, + double jointAngularVelocity, + double activation, + TorqueMuscleInfo& torqueMuscleInfoStruct) const; + + /** + @return the sign of the joint torque (+/- 1) + */ + double getJointTorqueSign() const; + + /** + @return the sign of the angle sign relative to the + figures in the class description (+/- 1) + */ + double getJointAngleSign() const; + + /** + @return the offset angle between the model's joint + and the figures in the class description (rad) + */ + double getJointAngleOffset() const; + + + /** + @return the maximum-active-isometric torque that this muscle + can produce in Nm. + */ + double getMaximumActiveIsometricTorque() const; + + /** + @return the joint angle at which the normalized + active-torque-angle curve peaks at its + maximum value of 1.0. Angle is in radians + */ + double getJointAngleAtMaximumActiveIsometricTorque() const; + + /** + @return the joint angle at which the normalized + passive-torque-angle curve reaches a value + of 1.0. If this curve never reaches a value + of 1.0 (because it is flat, or the + passiveTorqueScale has been set to 0) a value + of std::numeric_limits::signaling_NaN() + is returned. Use the std function isfinite to + test if a signaling_NaN has been returned. + Angle is in radians + */ + double getJointAngleAtOneNormalizedPassiveIsometricTorque() const; + + /** + @return the maximum concentric joint angular velocity + in radians. + */ + double getMaximumJointAngularVelocity() const; + + /** + @return the passive-torque-scale \f$s_P\f$ that is applied + to the passive-torque-curve. + */ + double getPassiveTorqueScale() const; + + /** + @return the angle \f$\theta_S\f$ that the passive curve has + been shifted (radians). + */ + double getPassiveCurveAngleOffset() const; + + /** + @return The normalized damping term \f$\beta\f$ term + in the torque muscle model. See class description + for details. + */ + double getNormalizedDampingCoefficient() const; + + /** + @param beta -t normalized damping term \f$\beta\f$ term + in the torque muscle model. See class description + for details. + @throw abort() if beta < 0 + */ + void setNormalizedDampingCoefficient(double beta); + /** + Sets the scaling of the passive-joint-torques. By default + this scale + is one. + + @param passiveTorqueScale + The scale \f$s_P\f$ applied to the + passive-joint-torque curve (unitless) + */ + void setPassiveTorqueScale(double passiveTorqueScale); + + /** + @param passiveCurveAngleOffsetVal the angle \f$\theta_S\f$ + that the passive curve should be shifted. Angles in radians + */ + void setPassiveCurveAngleOffset( + double passiveCurveAngleOffsetVal); + + + /** + This function iteratively solves for the passiveTorqueScale + so that at the specified jointAngle the passive curve + develops the specified passiveTorque + + @param jointAngle the target joint angle in radians + @param passiveTorque the target passive joint torque in Nm. + @throws abort if jointAngle is not in the domain of the curve + @throws abort if passiveTorque < sqrt(eps) + */ + void fitPassiveTorqueScale(double jointAngle, + double passiveTorque); + + /** + This function solves for the passive curve angle offset + so that at the specified jointAngle the passive curve + develops the specified passiveTorque + + @param jointAngle the target joint angle in radians + @param passiveTorque the target passive joint torque in Nm. + @throws abort if passiveTorque < sqrt(eps) + */ + void fitPassiveCurveAngleOffset(double jointAngle, + double passiveTorque); + + /** + Sets the strength of the muscle to match a desired value. + + @param maxIsometricTorque + The desired maximum-active-isometric torque of the + muscle (Nm) + + */ + void setMaximumActiveIsometricTorque( + double maxIsometricTorque); + + /** + @return the SmoothSegmentedFunction the has been fitted to + Anderson et al.'s passive torque angle curve. + */ + const RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& getActiveTorqueAngleCurve() const; + + /** + @return the SmoothSegmentedFunction the has been fitted to + Anderson et al.'s active torque angle curve. + */ + const RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& getPassiveTorqueAngleCurve() const; + + /** + @return the SmoothSegmentedFunction the has been fitted to + Anderson et al.'s torque velocity curve. + */ + const RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& getTorqueAngularVelocityCurve() const; + + + /** + Prints 2 csv files: + -# 'fileName' + '_variableLengthfixedVelocity': All of the + fields in TorqueMuscleInfo are recorded to file as the + jointAngle varies but the jointAngularVelocity is zero. + -#'fileName' + '_fixedLengthVariableVelocity': All of the fields + in TorqueMuscleInfo are recorded to file as the jointAngle is + fixed but the jointAngularVelocity varies. + + Each column has a header, so that you can tell what each + piece of data means. + + @param path: the path to the destination folder. Don't put + an '\' on the end. + @param fileNameWithoutExtension: the name of the file, but + without an extension. + @param numberOfSamplePoints: the number of sample points to + use in the files. + */ + void printJointTorqueProfileToFile( + const std::string& path, + const std::string& fileNameWithoutExtension, + int numberOfSamplePoints); + + std::string getName(); + void setName(std::string& name); + + private: + /** + @return the parameters c1,...,c6 that desribe the + active-torque-angle and torque-velocity curves of this + torque muscle model. See the Anderson et al. paper metioned + in the class description for detail. + */ + //const RigidBodyDynamics::Math::VectorNd& + //getParametersC1C2C3C4C5C6(); + + /** + @return the parameters b1,k1,b2,k2 that desribe the + passive-torque-angle curves of this model. See the Anderson + et al. paper metioned in the class description for detail. + */ + //const RigidBodyDynamics::Math::VectorNd& + //getParametersB1K1B2K2(); + + bool muscleCurvesAreDirty; + void updateTorqueMuscleCurves(); + TorqueMuscleInfo tmInfo; + + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction taCurve; + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction tpCurve; + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction tvCurve; + + RigidBodyDynamics::Math::VectorNd c1c2c3c4c5c6Anderson2007; + RigidBodyDynamics::Math::VectorNd b1k1b2k2Anderson2007; + RigidBodyDynamics::Math::VectorNd gymnastParams; + + DataSet::item dataSet; + + double maxActiveIsometricTorque; + double angleAtOneNormActiveTorque; + double omegaMax; + double angleAtOneNormPassiveTorque; + double passiveTorqueScale; + double passiveCurveAngleOffset; + + double beta; //passive damping coefficient + + double subjectHeightInMeters; + double subjectMassInKg; + double scaleFactorAnderson2007; + + double signOfJointAngle; + double signOfConcentricAnglularVelocity; + double signOfJointTorque; + double angleOffset; + + std::string muscleName; + + double calcJointAngle(double fiberAngle) const; + double calcFiberAngle(double jointAngle) const; + double calcFiberAngularVelocity( + double jointAngularVelocity) const; + + + + //const static RigidBodyDynamics::Math::MatrixNd& + //getAnderson2007ParameterMatrix(); + static double const Anderson2007Table3Mean[36][14]; + static double const Anderson2007Table3Std[36][14]; + static double const GymnastWholeBody[22][12]; + + }; + + + +} +} +} + +#endif diff --git a/3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.cc b/3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.cc new file mode 100644 index 0000000..8c09b36 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.cc @@ -0,0 +1,927 @@ +/* -------------------------------------------------------------------------- * + * OpenSim: SmoothSegmentedFunctionFactory.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ + /* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ + +//============================================================================= +// INCLUDES +//============================================================================= + +#include "MuscleFunctionFactory.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace RigidBodyDynamics::Addons::Muscle; +using namespace RigidBodyDynamics::Addons::Geometry; +//============================================================================= +// STATICS +//============================================================================= +//using namespace std; + + +static int NUM_SAMPLE_PTS = 100; //The number of knot points to use to sample + //each Bezier corner section + +static double SMOOTHING = 0; //The amount of smoothing to use when fitting + //3rd order splines to the quintic Bezier + //functions +static bool DEBUG = true; //When this is set to true, each function's debug + //routine will be called, which ususally results + //in a text file of its output being produced + +static double UTOL = (double)std::numeric_limits::epsilon()*1e2; + +static double INTTOL = (double)std::numeric_limits::epsilon()*1e4; + +static int MAXITER = 20; +//============================================================================= +// UTILITY FUNCTIONS +//============================================================================= + +//============================================================================= +// MUSCLE CURVE FITTING FUNCTIONS +//============================================================================= +void MuscleFunctionFactory::createFiberActiveForceLengthCurve( + double x0, + double x1, + double x2, + double x3, + double ylow, + double dydx, + double curviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Ensure that the inputs are within a valid range + double rootEPS = sqrt(std::numeric_limits::epsilon()); + + if( (!(x0>=0 && x1>x0+rootEPS && x2>x1+rootEPS && x3>x2+rootEPS) ) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberActiveForceLengthCurve: " + << curveName + << ": This must be true: 0 < lce0 < lce1 < lce2 < lce3" + << endl; + assert(0); + abort(); + + } + + + if( !(ylow >= 0) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberActiveForceLengthCurve:" + << curveName + <<": shoulderVal must be greater than, or equal to 0" + << endl; + assert(0); + abort(); + + } + + double dydxUpperBound = (1-ylow)/(x2-x1); + + + if( !(dydx >= 0 && dydx < dydxUpperBound) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberActiveForceLengthCurve:" + << curveName + << ": plateauSlope must be greater than 0 and less than " + << dydxUpperBound + << endl; + assert(0); + abort(); + } + + if( !(curviness >= 0 && curviness <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberActiveForceLengthCurve:" + << curveName + << ": curviness must be between 0 and 1" + << endl; + assert(0); + abort(); + } + + std::string name = curveName; + name.append(".createFiberActiveForceLengthCurve"); + + + + //Translate the users parameters into Bezier curves + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + + //The active force length curve is made up of 5 elbow shaped sections. + //Compute the locations of the joining point of each elbow section. + + //Calculate the location of the shoulder + double xDelta = 0.05*x2; //half the width of the sarcomere 0.0259, + //but TM.Winter's data has a wider shoulder than + //this + + double xs = (x2-xDelta);//x1 + 0.75*(x2-x1); + + //Calculate the intermediate points located on the ascending limb + double y0 = 0; + double dydx0 = 0; + + double y1 = 1 - dydx*(xs-x1); + double dydx01= 1.25*(y1-y0)/(x1-x0);//(y1-y0)/(x1-(x0+xDelta)); + + double x01 = x0 + 0.5*(x1-x0); //x0 + xDelta + 0.5*(x1-(x0+xDelta)); + double y01 = y0 + 0.5*(y1-y0); + + //Calculate the intermediate points of the shallow ascending plateau + double x1s = x1 + 0.5*(xs-x1); + double y1s = y1 + 0.5*(1-y1); + double dydx1s= dydx; + + //double dydx01c0 = 0.5*(y1s-y01)/(x1s-x01) + 0.5*(y01-y0)/(x01-x0); + //double dydx01c1 = 2*( (y1-y0)/(x1-x0)); + //double dydx01(1-c)*dydx01c0 + c*dydx01c1; + + //x2 entered + double y2 = 1; + double dydx2 = 0; + + //Descending limb + //x3 entered + double y3 = 0; + double dydx3 = 0; + + double x23 = (x2+xDelta) + 0.5*(x3-(x2+xDelta)); //x2 + 0.5*(x3-x2); + double y23 = y2 + 0.5*(y3-y2); + + //double dydx23c0 = 0.5*((y23-y2)/(x23-x2)) + 0.5*((y3-y23)/(x3-x23)); + //double dydx23c1 = 2*(y3-y2)/(x3-x2); + double dydx23 = (y3-y2)/((x3-xDelta)-(x2+xDelta)); + //(1-c)*dydx23c0 + c*dydx23c1; + + //Compute the locations of the control points + RigidBodyDynamics::Math::MatrixNd p0 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,ylow,dydx0,x01,y01,dydx01,c); + RigidBodyDynamics::Math::MatrixNd p1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x01,y01,dydx01,x1s,y1s,dydx1s,c); + RigidBodyDynamics::Math::MatrixNd p2 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x1s,y1s,dydx1s,x2, y2, dydx2,c); + RigidBodyDynamics::Math::MatrixNd p3 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x2, y2, dydx2,x23,y23,dydx23,c); + RigidBodyDynamics::Math::MatrixNd p4 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x23,y23,dydx23,x3,ylow,dydx3,c); + + RigidBodyDynamics::Math::MatrixNd mX(6,5), mY(6,5); + mX.col(0) = p0.col(0); + mX.col(1) = p1.col(0); + mX.col(2) = p2.col(0); + mX.col(3) = p3.col(0); + mX.col(4) = p4.col(0); + + mY.col(0) = p0.col(1); + mY.col(1) = p1.col(1); + mY.col(2) = p2.col(1); + mY.col(3) = p3.col(1); + mY.col(4) = p4.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX,mY,x0,x3,ylow,ylow,0,0,curveName); +} + +void MuscleFunctionFactory::createFiberForceVelocityCurve( + double fmaxE, + double dydxC, + double dydxNearC, + double dydxIso, + double dydxE, + double dydxNearE, + double concCurviness, + double eccCurviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Ensure that the inputs are within a valid range + + if( !(fmaxE > 1.0) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve: " + << curveName + <<": fmaxE must be greater than 1" + << endl; + assert(0); + abort(); + } + + if( !(dydxC >= 0.0 && dydxC < 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve: " + << curveName + << ": dydxC must be greater than or equal to 0 " + <<" and less than 1" + << endl; + assert(0); + abort(); + } + + if( !(dydxNearC > dydxC && dydxNearC <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve: " + << curveName + << ": dydxNearC must be greater than or equal to 0 " + << "and less than 1" + << endl; + assert(0); + abort(); + } + + if( !(dydxIso > 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve: " + << curveName + << ": dydxIso must be greater than (fmaxE-1)/1 (" + << ((fmaxE-1.0)/1.0) + << ")" + << endl; + assert(0); + abort(); + } + + if( !(dydxE >= 0.0 && dydxE < (fmaxE-1)) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve: " + << curveName + <<": dydxE must be greater than or equal to 0 " + << "and less than fmaxE-1 (" + << (fmaxE-1) << ")" + << endl; + assert(0); + abort(); + } + + if(!(dydxNearE >= dydxE && dydxNearE < (fmaxE-1))){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve" + << curveName + << ": dydxNearE must be greater than or equal to dydxE " + << "and less than fmaxE-1 (" << (fmaxE-1) + << ")" + << endl; + assert(0); + abort(); + } + + if(! (concCurviness <= 1.0 && concCurviness >= 0)){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve " + << curveName + << ": concCurviness must be between 0 and 1" + << endl; + assert(0); + abort(); + } + + if(! (eccCurviness <= 1.0 && eccCurviness >= 0)){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityCurve " + << curveName + << ": eccCurviness must be between 0 and 1" + << endl; + assert(0); + abort(); + } + + std::string name = curveName; + name.append(".createFiberForceVelocityCurve"); + + //Translate the users parameters into Bezier point locations + double cC = SegmentedQuinticBezierToolkit::scaleCurviness(concCurviness); + double cE = SegmentedQuinticBezierToolkit::scaleCurviness(eccCurviness); + + //Compute the concentric control point locations + double xC = -1; + double yC = 0; + + double xNearC = -0.9; + double yNearC = yC + 0.5*dydxNearC*(xNearC-xC) + 0.5*dydxC*(xNearC-xC); + + double xIso = 0; + double yIso = 1; + + double xE = 1; + double yE = fmaxE; + + double xNearE = 0.9; + double yNearE = yE + 0.5*dydxNearE*(xNearE-xE) + 0.5*dydxE*(xNearE-xE); + + + RigidBodyDynamics::Math::MatrixNd concPts1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( xC, yC, dydxC, + xNearC, yNearC,dydxNearC,cC); + RigidBodyDynamics::Math::MatrixNd concPts2 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xNearC,yNearC,dydxNearC, + xIso, yIso, dydxIso, cC); + RigidBodyDynamics::Math::MatrixNd eccPts1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( xIso, yIso, dydxIso, + xNearE, yNearE, dydxNearE, cE); + RigidBodyDynamics::Math::MatrixNd eccPts2 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xNearE, yNearE, dydxNearE, + xE, yE, dydxE, cE); + + RigidBodyDynamics::Math::MatrixNd mX(6,4), mY(6,4); + mX.col(0) = concPts1.col(0); + mX.col(1) = concPts2.col(0); + mX.col(2) = eccPts1.col(0); + mX.col(3) = eccPts2.col(0); + + mY.col(0) = concPts1.col(1); + mY.col(1) = concPts2.col(1); + mY.col(2) = eccPts1.col(1); + mY.col(3) = eccPts2.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX,mY,xC,xE,yC,yE,dydxC,dydxE,curveName); +} + + +void MuscleFunctionFactory::createFiberForceVelocityInverseCurve( + double fmaxE, + double dydxC, + double dydxNearC, + double dydxIso, + double dydxE, + double dydxNearE, + double concCurviness, + double eccCurviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Ensure that the inputs are within a valid range + if(! (fmaxE > 1.0 )){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve: " + << curveName + << ": fmaxE must be greater than 1" + << endl; + assert(0); + abort(); + } + + double SimTKSignificantReal = + pow((double)std::numeric_limits::epsilon(), 7.0/8.0); + + if(! (dydxC > SimTKSignificantReal && dydxC < 1 )){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << curveName + << ": dydxC must be greater than 0" + << "and less than 1" + << endl; + assert(0); + abort(); + + } + + if(! (dydxNearC > dydxC && dydxNearC < 1 )){ + std::stringstream errMsg; + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << ": dydxNearC must be greater than 0 " + << curveName + << " and less than 1" + << endl; + assert(0); + abort(); + } + + if(! (dydxIso > 1)){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << curveName + << ": dydxIso must be greater than or equal to 1" + << endl; + assert(0); + abort(); + } + + //double SimTKSignificantReal = + // pow(std::numeric_limits::epsilon(), 7.0/8.0); + + if(! (dydxE > SimTKSignificantReal && dydxE < (fmaxE-1)) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << curveName + << ": dydxE must be greater than or equal to 0" + << " and less than fmaxE-1 (" << (fmaxE-1) << ")" + << endl; + assert(0); + abort(); + } + + if(! (dydxNearE >= dydxE && dydxNearE < (fmaxE-1)) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << curveName + << ": dydxNearE must be greater than or equal to dydxE" + << "and less than fmaxE-1 ("<< (fmaxE-1) << ")" + << endl; + assert(0); + abort(); + } + + if(! (concCurviness <= 1.0 && concCurviness >= 0) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << curveName + << ": concCurviness must be between 0 and 1" + << endl; + assert(0); + abort(); + } + + if(! (eccCurviness <= 1.0 && eccCurviness >= 0) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceVelocityInverseCurve " + << curveName + << ": eccCurviness must be between 0 and 1" + << endl; + assert(0); + abort(); + } + + std::string name = curveName; + name.append(".createFiberForceVelocityInverseCurve"); + + //Translate the users parameters into Bezier point locations + double cC = SegmentedQuinticBezierToolkit::scaleCurviness(concCurviness); + double cE = SegmentedQuinticBezierToolkit::scaleCurviness(eccCurviness); + + //Compute the concentric control point locations + double xC = -1; + double yC = 0; + + double xNearC = -0.9; + double yNearC = yC + 0.5*dydxNearC*(xNearC-xC) + 0.5*dydxC*(xNearC-xC); + + double xIso = 0; + double yIso = 1; + + double xE = 1; + double yE = fmaxE; + + double xNearE = 0.9; + double yNearE = yE + 0.5*dydxNearE*(xNearE-xE) + 0.5*dydxE*(xNearE-xE); + + + RigidBodyDynamics::Math::MatrixNd concPts1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( xC, yC, dydxC, + xNearC, yNearC,dydxNearC,cC); + RigidBodyDynamics::Math::MatrixNd concPts2 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xNearC,yNearC,dydxNearC, + xIso, yIso, dydxIso, cC); + RigidBodyDynamics::Math::MatrixNd eccPts1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( xIso, yIso, dydxIso, + xNearE, yNearE, dydxNearE, cE); + RigidBodyDynamics::Math::MatrixNd eccPts2 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xNearE, yNearE, dydxNearE, + xE, yE, dydxE, cE); + + RigidBodyDynamics::Math::MatrixNd mX(6,4), mY(6,4); + mX.col(0) = concPts1.col(0); + mX.col(1) = concPts2.col(0); + mX.col(2) = eccPts1.col(0); + mX.col(3) = eccPts2.col(0); + + mY.col(0) = concPts1.col(1); + mY.col(1) = concPts2.col(1); + mY.col(2) = eccPts1.col(1); + mY.col(3) = eccPts2.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mY,mX,yC,yE,xC,xE,1/dydxC,1/dydxE, curveName); + +} + +void MuscleFunctionFactory::createFiberCompressiveForcePennationCurve( + double phi0, + double k, + double curviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Check the input arguments + if( !(phi0>0 && phi0<(M_PI/2.0)) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForcePennationCurve " + << curveName + << ": phi0 must be greater than 0, and less than Pi/2" + << endl; + assert(0); + abort(); + } + + if( !(k > (1.0/(M_PI/2.0-phi0))) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForcePennationCurve " + << curveName + << ": k must be greater than " << (1.0/(M_PI/2.0-phi0)) + << endl; + assert(0); + abort(); + } + + if( !(curviness>=0 && curviness <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForcePennationCurve " + << curveName + << ": curviness must be between 0.0 and 1.0" + << endl; + assert(0); + abort(); + } + + std::string name=curveName; + name.append(".createFiberCompressiveForcePennationCurve"); + + //Translate the user parameters to quintic Bezier points + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double x0 = phi0; + double y0 = 0; + double dydx0 = 0; + double x1 = M_PI/2.0; + double y1 = 1; + double dydx1 = k; + + RigidBodyDynamics::Math::MatrixNd ctrlPts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0,x1,y1,dydx1,c); + + RigidBodyDynamics::Math::MatrixNd mX(6,1), mY(6,1); + mX.col(0) = ctrlPts.col(0); + mY.col(0) = ctrlPts.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX,mY,x0,x1,y0,y1,dydx0,dydx1,curveName); +} + +void MuscleFunctionFactory:: + createFiberCompressiveForceCosPennationCurve( + double cosPhi0, + double k, + double curviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Check the input arguments + if( !(cosPhi0>0 && cosPhi0 < 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForceCosPennationCurve " + << curveName + << ": cosPhi0 must be greater than 0, and less than 1" + << endl; + assert(0); + abort(); + } + + if( !(k < 1/cosPhi0) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForceCosPennationCurve " + << curveName + << ": k must be less than 0" + << endl; + assert(0); + abort(); + } + + if( !(curviness>=0 && curviness <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForceCosPennationCurve" + << curveName + << ": curviness must be between 0.0 and 1.0" + << endl; + assert(0); + abort(); + } + + std::string name=curveName; + name.append(".createFiberCompressiveForceCosPennationCurve"); + + //Translate the user parameters to quintic Bezier points + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double x0 = 0; + double y0 = 1; + double dydx0 = k; + double x1 = cosPhi0; + double y1 = 0; + double dydx1 = 0; + + RigidBodyDynamics::Math::MatrixNd ctrlPts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0,x1,y1,dydx1,c); + + RigidBodyDynamics::Math::MatrixNd mX(6,1), mY(6,1); + mX.col(0) = ctrlPts.col(0); + mY.col(0) = ctrlPts.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX,mY,x0,x1,y0,y1,dydx0,dydx1,curveName); +} + +void MuscleFunctionFactory::createFiberCompressiveForceLengthCurve( + double lmax, + double k, + double curviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + + if( !(lmax>0) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForceLength " + << curveName + << ": l0 must be greater than 0" + << endl; + assert(0); + abort(); + } + + if( !(k < -(1.0/lmax)) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForceLength " + << curveName + << ": k must be less than " + << -(1.0/lmax) + << endl; + assert(0); + abort(); + } + + if( !(curviness>=0 && curviness <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberCompressiveForceLength " + << curveName + << ": curviness must be between 0.0 and 1.0" + << endl; + assert(0); + abort(); + } + + std::string caller = curveName; + caller.append(".createFiberCompressiveForceLength"); + + //Translate the user parameters to quintic Bezier points + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double x0 = 0.0; + double y0 = 1; + double dydx0 = k; + double x1 = lmax; + double y1 = 0; + double dydx1 = 0; + + RigidBodyDynamics::Math::MatrixNd ctrlPts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0,x1,y1,dydx1,c); + + RigidBodyDynamics::Math::MatrixNd mX(6,1), mY(6,1); + mX.col(0) = ctrlPts.col(0); + mY.col(0) = ctrlPts.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX,mY,x0,x1,y0,y1,dydx0,dydx1,curveName); +} + + +void MuscleFunctionFactory::createFiberForceLengthCurve( + double eZero, + double eIso, + double kLow, + double kIso, + double curviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + + if( !(eIso > eZero) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceLength " + << curveName + << ": The following must hold: eIso > eZero" + << endl; + assert(0); + abort(); + } + + if( !(kIso > (1.0/(eIso-eZero))) ){ + std::stringstream errMsg; + cerr << "MuscleFunctionFactory::" + << "createFiberForceLength " + << curveName + << ": kiso must be greater than 1/(eIso-eZero) (" + << (1.0/(eIso-eZero)) << ")" + << endl; + assert(0); + abort(); + } + + if( !(kLow > 0.0 && kLow < 1/(eIso-eZero)) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceLength " + << curveName + << ": kLow must be greater than 0 and less than" + << 1.0/(eIso-eZero) + << endl; + assert(0); + abort(); + } + + if( !(curviness>=0 && curviness <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createFiberForceLength " + << curveName + << ": curviness must be between 0.0 and 1.0" + << endl; + assert(0); + abort(); + } + + std::string callerName = curveName; + callerName.append(".createFiberForceLength"); + + + //Translate the user parameters to quintic Bezier points + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double xZero = 1+eZero; + double yZero = 0; + + double xIso = 1 + eIso; + double yIso = 1; + + double deltaX = std::min(0.1*(1.0/kIso), 0.1*(xIso-xZero)); + + double xLow = xZero + deltaX; + double xfoot = xZero + 0.5*(xLow-xZero); + double yfoot = 0; + double yLow = yfoot + kLow*(xLow-xfoot); + + //Compute the Quintic Bezier control points + RigidBodyDynamics::Math::MatrixNd p0 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xZero, yZero, 0, + xLow, yLow, kLow,c); + + RigidBodyDynamics::Math::MatrixNd p1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xLow, yLow, kLow, + xIso, yIso, kIso, c); + RigidBodyDynamics::Math::MatrixNd mX(6,2); + RigidBodyDynamics::Math::MatrixNd mY(6,2); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + + mX.col(1) = p1.col(0); + mY.col(1) = p1.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX, mY, xZero, xIso, yZero, yIso, 0.0, kIso, curveName); +} + + + + +void MuscleFunctionFactory:: + createTendonForceLengthCurve( double eIso, double kIso, + double fToe, double curviness, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + + if( !(eIso>0) ){ + cerr << "MuscleFunctionFactory::" + << "createTendonForceLengthCurve " + << curveName + << ": eIso must be greater than 0, but " + << eIso << " was entered" + << endl; + assert(0); + abort(); + + } + + if( !(fToe>0 && fToe < 1) ){ + cerr << "MuscleFunctionFactory::" + << "createTendonForceLengthCurve " + << curveName + << ": fToe must be greater than 0 and less than 1, but " + << fToe + << " was entered" + << endl; + assert(0); + abort(); + } + + if( !(kIso > (1/eIso)) ){ + cerr << "MuscleFunctionFactory::" + << "createTendonForceLengthCurve " + << curveName + << ": kIso must be greater than 1/eIso, (" + << (1/eIso) << "), but kIso (" + << kIso << ") was entered" + << endl; + assert(0); + abort(); + } + + + if( !(curviness>=0 && curviness <= 1) ){ + cerr << "MuscleFunctionFactory::" + << "createTendonForceLengthCurve " + << curveName + << " : curviness must be between 0.0 and 1.0, but " + << curviness << " was entered" + << endl; + assert(0); + abort(); + } + + std::string callerName = curveName; + callerName.append(".createTendonForceLengthCurve"); + + //Translate the user parameters to quintic Bezier points + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double x0 = 1.0; + double y0 = 0; + double dydx0 = 0; + + double xIso = 1.0 + eIso; + double yIso = 1; + double dydxIso = kIso; + + //Location where the curved section becomes linear + double yToe = fToe; + double xToe = (yToe-1)/kIso + xIso; + + + //To limit the 2nd derivative of the toe region the line it tends to + //has to intersect the x axis to the right of the origin + double xFoot = 1.0+(xToe-1.0)/10.0; + double yFoot = 0; + double dydxToe = (yToe-yFoot)/(xToe-xFoot); + + //Compute the location of the corner formed by the average slope of the + //toe and the slope of the linear section + double yToeMid = yToe*0.5; + double xToeMid = (yToeMid-yIso)/kIso + xIso; + double dydxToeMid = (yToeMid-yFoot)/(xToeMid-xFoot); + + //Compute the location of the control point to the left of the corner + double xToeCtrl = xFoot + 0.5*(xToeMid-xFoot); + double yToeCtrl = yFoot + dydxToeMid*(xToeCtrl-xFoot); + + + + //Compute the Quintic Bezier control points + RigidBodyDynamics::Math::MatrixNd p0 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0, + xToeCtrl,yToeCtrl,dydxToeMid,c); + RigidBodyDynamics::Math::MatrixNd p1 = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xToeCtrl, yToeCtrl, dydxToeMid, + xToe, yToe, dydxIso, c); + RigidBodyDynamics::Math::MatrixNd mX(6,2); + RigidBodyDynamics::Math::MatrixNd mY(6,2); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + + mX.col(1) = p1.col(0); + mY.col(1) = p1.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX, mY, x0, xToe, y0, yToe, dydx0, dydxIso, curveName); + +} diff --git a/3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.h b/3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.h new file mode 100644 index 0000000..ece6344 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/MuscleFunctionFactory.h @@ -0,0 +1,832 @@ +#ifndef MUSCLEFUNCTIONFACTORY_H_ +#define MUSCLEFUNCTIONFACTORY_H_ +/* -------------------------------------------------------------------------- * + * OpenSim: SmoothSegmentedFunctionFactory.h * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ +/* + Update: + This is a port of the original code so that it will work with + the multibody code RBDL written by Martin Felis. + + Author: + Matthew Millard + + Date: + Nov 2015 + +*/ +#include "../geometry/SmoothSegmentedFunction.h" +#include "../geometry/SegmentedQuinticBezierToolkit.h" + +#include +#include +#include +#include + + + +/** +This is a class that acts as a user friendly wrapper to QuinticBezerCurveSet +to build specific kinds of physiologically plausible muscle curves using C2 +continuous sets of quintic Bezier curves. This class has been written there did +not exist a set of curves describing muscle characteristics that was: + +1. Physiologically Accurate +2. Continuous to the second derivative +3. Parameterized in a physically meaningful manner + +For example, the curves employed by Thelen (Thelen DG(2003). Adjustment of +Muscle Mechanics Model Parameters to Simulate Dynamic Contractions in Older +Adults. ASME Journal of Biomechanical Engineering (125).) are parameterized in a +physically meaningful manner, making them easy to use. However there are +many shortcomings of these curves: + +1. The tendon and parallel element are not C2 continuous, making them slow to + simulate and likely not physiologically accurate. +2. The active force length curve approaches does not achieve its minimum value + at a normalized fiber length of 0.5, and 1.5. +3. The force velocity curve is not C2 continuous at the origin. As it is + written in the paper the curve is impossible to use with an equilibrium model + because it is not invertible. In addition the force-velocity curve actually + increases in stiffness as activation drops - a very undesirable property given + that many muscles are inactive at any one time. + +The muscle curves used in the literature until 2012 have been hugely influenced +by Thelen's work, and thus similar comments can easily be applied to just about +every other musculoskeletal simulation. + +Another example is from Miller (Miller,RH(2011).Optimal Control of +Human Running. PhD Thesis). On pg 149 a physiolgically plausible force velocity +curve is specified that gives the user the ability to change the concentric +curvature to be consistent with a slow or a fast twitch musle. This curve is +not C2 continuous at the origin, but even worse, it contains singularities in +its parameter space. Since these parameters do not have a physical +interpretation this model is difficult to use without accidentically creating a +curve with a singularity. + +With this motivation I set out to develop a class that could generate muscle +characteristic curves with the following properties: + +1. Physiologically Accurate +2. Continuous to the second derivative +3. Parameterized in a physically meaningful manner +4. Monotonicity for monotonic curves +5. Computationally efficient + +These goals were surprisingly time consuming achieve, but these goals have been +achieved using sets of C2 continuous quintic Bezier curves. The resulting +muscle curve functions in this class take parameters that would be intuitive to +biomechanists who simulate human musculoskeletal systems, and returns a +SmoothSegmentedFunction which is capable of evaluating the value, and +derivatives of the desired function (or actually relation as +the case may be). + +Each curve is made up of one or more C2 quintic Bezier curves x(u), +and y(u), with linearily extrapolated ends as shown in the figure below. These +quintic curves span 2 points, and achieve the desired derivative at its end +points. The degree of curviness can be varied from 0 to 1 (0, 0.75 and 1.0 are +shown in the figure in grey, blue and black respectively), and will make the +curve approximate a line when set to 0 (grey), and approximate a curve that +hugs the intersection of the lines that are defined by the end points locations +and the slopes at the end of each curve segment (red lines). Although you do +not need to set all of this information directly, for some of the curves it is +useful to know that both the slope and the curviness parameter may need to be +altered to achieve the desired shape. + + +\image html fig_GeometryAddon_quinticCornerSections.png + + + +Computational Cost Details +All computational costs assume the following operation costs: + +\verbatim +Operation Type : #flops +*,+,-,=,Boolean Op : 1 + / : 10 + sqrt: 20 + trig: 40 +\endverbatim + +These relative weightings will vary processor to processor, and so any of +the quoted computational costs are approximate. + + RBDL Port Notes +The port of this code from OpenSim has been accompanied by a few changes: + +1. The 'calcIntegral' method has been removed. Why? This function + relied on having access to a variable-step error controlled + integrator. There is no such integrator built into RBDL. Rather + than add a dependency (by using Boost perhaps) this functionality + has been removed. + +2. The function name .printMuscleCurveToFile(...) has been changed + to .printCurveToFile(). + + + +@author Matt Millard +@version 0.0 + +*/ +namespace RigidBodyDynamics { + namespace Addons { + namespace Muscle{ + +class MuscleFunctionFactory +{ + + + public: + + // friend class SmoothSegmentedFunction; + + + /** + This is a function that will produce a C2 (continuous to the second + derivative) active force length curve. + + + @param lce0 Normalized fiber length at the left-most shoulder of the + active force-length curve. The value of the active force + length curve for lce < lce0 will be equal to the value + set in shoulderVal. Normally lce0 is approximately 0.5 + + @param lce1 Normalized fiber length at the transition point between + the ascending limb and the plateau region of the active + force length curve. + + @param lce2 Normalized fiber length at the maximum active force length + curve value of 1. Normally lce2 is by definition 1. + + @param lce3 Normalized fiber length of the at the right most shoulder + of the active-force length curve. The value of the active + force length curve for lce > lce2 will be equal to the + value of shoulderVal. Normally lce3 is approximately 1.5 + + @param minActiveForceLengthValue + The minimum value of the active force length + curve. A physiological non-equibrium muscle model + would have this value set to 0. An equilibrium + muscle model would have a non-zero lower bound on + this value of 0.1 typically. shoulderVal must be + greater than, or equal to 0. + + @param plateauSlope The slope of the plateau of the active force + length curve between lce1 and lce2. This parameter + can vary depending on the muscle model, but a + value of 0.8616 is a good place to start. + + @param curviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow). A value of 0 will yield an active + force length curve that is composed of slightly curved + line segments. A value of 1 will yield an active force + length curve that is smoothly rounded. + + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it (e.g. "bicep_fiberActiveForceLengthCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + if these conditions aren't met + -0 < lce0 < lce1 < lce2 < lce3 + -shoulderVal >= 0 + -0 <= plateauSlope < (1/(lce3-lce2)) + -0 <= curviness <= 1 + + + \image html fig_MuscleAddon_MuscleFunctionFactory_falCurve.png + + + Conditions: + + Computational Costs + \verbatim + ~20,500 flops + \endverbatim + + Example: + @code + double lce0 = 0.5; + double lce1 = 0.75; + double lce2 = 1; + double lce3 = 1.5; + double shoulderVal = 0.1; + double plateauSlope = 0.75; + double curviness = 0.9; + + SmoothSegmentedFunction fiberfalCurve = SmoothSegmentedFunction(); + MuscleFunctionFactory:: + createFiberActiveForceLengthCurve(lce0, lce1, lce2, lce3, + shoulderVal, plateauSlope, curviness,"test", fiberfalCurve); + fiberfalCurve.printCurveToFile(); + @endcode + + + */ + static void createFiberActiveForceLengthCurve( + double lce0, + double lce1, + double lce2, + double lce3, + double minActiveForceLengthValue, + double plateauSlope, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry + ::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This function will generate a C2 continous (continuous to the second + derivative) force velocity curve of a single muscle fiber. The main + function of this element is to model the amount the force enhancement or + attenuation that is associated with contracting at a particular velocity. + + @param fmaxE The normalized maximum force the fiber can generate when + is being stretched. This value is reported to range + between 1.1 and 1.8 in the literature, though all values + are above 1. + + @param dydxC The slope of the fv(dlce(t)/dt) curve at the maximum + normalized concentric contraction velocity. Although + physiologically the value of dydxC at the maximum + concentric contracton velocity is by definition 0, a value + of 0 is often used. If you are using an equilbrium type + model this term must be positive and greater than zero so + that the fv curve can be inverted. +

+ Minimum Value: 0 + Maximum Value: dydxC < 1 +

+ + @param dydxNearC The slope of the force velocity curve as it approaches + the maximum concentric (shortening) contraction velocity. +

+ Minimum Value: > dydxC + Maximum Value: dydxNearC < 1 +

+ + + @param dydxIso The slope of the fv curve when dlce(t)/dt = 0. +

+ Minimim Value: dydxIso > 1.0 + Maximum Value: dydxIso < Inf + + @param dydxE The analogous term of dydxC parameter but for the + eccentric portion of the force-velocity curve. As with + the dydxC term, the physiologically accurate value for + this parameter is 0, though a value of 0 is rarely used + in muscle models. If you are using an equilbrium type + model this term must be positive and greater than zero + so that the fv curve can be inverted. +

+ Minimum Value: 0 + Maximum Value: dydxC < (fmaxE-1). +

+ As with the dydxC term, + the size of this term also affects the stiffness of the + integration problem for equilibrium-type muscle models: + the closer to zero this term is, the stiffer the model + will be (but only when (dlce(t)/dt)/vmax approaches 1. + + @param dydxNearE The slope of the force velocity curve as it approaches + the maximum eccentric (lengthening) contraction velocity. +

+ Minimum Value: > dydxE + Maximum Value: dydxNearE < (fmaxE-1) +

+ + + @param concCurviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow). This parameter affects only + the concentric side of the fv curve. + + @param eccCurviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow). This parameter affects only + the eccentric side of the fv curve. + + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it (e.g. "bicep_fiberForceVelocityCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + unless these conditions are met + -0 <= dydxC < 1 + -dydxC < dydxNearC < 1 + -1 < dydxIso + -dydxE < (fmaxE-1) + -dydxE < dydxNearC < (fmaxE-1) + -0<= concCurviness <=0 + -0 <= eccCurviness <= 0 + + + \image html fig_MuscleAddon_MuscleFunctionFactory_fvCurve.png + + + + Computational Costs + \verbatim + ~8,200 flops + \endverbatim + + Example: + @code + double fmaxE = 1.8; + double dydxC = 0.1; + double dydxNearC = 0.25; + double dydxE = 0.1; + double dydxNearE = 0.15; + double dydxIso= 5; + double concCurviness = 0.1; + double eccCurviness = 0.75; + + SmoothSegmentedFunction fiberFVCurve = SmoothSegmentedFunction(); + MuscleFunctionFactory:: + createFiberForceVelocityCurve(fmaxE, + dydxC, dydxNearC, dydxIso, dydxE, dydxNearE, + concCurviness, eccCurviness,"test", fiberFVCurve); + fiberFVCurve.printCurveToFile(); + @endcode + */ + static void createFiberForceVelocityCurve( + double fmaxE, + double dydxC, + double dydxNearC, + double dydxIso, + double dydxE, + double dydxNearE, + double concCurviness, + double eccCurviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This function will generate a C2 continuous (continuous to the 2nd + derivative) inverse curve that the function + createFiberForceVelocityCurve generates. The inverse force velocity + curve is required by every equilibrium muscle model in order to compute + the derivative of fiber velocity. To generate the inverse force velocity + curve simply call this function with EXACTLY the same parameter values + that you used to generate the force velocity curve. See the parameter + descriptions for createFiberForceVelocityCurve, as the parameters for + the inverse function are identical. The curve name should be different, + however, because this is an inverse curve + (e.g. "bicep_fiberForceVelocityInverseCurve") + + + \image html fig_MuscleAddon_MuscleFunctionFactory_fvInvCurve.png + + */ + static void createFiberForceVelocityInverseCurve( + double fmaxE, + double dydxC, + double dydxNearC, + double dydxIso, + double dydxE, + double dydxNearE, + double concCurviness, + double eccCurviness, + const std::string& muscleName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This element will generate a C2 continuous (continuous to the 2nd + derivative) compressive force profile curve as a function of pennation. + A muscle model with this element usually places this element parallel to + the fiber.The main function of this element is to prevent the fiber from + achieving a pennation angle of pi/2 radians. This type of element is + necessary for a parallelogram pennated equilibrium muscle models because + without it, the muscle model can deform to the point where a pennation + angle of pi/2 radians is reached, which causes a singularity in the + model. + + + @param phi0 The pennation angle at which the compressive force element + starts to engage . When the pennation angle is greater than + phi0, the compressive element is generating a force. When the + pennation angle is less than phi0, the compressive element + generates no force. + + @param kiso This is the maximum stiffness of the compressive element, + which occurs when the fiber is pennated by 90 degrees + + @param curviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow) + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it + (e.g. "bicep_fiberCompressiveForcePennationCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + unless the following conditions are met + -0 < phi0 < SimTK::Pi/2 + -kiso > 1/(SimTK::Pi/2-phi0) + -0 <= curviness <= 1 + + + \image html fig_MuscleAddon_MuscleFunctionFactory_fcphiCurve.png + + Computational Costs + \verbatim + ~4,100 flops + \endverbatim + + Example: + @code + double phi0 = (SimTK::Pi/2)*(8.0/9.0); + double kiso = 8.389863790885878; + double c = 0.0; + + SmoothSegmentedFunction fiberCEPhiCurve = SmoothSegmentedFunction(); + MuscleFunctionFactory:: + createFiberCompressiveForcePennationCurve(phi0,kiso,c,"test",fiberCEPhiCurve); + fiberCEPhiCurve.printCurveToFile(); + @endcode + */ + static void createFiberCompressiveForcePennationCurve( + double phi0, + double kiso, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This element will generate a C2 continuous (continuous to the 2nd + derivative) compressive force profile curve as a function of + cos(pennation). + + A muscle model with this element usually places this element in line + with the tendon. The main function of this element is to prevent the + fiber from achieving a pennation angle of pi/2 radians. This type of + element is necessary for a parallelogram pennated muscle models because + without it, the muscle model can deform to the point where a pennation + angle of pi/2 radians is reached, which causes a singularity in the + model. + + + @param cosPhi0 The cosine of the pennation angle at which the + compressive force element starts to engage. When the + cos of the pennation angle is greater than cosPhi0, the + compressive element generates no force. When cos of the + pennation angle is less than cosPhi0, the compressive + element generates a compressive force. + + @param kiso This is the maximum stiffness of the compressive element, + which occurs when cosPhi is zero. This parameter must be + negative + cos + @param curviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow) + + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it + (e.g. "bicep_fiberCompressiveForceCosPennationCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + unless the following conditions are met: + -0 < cosPhi0 + -kiso > 1/(cosPhi0) + -0 <= curviness <= 1 + + \image html fig_MuscleAddon_MuscleFunctionFactory_fcCosPhiCurve.png + + Computational Costs + \verbatim + ~4,100 flops + \endverbatim + + Example: + @code + double cosPhi0 = cos( (80.0/90.0)*SimTK::Pi/2); + double kiso = -1.2/(cosPhi0); + double c = 0.5; + + SmoothSegmentedFunction fiberCECosPhiCurve = MuscleFunctionFactory:: + createFiberCompressiveForceCosPennationCurve(cosPhi0,kiso,c,"test"); + fiberCEPhiCurve.printCurveToFile(); + @endcode + + + + */ + static void createFiberCompressiveForceCosPennationCurve( + double cosPhi0, + double kiso, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + + /** + This element will generate a C2 continous (continuous to the second + derivative) curve that models a compressive force profile that is a + function of fiber length. The main function of + this element is to prevent the fiber from achieving an unrealistically + short length. This type of element is necessary for equilibrium-type + muscle models because of the editing that is done to the active force + length curve that endows an equilibrium model fiber with the ability to + to generate force when a physiological fiber cannot. + + + + @param l0 The normalized fiber length at which the compressive element + starts to engage. When the fiber is shorter than l0, the + compressive element is generating a force. When the fiber + length is longer than l0, the compressive element generates + no force. + + @param kiso This is the maximum stiffness of the compressive element, + which occurs when the fiber has a length of 0, under a load + of 1 maximum isometric unit of force. + + @param curviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow) + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it + (e.g. "bicep_fiberCompressiveForceLengthCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + unless the following conditions are met + -e0 > 0 + -kiso > 1/(e0) + -0 <= curviness <= 1 + + + \image html fig_MuscleAddon_MuscleFunctionFactory_fpeCurve.png + + + Computational Costs + \verbatim + ~4,100 flops + \endverbatim + + Example: + @code + double lmax = 0.6; + double kiso = -8.389863790885878; + double c = 0.1;//0.0; + + SmoothSegmentedFunction fiberCECurve = MuscleFunctionFactory:: + createFiberCompressiveForceLengthCurve(lmax,kiso,c,"test"); + fiberCECurve.printCurveToFile(); + @endcode + + */ + static void createFiberCompressiveForceLengthCurve( + double l0, + double kiso, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This function will generate a C2 continuous curve that fits a fiber's + tensile force length curve. + + @param eZero The fiber strain at which the fiber begins to develop force. + Thus an e0 of 0.0 means that the fiber will start to develop + passive force when it has a normalized length of 1.0. Note + that e0 can be postive or negative. + + @param eIso The fiber strain at which the fiber develops 1 unit of + normalized force (1 maximum isometric force). Note that the + '1' is left off. Thus an e0 of 0.6 means that the fiber + will develop an 1 normalized force unit when it is strained + by 60% of its resting length, or to a normalized length of + 1.6 + + @param kLow The normalized stiffness (or slope) of the fiber curve + close to the location where the force-length curve + approaches a normalized force of 0. This is usually + chosen to be a small, but non-zero fraction of kIso + (kLow = 0.025 kIso is typical). + + @param kIso The normalized stiffness (or slope) of the fiber curve + when the fiber is strained by eIso (or has a length of + 1+eIso) under a load of 1 maximum isometric unit of force. + + + @param curviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow) + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it (e.g. "bicep_fiberForceLengthCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + unless the following conditions are met + -eIso > eZero + -kIso > 1/(eIso-eZero) + -0 < kLow < kIso + -0 <= curviness <= 1 + + \image html fig_MuscleAddon_MuscleFunctionFactory_fcLengthCurve.png + + + Computational Costs + \verbatim + ~4,100 flops + \endverbatim + + Example: + @code + double eIso = 0.6; + double eZero = 0.0; + double kIso = 4.0/(eIso-eZero); + double kNearZero = 0.025*kIso + double c = 0.5; + + SmoothSegmentedFunction fiberFLCurve + = MuscleFunctionFactory:: + createFiberForceLengthCurve(eZero, eIso, + kLow, kIso, c,"test"); + fiberFLCurve.printCurveToFile(); + @endcode + + */ + static void createFiberForceLengthCurve( + double eZero, + double eIso, + double kLow, + double kIso, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + Will generate a C2 continous (continuous to the second derivative) + curve in a MuscleFunctionObject object that fits a tendon's tensile + force length curve. + + + + @param eIso The tendon strain at which the tendon develops 1 unit + of normalized force (1 maximum isometric force). Note that + the'1' is left off. Thus an e0 of 0.04 means that the tendon + will develop an 1 normalized force unit when it is strained + by 4% of its resting length, at a normalized length of + 1.04 + + @param kIso The normalized stiffness (or slope) of the tendon + curve when the tendon is strained by e0 + (or has a length of 1+e0) under a load of 1 maximum + isometric unit of force. + + @param fToe The normalized force at which the tendon smoothly + transitions from the curved low stiffness region to + the linear stiffness region. + + @param curviness The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow) + + @param curveName The name of the muscle this curve applies to. This + curve name should have the name of the muscle and the + curve in it (e.g. "bicep_tendonForceLengthCurve") + sothat if this curve ever causes an exception, a + userfriendly error message can be displayed to the + end user to help them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + \b aborts \b + unless the following conditions are met: + -0 < fToe < 1 + -e0 > 0 + -kiso > 1/e0 + -0 <= curviness <= 1 + + + \image html fig_MuscleAddon_MuscleFunctionFactory_fseCurve.png + + + Computational Costs + \verbatim + ~4,100 flops + \endverbatim + + Example: + @code + double e0 = 0.04; + double kiso = 42.79679348815859; + double fToe = 1.0/3.0 + double c = 0.75; + + SmoothSegmentedFunction* tendonCurve = MuscleFunctionFactory:: + createTendonForceLengthCurve( + e0,kiso,fToe,c,"test"); + tendonCurve.printCurveToFile(); + @endcode + + + */ + static void createTendonForceLengthCurve(double eIso, + double kIso, + double fToe, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry:: + SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + + + +}; + +} +} +} + +#endif //MUSCLEFUNCTIONFACTORY_H_ diff --git a/3rdparty/rbdl/addons/muscle/NOTICE b/3rdparty/rbdl/addons/muscle/NOTICE new file mode 100644 index 0000000..e12de15 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/NOTICE @@ -0,0 +1,22 @@ +Author: + Matthew Millard + +Date: + July 2016 + + +Notice: these files + + -MuscleFunctionFactory.cc + -MuscleFunctionFactory.h + -csvtools.h + -csvtools.cc + -tests/testMuscleFunctionFactory.cc + +were originally part of OpenSim and have been ported +over to RBDL with some modification. These files are licenced under the +APACHE 2.0 license which, like the zlib license, is quite liberal. The +full licence can be found in this folder in the file "LICENSE_APACHE-2.0.txt" +and online here: + +http://www.apache.org/licenses/LICENSE-2.0.txt diff --git a/3rdparty/rbdl/addons/muscle/README.md b/3rdparty/rbdl/addons/muscle/README.md new file mode 100644 index 0000000..5905145 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/README.md @@ -0,0 +1,55 @@ +@brief muscle - a set of functions and classes for simulation musculotendon + dynamics. This addon is maintained by Matthew Millard, so if + you have problems with it email him. + +@author Matthew Millard + +\copyright 2016 Matthew Millard + +\b Requirements +This addon depends on the geometry addon + +\b Description + This addon currently contains an optimized library for creating specialized + curves that describe phenomenological curves for line-type muscles + -fiber active-force-length curve + -fiber force-velocity curve + -fiber passive-force-length curve + -tendon force-length curve + and torque-type muscles + -active torque-angle curve + -active torque-velocity curve + -passive torque-angle curve + In addition, there is a class that can be used to package the memory and + functions required to model torque muscles: Millard2016TorqueMuscle. + +\b Future Development +In the near future this library will also contain + +1. Torque-type muscle models with + a. Elastic tendons + b. Short-range-stiffness + +2. An implementation of the line-type muscle Millard2012Equilibrium muscle model + which features the option of using rigid and elastic tendons, and a damped/ + undamped formulation. + +3. An novel implemenation of 2D muscle wrapping using an obstacle set of + smooth convex shapes described using Pythagorean hodographs. + +\b Licensing +The following files have been ported over from OpenSim and Simbody and as such +are licenced under the Apache 2.0 Licence: + +SmoothSegmentedFunctionFactory.h +SmoothSegmentedFunctionFactory.cc + +The Apache License is very similar to the zlib license and is quite liberal. +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain a +copy of the License at http://www.apache.org/licenses/LICENSE-2.0. + +The remaining code has been written from scratch and is licenced under the +zlib license. See the LICENSE file for details. + + diff --git a/3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.cc b/3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.cc new file mode 100644 index 0000000..89b9f95 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.cc @@ -0,0 +1,1434 @@ +/*------------------------------------------------------------------------- + OpenSim: SmoothSegmentedFunctionFactory.cpp + -------------------------------------------------------------------------- + The OpenSim API is a toolkit for musculoskeletal modeling and simulation. + See http:%opensim.stanford.edu and the NOTICE file for more information. + OpenSim is developed at Stanford University and supported by the US + National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA + through the Warrior Web program. + + Copyright (c) 2005-2012 Stanford University and the Authors + Author(s): Matthew Millard + + Licensed under the Apache License, Version 2.0 (the 'License'); you may + not use this file except in compliance with the License. You may obtain a + copy of the License at http:%www.apache.org/licenses/LICENSE-2.0. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an 'AS IS' BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + -------------------------------------------------------------------------- + + Derivative work + Date : September 2016 + Authors(s): Millard + Updates : Made active torque-angle, passive-torque-angle, torque-velocity + and tendon-torque-angle curves based on the equivalent line-type + curves in OpenSim. +*/ + +#include "TorqueMuscleFunctionFactory.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace RigidBodyDynamics::Math; +using namespace RigidBodyDynamics::Addons::Muscle; +using namespace RigidBodyDynamics::Addons::Geometry; + +static double SQRTEPS = sqrt( (double)std::numeric_limits::epsilon()); + +//============================================================================= +// Anderson 2007 Active Torque Angle Curve +//============================================================================= + +void TorqueMuscleFunctionFactory:: + createAnderson2007ActiveTorqueAngleCurve( + double c2, + double c3, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Check the input arguments + if( !(c2 > 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAnderson2007ActiveTorqueAngleCurve " + << curveName + << ": c2 must be greater than 0" + << endl; + assert(0); + abort(); + } + + + std::string name=curveName; + name.append(".createAnderson2007ActiveTorqueAngleCurve"); + + //For now these advanced paramters are hidden. They will only be + //uncovered if absolutely necessary. + double minValueAtShoulders = 0; + double minShoulderSlopeMagnitude = 0; + + double curviness = 0.5; + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + + //Translate the user parameters to quintic Bezier points + double x0 = c3 - 1.05*(0.5*(M_PI/c2)); + double x1 = c3 - 0.95*(0.5*(M_PI/c2)); + double x2 = c3; + double x3 = c3 + 0.95*(0.5*(M_PI/c2)); + double x4 = c3 + 1.05*(0.5*(M_PI/c2)); + + double y0 = minValueAtShoulders; + double y1 = cos(c2*(x1-c3)); + double y2 = cos(c2*(x2-c3)); + double y3 = cos(c2*(x3-c3)); + double y4 = minValueAtShoulders; + + double dydx0 = minShoulderSlopeMagnitude; + double dydx1 = -sin(c2*(x1-c3))*c2; + double dydx2 = -sin(c2*(x2-c3))*c2; + double dydx3 = -sin(c2*(x3-c3))*c2; + double dydx4 = -minShoulderSlopeMagnitude; + + + //Compute the Quintic Bezier control points + RigidBodyDynamics::Math::MatrixNd p0 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0, + x1,y1,dydx1,c); + + RigidBodyDynamics::Math::MatrixNd p1 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x1,y1,dydx1, + x2,y2,dydx2,c); + + RigidBodyDynamics::Math::MatrixNd p2 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x2,y2,dydx2, + x3,y3,dydx3,c); + + RigidBodyDynamics::Math::MatrixNd p3 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x3,y3,dydx3, + x4,y4,dydx4,c); + + RigidBodyDynamics::Math::MatrixNd mX(6,4); + RigidBodyDynamics::Math::MatrixNd mY(6,4); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + mX.col(1) = p1.col(0); + mY.col(1) = p1.col(1); + mX.col(2) = p2.col(0); + mY.col(2) = p2.col(1); + mX.col(3) = p3.col(0); + mY.col(3) = p3.col(1); + + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX, mY, x0, x4, y0, y4, dydx0, dydx4, curveName); +} + +//============================================================================= +// ANDERSON 2007 Active Torque Angular Velocity Curve +//============================================================================= +void TorqueMuscleFunctionFactory:: + createAnderson2007ActiveTorqueVelocityCurve( + double c4, + double c5, + double c6, + double minEccentricMultiplier, + double maxEccentricMultiplier, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + //Check the input arguments + if( !(c4 < c5) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAndersonActiveTorqueVelocityCurve " + << curveName + << ": c4 must be greater than c5" + << endl; + assert(0); + abort(); + } + + if( !((c4 > 0)) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAndersonActiveTorqueVelocityCurve " + << curveName + << ": c4 must be greater than 0" + << endl; + assert(0); + abort(); + } + + if( !(c6 > 0.0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAndersonActiveTorqueVelocityCurve " + << curveName + << ": c6 must be greater than 1.0" + << endl; + assert(0); + abort(); + } + + if( !(minEccentricMultiplier > 1.0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAndersonActiveTorqueVelocityCurve " + << curveName + << ": minEccentricMultiplier must be greater than 1.0" + << endl; + assert(0); + abort(); + } + + if( !(maxEccentricMultiplier > minEccentricMultiplier) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAndersonActiveTorqueVelocityCurve " + << curveName + << ": maxEccentricMultiplier must be greater than " + << " minEccentricMultiplier" + << endl; + assert(0); + abort(); + } + + //Advanced settings that we'll hide for now + double minShoulderSlopeMagnitude = 0; + double curviness = 0.75; + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + + //Go and get the value of the curve that is closest to + //the maximum contraction velocity by setting rhs of Eqn. 9 + //to 0 and solving + double dthMaxConc = abs( 2.0*c4*c5/(c5-3.0*c4) ); + + //Go and evaluate the concentric side of the Anderson curve + //at 1/2 of omega max - we need this to use the updated + //torque-velocity curve. + double wMid = dthMaxConc*0.50; + double tvMidDen = (2*c4*c5 + wMid*(2*c5-4*c4)); + double tvMid = (2*c4*c5 + wMid*(c5-3*c4))/tvMidDen; + + tvMid = min(tvMid,0.45); + double tvMaxEcc = 1.1 + c6*0.2; + + createTorqueVelocityCurve(tvMaxEcc, + tvMid, + curveName, + smoothSegmentedFunctionToUpdate); + + +} +//============================================================================= +// ANDERSON 2007 Passive Torque Angle Curve +//============================================================================= +void TorqueMuscleFunctionFactory:: + createAnderson2007PassiveTorqueAngleCurve( + double scale, + double c1, + double b1, + double k1, + double b2, + double k2, + const std::string& curveName, + SmoothSegmentedFunction& smoothSegmentedFunctionToUpdate) +{ + + if( !(scale > 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createAnderson2007PassiveTorqueAngleCurve " + << curveName + << ": scale must be greater than 0" + << endl; + assert(0); + abort(); + } + + if( !(c1 > 0) ) { + cerr << "TorqueMuscleFunctionFactory::" + << "createAnderson2007PassiveTorqueAngleCurve " + << curveName + << ": c1 must be greater than 0" + << endl; + assert(0); + abort(); + } + + //Advanced settings that we'll hide for now + double curviness = 0.75; + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double minShoulderSlopeMagnitude = 0; + + //Zero out the coefficients associated with a + //the side of the curve that goes negative. + bool flag_oneSided = true; + + if(flag_oneSided){ + if(fabs(b1) > 0){ + if(b1 > 0){ + b2 = 0; + k2 = 0; + }else{ + b1 = 0; + k1 = 0; + } + + }else if(fabs(b2) > 0){ + if(b2 > 0){ + b1 = 0; + k1 = 0; + }else{ + b2 = 0; + k2 = 0; + } + } + } + //Divide up the curve into a left half + //and a right half, rather than 1 and 2. + //Why? These two different halves require different + // Bezier curves. + + double c1Scale = c1*scale; + double thL = 0.; //left + double thR = 0.; //right + double DtauDthL = 0.; + double DtauDthR = 0.; + double bL = 0.; + double kL = 0.; + double bR = 0.; + double kR = 0.; + + int curveType = 0; //flat curve + int flag_thL = 0; + int flag_thR = 0; + + if(fabs(k1)>0 && fabs(b1)>0){ + //The value of theta where the passive force generated by the + //muscle is equal to 1 maximum isometric contraction. + thL = (1/k1)*log(fabs( c1Scale/b1 )); + DtauDthL = b1*k1*exp(thL*k1); + bL = b1; + kL = k1; + flag_thL = 1; + } + + if(fabs(k2)>0 && fabs(b2)>0){ + //The value of theta where the passive force generated by the + //muscle is equal to 1 maximum isometric contraction. + thR = (1/k2)*log(fabs( c1Scale/b2 )); + DtauDthR = b2*k2*exp(thR*k2); + bR = b2; + kR = k2; + flag_thR = 1; + } + + //A 'left' curve has a negative slope, + //A 'right' curve has a positive slope. + if(DtauDthL > DtauDthR){ + double tmpD = thL; + thL = thR; + thR = tmpD; + + tmpD = bR; + bR = bL; + bL = tmpD; + + tmpD = kR; + kR = kL; + kL = tmpD; + + tmpD = DtauDthL; + DtauDthL = DtauDthR; + DtauDthR = tmpD; + + int tmpI = flag_thL; + flag_thL = flag_thR; + flag_thR = tmpI; + } + + + if(flag_thL){ + curveType = curveType + 1; + } + if(flag_thR){ + curveType = curveType + 2; + } + + RigidBodyDynamics::Math::MatrixNd mX(6,1); + RigidBodyDynamics::Math::MatrixNd mY(6,1); + + double xStart = 0; + double xEnd = 0; + double yStart = 0; + double yEnd = 0; + double dydxStart = 0; + double dydxEnd = 0; + + + switch(curveType){ + + case 0: + {//No curve - it's a flat line + RigidBodyDynamics::Math::MatrixNd p0 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(0.,0.,0., + 1.,0.,0.,c); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + + }break; + case 1: + { + //Get a point on the curve that is close to 0. + double x1 = (1/kL)*log(fabs(0.01*c1Scale/bL) ); + double y1 = bL*exp(kL*x1); + double dydx1 = bL*kL*exp(kL*x1); + + //Get a point that is at 1 maximum isometric torque + double x3 = thL; + double y3 = bL*exp(kL*x3); + double dydx3 = bL*kL*exp(kL*x3); + + //Get a mid-point + double x2 = 0.5*(x1+x3); + double y2 = bL*exp(kL*x2); + double dydx2 = bL*kL*exp(kL*x2); + + //Past the crossing point of the linear extrapolation + double x0 = x1 - 2*y1/dydx1; + double y0 = 0; + double dydx0 = minShoulderSlopeMagnitude*copysign(1.0,dydx1); + + xStart = x3; + xEnd = x0; + yStart = y3; + yEnd = y0; + dydxStart = dydx3; + dydxEnd = dydx0; + + RigidBodyDynamics::Math::MatrixNd p0 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x3,y3,dydx3, + x2,y2,dydx2,c); + RigidBodyDynamics::Math::MatrixNd p1 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x2,y2,dydx2, + x1,y1,dydx1,c); + RigidBodyDynamics::Math::MatrixNd p2 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x1,y1,dydx1, + x0,y0,dydx0,c); + + mX.resize(6,3); + mY.resize(6,3); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + mX.col(1) = p1.col(0); + mY.col(1) = p1.col(1); + mX.col(2) = p2.col(0); + mY.col(2) = p2.col(1); + + }break; + case 2: + { + //Get a point on the curve that is close to 0. + double x1 = (1/kR)*log(fabs(0.01*c1Scale/bR) ); + double y1 = bR*exp(kR*x1); + double dydx1 = bR*kR*exp(kR*x1); + + //Go just past the crossing point of the linear extrapolation + double x0 = x1 - 2*y1/dydx1; + double y0 = 0; + double dydx0 = minShoulderSlopeMagnitude*copysign(1.0,dydx1); + + //Get a point close to 1 maximum isometric torque + double x3 = thR; + double y3 = bR*exp(kR*x3); + double dydx3 = bR*kR*exp(kR*x3); + + //Get a mid point. + double x2 = 0.5*(x1+x3); + double y2 = bR*exp(kR*x2); + double dydx2 = bR*kR*exp(kR*x2); + + xStart = x0; + xEnd = x3; + yStart = y0; + yEnd = y3; + dydxStart = dydx0; + dydxEnd = dydx3; + + RigidBodyDynamics::Math::MatrixNd p0 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0, + x1,y1,dydx1,c); + RigidBodyDynamics::Math::MatrixNd p1 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x1,y1,dydx1, + x2,y2,dydx2,c); + RigidBodyDynamics::Math::MatrixNd p2 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x2,y2,dydx2, + x3,y3,dydx3,c); + mX.resize(6,3); + mY.resize(6,3); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + mX.col(1) = p1.col(0); + mY.col(1) = p1.col(1); + mX.col(2) = p2.col(0); + mY.col(2) = p2.col(1); + + }break; + case 3: + { + //Only when flag_oneSided = false; + double x0 = thL; + double x4 = thR; + + double x2 = 0.5*(x0 + x4); + double x1 = 0.5*(x0 + x2); + double x3 = 0.5*(x2 + x4); + + double y0 = b1*exp(k1*x0) + + b2*exp(k2*x0); + double y1 = b1*exp(k1*x1) + + b2*exp(k2*x1); + double y2 = b1*exp(k1*x2) + + b2*exp(k2*x2); + double y3 = b1*exp(k1*x3) + + b2*exp(k2*x3); + double y4 = b1*exp(k1*x4) + + b2*exp(k2*x4); + + double dydx0 = b1*k1*exp(k1*x0) + + b2*k2*exp(k2*x0); + double dydx1 = b1*k1*exp(k1*x1) + + b2*k2*exp(k2*x1); + double dydx2 = b1*k1*exp(k1*x2) + + b2*k2*exp(k2*x2); + double dydx3 = b1*k1*exp(k1*x3) + + b2*k2*exp(k2*x3); + double dydx4 = b1*k1*exp(k1*x4) + + b2*k2*exp(k2*x4); + + xStart = x0; + xEnd = x4; + yStart = y0; + yEnd = y4; + dydxStart = dydx0; + dydxEnd = dydx4; + + RigidBodyDynamics::Math::MatrixNd p0 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0,y0,dydx0, + x1,y1,dydx1,c); + RigidBodyDynamics::Math::MatrixNd p1 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x1,y1,dydx1, + x2,y2,dydx2,c); + RigidBodyDynamics::Math::MatrixNd p2 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x2,y2,dydx2, + x3,y3,dydx3,c); + RigidBodyDynamics::Math::MatrixNd p3 = + SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x3,y3,dydx3, + x4,y4,dydx4,c); + + mX.resize(6,4); + mY.resize(6,4); + + mX.col(0) = p0.col(0); + mY.col(0) = p0.col(1); + mX.col(1) = p1.col(0); + mY.col(1) = p1.col(1); + mX.col(2) = p2.col(0); + mY.col(2) = p2.col(1); + mX.col(3) = p3.col(0); + mY.col(3) = p3.col(1); + + }break; + default: + { + cerr << "TorqueMuscleFunctionFactory::" + << "createAnderson2007PassiveTorqueAngleCurve " + << curveName + << ": undefined curveType" + << endl; + assert(0); + abort(); + } + + }; + + //Normalize the y values. + mY = mY*(1/c1Scale); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + mX, mY, + xStart, xEnd, + yStart/c1Scale, yEnd/c1Scale, + dydxStart/c1Scale, dydxEnd/c1Scale, + curveName); +} + +//============================================================================= +// +// New torque-muscle characteristic curves +// +//============================================================================= + + +//============================================================================= +// torque-velocity curves +//============================================================================= + +void TorqueMuscleFunctionFactory::createTorqueVelocityCurve( + double tvAtEccentricOmegaMax, + double tvAtHalfConcentricOmegaMax, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ) + { + + double slopeAtConcentricOmegaMax = 0.0; + double slopeNearEccentricOmegaMax = -0.025; + double slopeAtEccentricOmegaMax = 0.0; + double eccentricCurviness = 0.75; + + createTorqueVelocityCurve( + tvAtEccentricOmegaMax, + tvAtHalfConcentricOmegaMax, + slopeAtConcentricOmegaMax, + slopeNearEccentricOmegaMax, + slopeAtEccentricOmegaMax, + eccentricCurviness, + curveName, + smoothSegmentedFunctionToUpdate); + + } + +void TorqueMuscleFunctionFactory::createTorqueVelocityCurve( + double tvAtEccentricOmegaMax, + double tvAtHalfConcentricOmegaMax, + double slopeAtConcentricOmegaMax, + double slopeNearEccentricOmegaMax, + double slopeAtEccentricOmegaMax, + double eccentricCurviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ) +{ + + if( (tvAtEccentricOmegaMax < 1.05) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": tvAtEccentricOmegaMax must be greater than 1.05" + << endl; + assert(0); + abort(); + } + + if( (tvAtHalfConcentricOmegaMax < 0.05 + || tvAtHalfConcentricOmegaMax > 0.45) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": tvAtHalfOmegaMax must be in the interval [0.05,0.45]" + << endl; + assert(0); + abort(); + } + + if( (slopeAtConcentricOmegaMax > 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": slopeAtConcentricOmegaMax cannot be less than 0" + << endl; + assert(0); + abort(); + } + + if( (slopeNearEccentricOmegaMax > 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": slopeNearEccentricOmegaMax cannot be less than 0" + << endl; + assert(0); + abort(); + } + + if( (slopeAtEccentricOmegaMax > 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": slopeAtEccentricOmegaMax cannot be less than 0" + << endl; + assert(0); + abort(); + } + + if( (eccentricCurviness < 0 || eccentricCurviness > 1.0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": eccentricCurviness must be in the interval [0,1]" + << endl; + assert(0); + abort(); + } + + + double omegaMax = 1.0; + double wmaxC = omegaMax; //In biomechanics the concentric side gets a + //a +'ve signed velocity + double wmaxE = -omegaMax; + + //----------------------------------------------------------------------- + // 1. Fit concentric Bezier curves to a Hill-type hyperbolic curve + //----------------------------------------------------------------------- + + /* + First we compute the terms that are consistent with a Hill-type concentric + contraction. Starting from Hill's hyperbolic equation + + f(w) = (fiso*b - a*w) / (b+w) + + + Taking a derivative w.r.t omega yields + + df(w)/dw = [ (fiso*b - a*w + a*(b+w))] / (b+w)^2 + + at w = wmaxC the numerator goes to 0 + + (fiso*b - a*wmaxC) / (b + wmaxC) = 0 + (fiso*b - a*wmaxC) = 0; + b = a*wmaxC/fiso; + + Subtituting this expression for b into the expression when + f(wHalf) = tvAtHalfOmegaMax yields this expression for parameter a + + a = tvAtHalfOmegaMax*w*fiso ... + / (wmaxC*tvAtHalfOmegaMax + fiso*wmaxC - fiso*w); + + */ + + double fiso = 1.0; + double w = 0.5*wmaxC; + double a = -tvAtHalfConcentricOmegaMax*w*fiso + / (wmaxC*tvAtHalfConcentricOmegaMax - fiso*wmaxC + fiso*w); + + double b = a*wmaxC/fiso; + double yCheck = (b*fiso-a*w)/(b+w); + + if( abs(yCheck-tvAtHalfConcentricOmegaMax) > SQRTEPS ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": Internal error fitting the concentric curve to Hill's " + << "hyperbolic curve. This error condition was true: " + << "abs(yCheck-tvAtHalfOmegaMax) > sqrt(eps)" + << "Consult the maintainers of this addon." + << endl; + assert(0); + abort(); + } + + + w = 0*wmaxC; + double dydxIso = (-(a)*(b+w) - (b*fiso-a*w)) / ((b+w)*(b+w)); + + + w = 0.9*wmaxC; + double dydxNearC = (-(a)*(b+w) - (b*fiso-a*w)) / ((b+w)*(b+w)); + + + if( dydxNearC > slopeAtConcentricOmegaMax || abs(dydxNearC) > abs(1/wmaxC) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTorqueVelocityCurve " + << curveName + << ": Internal error fitting the concentric curve to Hill's " + << "hyperbolic curve. This error condition was true: " + << " dydxNearC < dydxC || dydxNearC > abs(1/wmaxC)" + << "Consult the maintainers of this addon." + << endl; + assert(0); + abort(); + } + + //---------------------------------------------------------------------------- + // Iterate over the curviness value to get a good match between Hill's + // hyperbolic curve and the Bezier curve + //---------------------------------------------------------------------------- + + double xNearC = 0.9*wmaxC; + double yNearC = (b*fiso-a*w)/(b+w); + double xIso = 0; + double yIso = 1.0; + + double cC = 0.5; + + MatrixNd pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xIso, yIso, dydxIso, + xNearC,yNearC, dydxNearC, + cC); + MatrixNd xpts(6,1); + xpts.col(0) = pts.col(0); + MatrixNd ypts(6,1); + ypts.col(0) = pts.col(1); + + SmoothSegmentedFunction fvCFcn = SmoothSegmentedFunction( + xpts,ypts, + xIso,xNearC, + yIso,yNearC, + dydxIso,dydxNearC, + "tvFcn"); + SmoothSegmentedFunction fvCFcnLeft = SmoothSegmentedFunction(); + SmoothSegmentedFunction fvCFcnRight = SmoothSegmentedFunction(); + + int nSample = 10; + double f = 0; + double yHill = 0; + + //Calculate the error of the Bezier curve with the starting curviness value + //of 0.5 + for(int j=0; j (0.9/abs(angleAtOneNormTorque-angleAtZeroTorque)) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createPassiveTorqueAngleCurve " + << curveName + << ": stiffnessAtLowTorque has a magnitude that is too " + << "large, it exceeds 0.9/abs(angleAtOneNormTorque-angleAtZeroTorque)" + << endl; + assert(0); + abort(); + } + + if( abs(stiffnessAtOneNormTorque) + < (1.1/abs(angleAtOneNormTorque-angleAtZeroTorque)) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createPassiveTorqueAngleCurve " + << curveName + << ": stiffnessAtOneNormTorque has a magnitude that is too " + << "small, it is less than 1.1/abs(angleAtOneNormTorque-angleAtZeroTorque)" + << endl; + assert(0); + abort(); + } + + if( stiffnessAtOneNormTorque*stiffnessAtLowTorque < 0.0 ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createPassiveTorqueAngleCurve " + << curveName + << ": stiffnessAtLowTorque and stiffnessAtOneNormTorque must have the" + << " same sign." + << endl; + assert(0); + abort(); + } + + if( stiffnessAtOneNormTorque + *(angleAtOneNormTorque-angleAtZeroTorque) < 0.0){ + cerr << "TorqueMuscleFunctionFactory::" + << "createPassiveTorqueAngleCurve " + << curveName + << ": stiffnessAtOneNormTorque must have the same sign as " + << "(angleAtOneNormTorque-angleAtZeroTorque)" + << endl; + assert(0); + abort(); + } + + if( (curviness < 0 || curviness > 1.0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createPassiveTorqueAngleCurve " + << curveName + << ": curviness must be in the interval [0,1]" + << endl; + assert(0); + abort(); + } + + MatrixNd xM(6,2); + MatrixNd yM(6,2); + MatrixNd pts(6,2); + + double x0,x1,y0,y1,dydx0,dydx1; + if(angleAtZeroTorque < angleAtOneNormTorque){ + x0 = angleAtZeroTorque; + x1 = angleAtOneNormTorque; + y0 = 0.; + y1 = 1.; + dydx0 = 0.0; + dydx1 = stiffnessAtOneNormTorque; + }else{ + x0 = angleAtOneNormTorque; + x1 = angleAtZeroTorque; + y0 = 1.0; + y1 = 0.0; + dydx0 = stiffnessAtOneNormTorque; + dydx1 = 0.0; + } + + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + + if(abs(stiffnessAtOneNormTorque) > SQRTEPS){ + + double delta = min( 0.1*(1.0-abs(1.0/stiffnessAtOneNormTorque)), + 0.05*abs(x1-x0)); + if(stiffnessAtOneNormTorque < 0.){ + delta *= -1.0; + } + + double xLow = angleAtZeroTorque + delta; + double xFoot = angleAtZeroTorque + 0.5*(xLow-angleAtZeroTorque); + double yFoot = 0.0; + double yLow = yFoot + stiffnessAtLowTorque*(xLow-xFoot); + + pts = SegmentedQuinticBezierToolkit::calcQuinticBezierCornerControlPoints( + x0, y0, dydx0, + xLow,yLow,stiffnessAtLowTorque, c); + xM.col(0) = pts.col(0); + yM.col(0) = pts.col(1); + + pts = SegmentedQuinticBezierToolkit::calcQuinticBezierCornerControlPoints( + xLow,yLow,stiffnessAtLowTorque, + x1, y1, dydx1, c); + xM.col(1) = pts.col(0); + yM.col(1) = pts.col(1); + + }else{ + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( + angleAtZeroTorque,0, 0, + angleAtOneNormTorque,0, 0, + c); + xM.col(0) = pts.col(0); + yM.col(1) = pts.col(1); + } + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + xM, yM, + x0, x1, + y0, y1, + dydx0, dydx1, + curveName); + + +} + +//============================================================================= +// active-torque angle curve +//============================================================================= + + +void TorqueMuscleFunctionFactory::createGaussianShapedActiveTorqueAngleCurve( + double angleAtOneNormTorque, + double angularStandardDeviation, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ) +{ + + createGaussianShapedActiveTorqueAngleCurve( + angleAtOneNormTorque, + angularStandardDeviation, + 0.0, + 0.0, + 0.5, + curveName, + smoothSegmentedFunctionToUpdate ); + +} + + +void TorqueMuscleFunctionFactory::createGaussianShapedActiveTorqueAngleCurve( + double angleAtOneNormTorque, + double angularStandardDeviation, + double minSlopeAtShoulders, + double minValueAtShoulders, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ) +{ + + if( (angularStandardDeviation < SQRTEPS) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createGaussianShapedActiveTorqueAngleCurve " + << curveName + << ": angularStandardDeviation is less than sqrt(eps)" + << endl; + assert(0); + abort(); + } + + if( (minValueAtShoulders < 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createGaussianShapedActiveTorqueAngleCurve " + << curveName + << ": minValueAtShoulders is less than 0" + << endl; + assert(0); + abort(); + } + + + if( (minSlopeAtShoulders < 0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createGaussianShapedActiveTorqueAngleCurve " + << curveName + << ": minSlopeAtShoulders is less than 0" + << endl; + assert(0); + abort(); + } + + if( (curviness < 0 || curviness > 1.0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createGaussianShapedActiveTorqueAngleCurve " + << curveName + << ": curviness must be in the interval [0,1]" + << endl; + assert(0); + abort(); + } + + double taCutoff = 1e-3; + if(taCutoff < minValueAtShoulders ){ + taCutoff = minValueAtShoulders; + } + double angularStandardDeviationSq = + angularStandardDeviation*angularStandardDeviation; + double thetaWidth = sqrt(-log(taCutoff)*2*angularStandardDeviationSq); + double thetaMin = -thetaWidth + angleAtOneNormTorque; + double thetaMax = thetaWidth + angleAtOneNormTorque; + + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + + double x0 = thetaMin; + + double x1 = angleAtOneNormTorque - angularStandardDeviation; + double x2 = angleAtOneNormTorque; + double x3 = angleAtOneNormTorque + angularStandardDeviation; + + if( (angleAtOneNormTorque-thetaMin) < + (angleAtOneNormTorque-angularStandardDeviation)){ + x1 = angleAtOneNormTorque - 0.5*thetaMin; + x3 = angleAtOneNormTorque + 0.5*thetaMin; + } + + double x4 = thetaMax; + + double y0 = minValueAtShoulders; + double y1 = exp(-(x1-angleAtOneNormTorque)*(x1-angleAtOneNormTorque) + / (2*angularStandardDeviationSq) ); + double y2 = exp(-(x2-angleAtOneNormTorque)*(x2-angleAtOneNormTorque) + / (2*angularStandardDeviationSq) ); + double y3 = exp(-(x3-angleAtOneNormTorque)*(x3-angleAtOneNormTorque) + / (2*angularStandardDeviationSq) ); + double y4 = minValueAtShoulders; + + + double dydx1 = -(2*(x1-angleAtOneNormTorque) + / (2*angularStandardDeviationSq)) * y1; + double dydx2 = -(2*(x2-angleAtOneNormTorque) + / (2*angularStandardDeviationSq)) * y2; + double dydx3 = -(2*(x3-angleAtOneNormTorque) + / (2*angularStandardDeviationSq)) * y3; + + double dydx0 = minSlopeAtShoulders; + double dydx4 = minSlopeAtShoulders; + + if(dydx1 < 0){ + dydx0 *= -1.0; + } + if(dydx3 < 0){ + dydx4 *= -1.0; + } + + + MatrixNd xM(6,4); + MatrixNd yM(6,4); + MatrixNd pts(6,2); + + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( + x0,y0, dydx0, + x1,y1, dydx1, c); + + xM.col(0) = pts.col(0); + yM.col(0) = pts.col(1); + + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( + x1,y1, dydx1, + x2,y2, dydx2, c); + + xM.col(1) = pts.col(0); + yM.col(1) = pts.col(1); + + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( + x2,y2, dydx2, + x3,y3, dydx3, c); + + xM.col(2) = pts.col(0); + yM.col(2) = pts.col(1); + + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints( + x3,y3, dydx3, + x4,y4, dydx4, c); + + xM.col(3) = pts.col(0); + yM.col(3) = pts.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction( + xM, yM, + x0, x4, + y0, y4, + dydx0,dydx4, + curveName); + +} + + +//============================================================================= +// tendon-torque angle curve +//============================================================================= + +void TorqueMuscleFunctionFactory::createTendonTorqueAngleCurve( + double angularStretchAtOneNormTorque, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ) +{ + + createTendonTorqueAngleCurve( + angularStretchAtOneNormTorque, + 1.5/angularStretchAtOneNormTorque, + 1.0/3.0, + 0.5, + curveName, + smoothSegmentedFunctionToUpdate ); + +} + + +void TorqueMuscleFunctionFactory::createTendonTorqueAngleCurve( + double angularStretchAtOneNormTorque, + double stiffnessAtOneNormTorque, + double normTorqueAtToeEnd, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ) +{ + + + if( angularStretchAtOneNormTorque < SQRTEPS ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTendonTorqueAngleCurve " + << curveName + << ": angularStretchAtOneNormTorque should be greater than sqrt(eps)" + << endl; + assert(0); + abort(); + } + + if( stiffnessAtOneNormTorque < 1.1/angularStretchAtOneNormTorque){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTendonTorqueAngleCurve " + << curveName + << ": stiffnessAtOneNormTorque should be greater " + << " than 1.1/angularStretchAtOneNormTorque" + << endl; + assert(0); + abort(); + } + + if( normTorqueAtToeEnd < SQRTEPS || normTorqueAtToeEnd > 0.99){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTendonTorqueAngleCurve " + << curveName + << ": normTorqueAtToeEnd must be in the inteval [sqrt(eps), 0.99]" + << endl; + assert(0); + abort(); + } + + if( (curviness < 0 || curviness > 1.0) ){ + cerr << "TorqueMuscleFunctionFactory::" + << "createTendonTorqueAngleCurve " + << curveName + << ": curviness must be in the interval [0,1]" + << endl; + assert(0); + abort(); + } + + double c = SegmentedQuinticBezierToolkit::scaleCurviness(curviness); + double x0 = 0; + double y0 = 0; + double dydx0 = 0; + + double xIso = angularStretchAtOneNormTorque; + double yIso = 1; + double dydxIso = stiffnessAtOneNormTorque; + + //Location where the curved section becomes linear + double yToe = normTorqueAtToeEnd; + double xToe = (yToe-1)/stiffnessAtOneNormTorque + xIso; + + + //To limit the 2nd derivative of the toe region the line it tends to + //has to intersect the x axis to the right of the origin + double xFoot = (xToe)/10.0; + double yFoot = 0; + + + //Compute the location of the corner formed by the average slope of the + //toe and the slope of the linear section + double yToeMid = yToe*0.5; + double xToeMid = (yToeMid-yIso)/stiffnessAtOneNormTorque + xIso; + double dydxToeMid = (yToeMid-yFoot)/(xToeMid-xFoot); + + //Compute the location of the control point to the left of the corner + double xToeCtrl = xFoot + 0.5*(xToeMid-xFoot); + double yToeCtrl = yFoot + dydxToeMid*(xToeCtrl-xFoot); + + MatrixNd xM(6,2); + MatrixNd yM(6,2); + MatrixNd pts(6,2); + + //Compute the Quintic Bezier control points + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(x0, y0, dydx0, + xToeCtrl,yToeCtrl,dydxToeMid, c); + xM.col(0) = pts.col(0); + yM.col(0) = pts.col(1); + + pts = SegmentedQuinticBezierToolkit:: + calcQuinticBezierCornerControlPoints(xToeCtrl, yToeCtrl, dydxToeMid, + xToe, yToe, dydxIso, c); + xM.col(1) = pts.col(0); + yM.col(1) = pts.col(1); + + smoothSegmentedFunctionToUpdate.updSmoothSegmentedFunction(xM,yM, + x0,xToe, + y0,yToe, + dydx0,dydxIso, + curveName); + +} diff --git a/3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.h b/3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.h new file mode 100644 index 0000000..44f92ef --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/TorqueMuscleFunctionFactory.h @@ -0,0 +1,663 @@ +#ifndef TORQUEMUSCLEFUNCTIONFACTORY_H_ +#define TORQUEMUSCLEFUNCTIONFACTORY_H_ +/*------------------------------------------------------------------------- + OpenSim: SmoothSegmentedFunctionFactory.cpp + -------------------------------------------------------------------------- + The OpenSim API is a toolkit for musculoskeletal modeling and simulation. + See http:%opensim.stanford.edu and the NOTICE file for more information. + OpenSim is developed at Stanford University and supported by the US + National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA + through the Warrior Web program. + + Copyright (c) 2005-2012 Stanford University and the Authors + Author(s): Matthew Millard + + Licensed under the Apache License, Version 2.0 (the 'License'); you may + not use this file except in compliance with the License. You may obtain a + copy of the License at http:%www.apache.org/licenses/LICENSE-2.0. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an 'AS IS' BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + -------------------------------------------------------------------------- + + Derivative work + Date : September 2016 + Authors(s): Millard + Updates : Made active torque-angle, passive-torque-angle, torque-velocity + and tendon-torque-angle curves based on the equivalent line-type + curves in OpenSim. +*/ + +#include "../geometry/SmoothSegmentedFunction.h" +#include "../geometry/SegmentedQuinticBezierToolkit.h" + +#include +#include +#include +#include + +namespace RigidBodyDynamics { + namespace Addons { + namespace Muscle{ + +class TorqueMuscleFunctionFactory +{ + public: + + + /** + This is a function that will produce a C2 (continuous to the second + derivative) active torque angle curve. This Bezier curve has been + fitted to match the active-torque-angle curve described in + + Anderson, Dennis E., Michael L. Madigan, and Maury A. Nussbaum. + "Maximum voluntary joint torque as a function of joint angle and + angular velocity: model development and application to the lower + limb." Journal of biomechanics 40, no. 14 (2007): 3105-3113. + + but note that its range is normalized to [0,1]. + + @param c2 (radians) + The active-torque-angle width parameter. The parameter c2 + is defined by Anderson et al. as + + c2 = pi/(theta_max - theta_min). + + @param c3 : (radians) + Then angle which has the largest active-torque. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + +*/ +static void createAnderson2007ActiveTorqueAngleCurve( + double c2, + double c3, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This is a function that will produce a C2 (continuous to the second + derivative) active torque (angular) velocity curve. This Bezier curve + has been fitted to match the active-torque-angle curve described in + + Anderson, Dennis E., Michael L. Madigan, and Maury A. Nussbaum. + "Maximum voluntary joint torque as a function of joint angle and + angular velocity: model development and application to the lower + limb." Journal of biomechanics 40, no. 14 (2007): 3105-3113. + + While the concentric side of the Bezier curve and the original + formulation match, the eccentric side does not: the equations + Anderson et al. chose decrease down to 0 rapidly. Since Anderson + et al. did not collect data at the higher eccentric velocities the + oddities in their chosen curves are likely due to the parameterization + they chose. The eccentric side of the Bezier curve will be fitted + so that, if possible, it passes close to the value of the original + curves for theta = -60 deg/s within the limits imposed by + minEccentricMultiplier and maxEccentricMultiplier. + + @param c4 (rads/s) + Angular velocity when the torque is 75% of the maximum + isometric torque. + + @param c5 (rads/s) + Angular velocity when the torque is 50% of the maximum + isometric torque. + + @param c6 + Multiplier that Anderson et al. uses to describe the + change in slope of the curve as the contraction velocity + changes sign from + to -. + + @param minEccentricMultiplier + The minimum value of the torque-(angular)-velocity curve + tends to at large eccentric contraction velocities. Note + minEccentricMultiplier > 1.0 + + @param maxEccentricMultiplier + The value of the torque-(angular)-velocity curve tends + to at large eccentric contraction velocities. Note + maxEccentricMultiplier > minEccentricMultiplier. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + */ + static void createAnderson2007ActiveTorqueVelocityCurve( + double c4, + double c5, + double c6, + double minEccentricMultiplier, + double maxEccentricMultiplier, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + /** + This is a function that will produce a C2 (continuous to the second + derivative) passive torque angle curve described in + + Anderson, Dennis E., Michael L. Madigan, and Maury A. Nussbaum. + "Maximum voluntary joint torque as a function of joint angle and + angular velocity: model development and application to the lower + limb." Journal of biomechanics 40, no. 14 (2007): 3105-3113. + + Note the following differences between this implementation and + the original equations presented in Anderson et al.: + + 1. This function will return a curve that is fitted to the + positive side of the curve defined by the coefficients + b1, k1, b2, and k2. Because of the sign convention employed by + Anderson et al. the positive side of the curve corresponds to + the passive curve generated by the torque actuator associated + with the rest of the coefficients. + + 2. This function has been normalized so that a value of 1.0 + corresponds to one-maximum-isometric-active-contraction + torque, or c1*subjectWeightInNewtons*subjectHeightInMeters. + + @param scale + The scaling factor used on the c1 column in Table 3 of + Anderson et al.: + + scale = subjectWeightInNewtons * subjectHeightInMeters + + @param c1 + The normalized c1 parameter listed in Tabel 3 of + Anderson et al. + + @param b1 + The passive torque angle curve parameter used in + Anderson et al.'s Eqn. 1: + + torquePassive = b1*exp(k1*theta) + b2*exp(k2*theta) + + @param k1 + The term k1 in Anderson et al.'s Eqn. 1. + + @param b2 + The term b2 in Anderson et al.'s Eqn. 1. + + @param k2 + The term k2 in Anderson et al.'s Eqn. 1. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + */ + static void createAnderson2007PassiveTorqueAngleCurve( + double scale, + double c1, + double b1, + double k1, + double b2, + double k2, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate); + + + /** + This function creates a normalized torque-velocity curve. The concentric + side of the curve is fitted to Hill's hyperbola that passes through + the value of tv-at-half-of-the-maximum-concentric-velocity (a parameter + supplied by the user). The eccentric side of the curve rapidly, but smoothly + approaches a terminal value of + tv-at-the-maximum-eccentric-contraction-velocity. Outside of the normalized + velocities of -1 to 1 the curve takes the values of 0, and + tvAtEccentricOmegaMax respectively with a slope of 0. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_TorqueVelocityCurveSimple.png + + @param tvAtEccentricOmegaMax + The value of the torque-velocity-multiplier at the maximum + eccentric contraction velocity. This value must be + + @param tvAtHalfConcentricOmegaMax + The value of the torque-velocity- + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + -tvAtEccentricOmegaMax < 1.05 + -tvAtHalfOmegaMax >= 0.45 + -tvAtHalfOmegaMax <= 0.05 + + References + Hill, A. V. (1938). The heat of shortening and the dynamic constants of + muscle. Proceedings of the Royal Society of London B: Biological Sciences, + 126(843), 136-195. + + */ + + static void createTorqueVelocityCurve( + double tvAtEccentricOmegaMax, + double tvAtHalfConcentricOmegaMax, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ); + + /** + This function creates a normalized torque-velocity curve. The concentric + side of the curve is fitted to Hill's hyperbola that passes through + the value of tv-at-half-of-the-maximum-concentric-velocity (a parameter + supplied by the user). The eccentric side of the curve rapidly, but smoothly + approaches a terminal value of + tv-at-the-maximum-eccentric-contraction-velocity. Shape of the eccentric + side of the curve can be changed using the slopeNearEccentricOmegaMax + and curviness variables. Outside of the normalized + velocities of -1 to 1 the curve takes the values of + slopeAtConcentricOmegaMax and slopeAtEccentricOmegaMax respectively. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_TorqueVelocityCurve.png + + @param tvAtEccentricOmegaMax + The value of the torque-velocity-multiplier at the maximum + eccentric contraction velocity. This value must be + + @param tvAtHalfConcentricOmegaMax + The value of the torque-velocity- + + @param slopeAtConcentricOmegaMax + The slope of the curve at a normalized angular velocity of -1. This slope + is used to extrapolate \f$\mathbf{t}_V\f$ for normalized angular velocities + of less than -1. + + @param slopeNearEccentricOmegaMax + The slope of the eccentric side of the curve as the normalized angular + velocity approaches 1. + + @param slopeAtEccentricOmegaMax + The slope of the curve at a normalized angular velocity of 1. This slope + is used to extrapolate \f$\mathbf{t}_V\f$ for normalized angular velocities + of greater than 1. + + @param eccentricCurviness + This parameter controls the shape of the curve between the normalized + angular velocities of 0 and 1. An eccentricCurviness of 0 will flatten + the elbow so that the curve closely follows a line that begins at (0,1) + and ends at (1,tvAtEccentricOmegaMax). An eccentricCurviness of 1 will + give the curve a strong elbow so that it quickly approaches the line that + passes through the point (1,tvAtEccentricOmegaMax) and has a slope of + slopeNearEccentricOmegaMax. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + -tvAtEccentricOmegaMax < 1.05 + -tvAtHalfOmegaMax > 0.45 or tvAtHalfOmegaMax < 0.05 + -slopeAtConcentricOmegaMax < 0 + -slopeNearEccentricOmegaMax < 0 + -slopeAtEccentricOmegaMax < 0 + -eccentricCurviness < 0 or eccentricCurviness > 1 + + References + Hill, A. V. (1938). The heat of shortening and the dynamic constants of + muscle. Proceedings of the Royal Society of London B: Biological Sciences, + 126(843), 136-195. + + */ + static void createTorqueVelocityCurve( + double tvAtEccentricOmegaMax, + double tvAtHalfConcentricOmegaMax, + double slopeAtConcentricOmegaMax, + double slopeNearEccentricOmegaMax, + double slopeAtEccentricOmegaMax, + double eccentricCurviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ); + + /** + This function creates a Bezier spline that closely follows the + exponential curves that are typically used to model the passive-torque-angle + characteristic of muscles. This curve has a value and a slope of zero + for angles that are less than abs(angleAtZeroTorque). For angles that + have an absolute magnitude larger than abs(angleAtOneNormTorque) the curve + is simply linearly extrapolated. + + Note that curves can be represented that increase left-to-right, or + decrease left-to-right by setting the variables angleAtOneNormTorque and + angleAtZeroTorque correctly. For example using (0,1) for + angleAtOneNormTorque and angleAtZeroTorque produces a curve that increases + left-to-right while using (-1,0) produces a curve that decreases left to + right. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_PassiveTorqueAngleCurveSimple.png + + @param angleAtZeroTorque is the angle at which the curve transitions from + a flat line and begins curving upwards. (radians) + + @param angleAtOneNormTorque is the angle at which this curve achieves a + value of 1.0. (radians) + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + - abs(angleAtOneNormTorque-angleAtZeroTorque) < sqrt(eps) + */ + static void createPassiveTorqueAngleCurve( + double angleAtZeroTorque, + double angleAtOneNormTorque, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ); + + +/** + This function creates a Bezier spline that closely follows the + exponential curves that are typically used to model the passive-torque-angle + characteristic of muscles. This curve has a value and a slope of zero + for angles that are less than abs(angleAtZeroTorque). For angles that + have an absolute magnitude larger than abs(angleAtOneNormTorque) the curve + is simply linearly extrapolated. + + Note that curves can be represented that increase left-to-right, or + decrease left-to-right by setting the variables angleAtOneNormTorque and + angleAtZeroTorque correctly. For example using (0,1) for + angleAtOneNormTorque and angleAtZeroTorque produces a curve that increases + left-to-right while using (-1,0) produces a curve that decreases left to + right. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_PassiveTorqueAngleCurve.png + + @param angleAtZeroTorque is the angle at which the curve transitions from + a flat line and begins curving upwards. (radians) + + @param angleAtOneNormTorque is the angle at which this curve achieves a + value of 1.0. (radians) + + @param stiffnessAtLowTorque + The normalized stiffness (or slope) of the curve achieves as it begins + to increase. This is usually chosen to be a small, but non-zero fraction + of stiffnessAtOneNormTorque + (stiffnessAtLowTorque = 0.025 stiffnessAtOneNormTorque is typical). + The sign of stiffnessAtLowTorque must be positive if + angleAtOneNormTorque > angleAtZeroPassiveTorque. The sign + of stiffnessAtLowTorque must be negative if + angleAtOneNormTorque < angleAtZeroPassiveTorque. + (Norm.Torque/radians) + + @param stiffnessAtOneNormTorque + The normalized stiffness (or slope) of the fiber curve + when the fiber is stretched by + angleAtOneNormTorque - angleAtZeroPassiveTorque. The sign + of stiffnessAtOneNormTorque must agree with stiffnessAtLowTorque. + (Norm.Torque/radians) + + @param curviness + The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow). A value of 0.5 is typical as it produces a + graceful curve. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + - abs(angleAtOneNormTorque-angleAtZeroTorque) < sqrt(eps) + - sign(stiffnessAtLowTorque) != sign(angleAtOneNormTorque-angleAtLowTorque) + - sign(stiffnessAtOneNormTorque) != sign(stiffnessAtLowTorque) + - abs(stiffnessAtLowTorque) > 0.9/abs(angleAtOneNormTorque-angleAtZeroTorque) + - abs(stiffnessAtOneTorque) <= 1.1/abs(angleAtOneNormTorque-angleAtZeroTorque) + - curviness < 0 or curviness > 1 + + */ + static void createPassiveTorqueAngleCurve( + double angleAtZeroTorque, + double angleAtOneNormTorque, + double stiffnessAtLowTorque, + double stiffnessAtOneNormTorque, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate ); + + + /** + This function produces a Bezier curve fitted to a Gaussian function. As the + tails of the Gaussian curve become small this curve is simply extrapolated + as a line with a y-value of zero and a slope of zero. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_GaussianActiveTorqueAngleCurveSimple.png + + @param angleAtOneNormTorque The angle at which the Gaussian curve develops + a value of 1. + + @param angularStandardDeviation The angular deviation from + the mean at which the Gaussian curve reaches a value of \f$e^{-1/2}\f$. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + - angularWidthOfOneStandardDeviation < sqrt(eps) + */ + static void createGaussianShapedActiveTorqueAngleCurve( + double angleAtOneNormTorque, + double angularStandardDeviation, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate + ); + + +/** + This function produces a C2 continuous Bezier curve fitted to a Gaussian + function. As the tails of the Gaussian curve become less than + minValueAtShoulders, the curve is linearly extrapolated at a sloe of + minSlopeOfShoulders. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_GaussianActiveTorqueAngleCurve.png + + @param angleAtOneNormTorque The angle at which the Gaussian curve develops + a value of 1. + + @param angularStandardDeviation The angular deviation from + the mean at which the Gaussian curve reaches a value of \f$e^{-1/2}\f$. + + @param minSlopeAtShoulders The y-value at which the Bezier curve transitions + from having a shape like a Gaussian curve to being linearly extrapolated. + + @param minValueAtShoulders The slope of the linear extrapolation of the + Bezier curve for y-values that are less than minSlopeAtShoulders. The + sign of minValueAtShoulders is automatically set so that it matches the + curve near it (see the figure). + + @param curviness + The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow). A value of 0.5 is typical as it produces a + graceful curve. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + - angularWidthOfOneStandardDeviation < sqrt(eps) + - minSlopeAtShoulders < 0 + - minValueAtShoulders < 0 + - curviness > 1 or curviness < 0 + + */ + static void createGaussianShapedActiveTorqueAngleCurve( + double angleAtOneNormTorque, + double angularStandardDeviation, + double minSlopeAtShoulders, + double minValueAtShoulders, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate + ); + + + /** + This function produces a normalized tendon-torque-angle curve with a + toe region that is in the range of \f$\mathbf{t}_V\f$ [0,1./3,] after which + the curve is linearly extrapolated. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurveSimple.png + + @param angularStretchAtOneNormTorque The amount of angular stretch of the + joint as the tendon goes from developing zero torque at its slack length + to developing one maximum isometric torque. (radians) + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + - angularWidthOfOneStandardDeviation < sqrt(eps) + + */ + static void createTendonTorqueAngleCurve( + double angularStretchAtOneNormTorque, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate + ); + + /** + This function produces a normalized tendon-torque-angle curve with a + toe region, final stiffness, and shape that can be controlled. + + \image html fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurve.png + + @param angularStretchAtOneNormTorque The amount of angular stretch of the + joint as the tendon goes from developing zero torque at its slack length + to developing one maximum isometric torque. (radians) + + @param stiffnessAtOneNormTorque The linear stiffness value of the tendon + that is used for all y-values greater than the toe region. + (Norm. Torque/rad) + + @param normTorqueAtToeEnd The normalized torque value which defines the + end of the nonlinear-stiffness region of the tendon and the beginning of + the linear stiffness region of the tendon. + + @param curviness + The dimensionless 'curviness' parameter that + can vary between 0 (a line) to 1 (a smooth, but + sharply bent elbow). A value of 0.5 is typical as it produces a + graceful curve. + + @param curveName The name of the joint torque this curve applies to. This + curve name should have the name of the joint and the + direction (e.g. hipExtensionTorqueMuscle) so that if + this curve ever causes an exception, a user friendly + error message can be displayed to the end user to help + them debug their model. + + @param smoothSegmentedFunctionToUpdate + A SmoothSegmentedFunction object that will be erased and filled with + the coefficients that are defined by this curve. + + aborts + - angularStretchAtOneNormTorque < sqrt(eps) + - stiffnessAtOneNormTorque < 1.1/angularStretchAtOneNormTorque + - normTorqueAtToeEnd < sqrt(eps) or normTorqueAtToeEnd > 0.99 + - curviness < 0 or curviness > 1 + + */ + static void createTendonTorqueAngleCurve( + double angularStretchAtOneNormTorque, + double stiffnessAtOneNormTorque, + double normTorqueAtToeEnd, + double curviness, + const std::string& curveName, + RigidBodyDynamics::Addons::Geometry::SmoothSegmentedFunction& + smoothSegmentedFunctionToUpdate + ); + +}; +} +} +} +#endif //TORQUEMUSCLEFUNCTIONFACTORY_H_ diff --git a/3rdparty/rbdl/addons/muscle/csvtools.cc b/3rdparty/rbdl/addons/muscle/csvtools.cc new file mode 100644 index 0000000..b8b5517 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/csvtools.cc @@ -0,0 +1,160 @@ +/* -------------------------------------------------------------------------- * + * csvtools.cpp * + * -------------------------------------------------------------------------- * + * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * + * See http://opensim.stanford.edu and the NOTICE file for more information. * + * OpenSim is developed at Stanford University and supported by the US * + * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * + * through the Warrior Web program. * + * * + * Copyright (c) 2005-2012 Stanford University and the Authors * + * Author(s): Matthew Millard * + * * + * Licensed under the Apache License, Version 2.0 (the "License"); you may * + * not use this file except in compliance with the License. You may obtain a * + * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * + * * + * Unless required by applicable law or agreed to in writing, software * + * distributed under the License is distributed on an "AS IS" BASIS, * + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * + * See the License for the specific language governing permissions and * + * limitations under the License. * + * -------------------------------------------------------------------------- */ +#include "csvtools.h" + + +std::vector > readMatrixFromFile( + const std::string& filename, + int startingRow) +{ + std::ifstream datafile; + datafile.open(filename.c_str()); + + + //SimTK::Matrix data; + std::vector > dataMatrix; + std::vector rowVector; + + if(datafile.is_open()) + { + std::string line; + std::string entry; + int row = 0; + int col = 0; + //int matrixRowNum = 0; + int matrixColNum = 1; + + //1. Size the matrix + + + getline(datafile,line); //get a line of text + + while(row < startingRow){ + getline(datafile,line); + row++; + } + + //parse it for all of the comma spots + unsigned pos1 = 0; + unsigned pos2 = 0; + do{ + pos2 = line.find(",",pos1); + //if this is the first time running this loop, count + //the number of columns + if(pos2 != std::string::npos && row == 0) + matrixColNum++; + + pos1 = pos2+1; + }while(pos2 != std::string::npos); + + //Initial matrix sizing + if(row == 0){ + //matrixRowNum = max(matrixColNum,20); + rowVector.resize(matrixColNum); + //dataMatrix.resize(matrixRowNum); + //data.resizeKeep(matrixRowNum, matrixColNum); + } + + + while(datafile.good()) + { + pos1 = 0; + pos2 = 0; + for(int i=0; i < matrixColNum; i++){ + pos2 = line.find(",",pos1); + if(pos2 == std::string::npos) + pos2 = line.length(); + entry = line.substr(pos1,pos2-pos1); + pos1 = pos2+1; + //data(row,i) = atof(entry.c_str()); + rowVector[i] = atof(entry.c_str()); + } + + //Resize the matrix if its too small for the next line + //if(row == matrixRowNum-1){ + // matrixRowNum = matrixRowNum*2; + // data.resizeKeep(matrixRowNum,matrixColNum); + //} + dataMatrix.push_back(rowVector); + + row++; + getline(datafile,line); + } + //data.resizeKeep(row,matrixColNum); + + } + datafile.close(); + return dataMatrix; +} + +void printMatrixToFile( + const std::vector >& dataMatrix, + const std::string& header, + const std::string& filename) +{ + + std::ofstream datafile; + datafile.open(filename.c_str()); + datafile << std::scientific; + datafile.precision(16); + if(header.length() > 1) + datafile << header << "\n"; + + for(int i = 0; i < dataMatrix.size(); i++){ + for(int j = 0; j < dataMatrix[0].size(); j++){ + if(j >& dataMatrix, + const std::string& header, + const std::string& filename) +{ + + std::ofstream datafile; + datafile.open(filename.c_str()); + //datafile << std::scientific; + //datafile.precision(16); + if(header.length() > 1) + datafile << header << "\n"; + + for(int i = 0; i < dataMatrix.size(); i++){ + for(int j = 0; j < dataMatrix[0].size(); j++){ + if(j +#include +#include +#include +#include +#include + +/** +This function will print cvs file of the matrix + data + +@params data: A vector of state vectors +@params filename: The name of the file to print +*/ +void printMatrixToFile( const std::vector >& dataMatrix, + const std::string& header, + const std::string& filename); + +void printMatrixToFile( const std::vector >& dataMatrix, + const std::string& header, + const std::string& filename); +/** +This function will read in a cvs file assuming that all entries are numbers. + +@params filename: The name of the file to print +@params startingRow: the index of first row that contains numeric data. + +@return: A matrix of data +*/ +std::vector > readMatrixFromFile( + const std::string& filename, + int startingRow); diff --git a/3rdparty/rbdl/addons/muscle/muscle.h b/3rdparty/rbdl/addons/muscle/muscle.h new file mode 100644 index 0000000..5df7f1e --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/muscle.h @@ -0,0 +1,12 @@ +//============================================================================== +/* + * RBDL - Rigid Body Dynamics Library: Addon : forceElements + * Copyright (c) 2016 Matthew Millard + * + * Licensed under the zlib license. See LICENSE for more details. + */ +#ifndef MUSCLE_H_ +#define MUSCLE_H_ + +#include "Millard2016TorqueMuscle.h" +#endif diff --git a/3rdparty/rbdl/addons/muscle/tests/CMakeLists.txt b/3rdparty/rbdl/addons/muscle/tests/CMakeLists.txt new file mode 100644 index 0000000..00865b0 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/tests/CMakeLists.txt @@ -0,0 +1,78 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 2.6) + +CMAKE_POLICY(SET CMP0048 NEW) +CMAKE_POLICY(SET CMP0040 NEW) + +SET ( RBDL_ADDON_MUSCLE_TESTS_VERSION_MAJOR 1 ) +SET ( RBDL_ADDON_MUSCLE_TESTS_VERSION_MINOR 0 ) +SET ( RBDL_ADDON_MUSCLE_TESTS_VERSION_PATCH 0 ) + +SET ( RBDL_ADDON_MUSCLE_VERSION + ${RBDL_ADDON_MUSCLE_TESTS_VERSION_MAJOR}.${RBDL_ADDON_MUSCLE_TESTS_VERSION_MINOR}.${RBDL_ADDON_MUSCLE_TESTS_VERSION_PATCH} +) + + +PROJECT (RBDL_MUSCLE_TESTS VERSION ${RBDL_ADDON_MUSCLE_VERSION}) +#SET (PROJECT_VERSION ${RBDL_ADDON_MUSCLE_TESTS_VERSION}) + +# Needed for UnitTest++ +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../CMake ) + +# Look for unittest++ +FIND_PACKAGE (UnitTest++ REQUIRED) +INCLUDE_DIRECTORIES (${UNITTEST++_INCLUDE_DIR}) + +SET ( MUSCLE_TESTS_SRCS + testMillard2016TorqueMuscle.cc + testMuscleFunctionFactory.cc + testTorqueMuscleFunctionFactory.cc + ../muscle.h + ../MuscleFunctionFactory.h + ../MuscleFunctionFactory.cc + ../TorqueMuscleFunctionFactory.h + ../TorqueMuscleFunctionFactory.cc + ../Millard2016TorqueMuscle.h + ../Millard2016TorqueMuscle.cc + ../csvtools.h + ../csvtools.cc + ../../geometry/geometry.h + ../../geometry/SegmentedQuinticBezierToolkit.h + ../../geometry/SmoothSegmentedFunction.h + ../../geometry/SegmentedQuinticBezierToolkit.cc + ../../geometry/SmoothSegmentedFunction.cc + ../../geometry/tests/numericalTestFunctions.cc + ../../geometry/tests/numericalTestFunctions.h + ) + +INCLUDE_DIRECTORIES ( ../../../geometry ) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX +) + +ADD_EXECUTABLE ( rbdl_muscle_tests ${MUSCLE_TESTS_SRCS} ) + +SET_TARGET_PROPERTIES ( rbdl_muscle_tests PROPERTIES + LINKER_LANGUAGE CXX + OUTPUT_NAME runMuscleTests + ) + +SET (RBDL_LIBRARY rbdl) +IF (RBDL_BUILD_STATIC) + SET (RBDL_LIBRARY rbdl-static) +ENDIF (RBDL_BUILD_STATIC) + +TARGET_LINK_LIBRARIES ( rbdl_muscle_tests + ${UNITTEST++_LIBRARY} + ${RBDL_LIBRARY} + ) + +OPTION (RUN_AUTOMATIC_TESTS "Perform automatic tests after compilation?" OFF) + +IF (RUN_AUTOMATIC_TESTS) +ADD_CUSTOM_COMMAND (TARGET rbdl_muscle_tests + POST_BUILD + COMMAND ./runMuscleTests + COMMENT "Running automated addon muscle tests..." + ) +ENDIF (RUN_AUTOMATIC_TESTS) diff --git a/3rdparty/rbdl/addons/muscle/tests/testMillard2016TorqueMuscle.cc b/3rdparty/rbdl/addons/muscle/tests/testMillard2016TorqueMuscle.cc new file mode 100644 index 0000000..86e4316 --- /dev/null +++ b/3rdparty/rbdl/addons/muscle/tests/testMillard2016TorqueMuscle.cc @@ -0,0 +1,725 @@ +/* * + * TorqueMuscle + * Copyright (c) 2016 Matthew Millard + * + * Licensed under the zlib license. See LICENSE for more details. + */ + + +//============================================================================== +// INCLUDES +//============================================================================== + + + +#include "../Millard2016TorqueMuscle.h" +#include "../csvtools.h" +#include "../../geometry/tests/numericalTestFunctions.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +using namespace RigidBodyDynamics::Addons::Muscle; +using namespace RigidBodyDynamics::Addons::Geometry; +using namespace std; +/* + Constructor tests: + 1. Coefficients are copied over correctly. + 2. Curves are made correctly + + calcTorqueMuscleInfo test + stiffness calculation + power calculation + +*/ + +TEST(ConstructorRegularCallCheck) +{ + + + //Check that the 3 constructors when called properly + //do not abort + Millard2016TorqueMuscle test0 = Millard2016TorqueMuscle(); + + + SubjectInformation subjectInfo; + subjectInfo.gender = GenderSet::Male; + subjectInfo.ageGroup = AgeGroupSet::Young18To25; + subjectInfo.heightInMeters = 1.732; + subjectInfo.massInKg = 69.0; + + + Millard2016TorqueMuscle test2 = + Millard2016TorqueMuscle( + DataSet::Anderson2007, + subjectInfo, + Anderson2007::HipExtension, + 0.0, + 1.0, + 1.0, + "test_easyConstructor"); + + CHECK(abs( test2.getPassiveTorqueScale()-1.0) < TOL); + +} + + + +TEST(calcJointTorqueCorrectnessTests){ + + + double jointAngleOffset = 0; + double signOfJointAngle = 1; + double signOfJointTorque = 1; + double err = 0.0; + + std::string name("test"); + + SubjectInformation subjectInfo; + subjectInfo.gender = GenderSet::Male; + subjectInfo.ageGroup = AgeGroupSet::Young18To25; + subjectInfo.heightInMeters = 1.732; + subjectInfo.massInKg = 69.0; + + Millard2016TorqueMuscle tq = + Millard2016TorqueMuscle(DataSet::Anderson2007, + subjectInfo, + Anderson2007::HipExtension, + jointAngleOffset, + signOfJointAngle, + signOfJointTorque, + name); + + bool flagMakeTestVector = false; + if(flagMakeTestVector){ + Millard2016TorqueMuscle tqG = + Millard2016TorqueMuscle(DataSet::Gymnast, + subjectInfo, + Gymnast::HipExtension, + jointAngleOffset, + signOfJointAngle, + signOfJointTorque, + name); + TorqueMuscleInfo tmiG; + tqG.calcTorqueMuscleInfo(M_PI/3.0,0.1,0.77,tmiG); + + printf("%f\n",tmiG.fiberAngle); + printf("%f\n",tmiG.fiberAngularVelocity); + printf("%f\n",tmiG.activation); + printf("%f\n",tmiG.fiberTorque); + printf("%f\n",tmiG.fiberStiffness); + printf("%f\n",tmiG.fiberPassiveTorqueAngleMultiplier); + printf("%f\n",tmiG.fiberActiveTorqueAngleMultiplier); + printf("%f\n",tmiG.fiberActiveTorqueAngularVelocityMultiplier); + printf("%f\n",tmiG.fiberPassiveTorque); + printf("%f\n",tmiG.fiberActiveTorque); + printf("%f\n",tmiG.fiberDampingTorque); + printf("%f\n",tmiG.fiberNormDampingTorque); + printf("%f\n",tmiG.fiberActivePower); + printf("%f\n",tmiG.fiberPassivePower); + printf("%f\n",tmiG.fiberPower); + printf("%f\n",tmiG.DjointTorqueDjointAngle); + printf("%f\n",tmiG.DjointTorqueDjointAngularVelocity); + printf("%f\n",tmiG.DjointTorqueDactivation); + + } + + + //Zero out the passive forces so that calcMuscleTorque reports + //just the active force - this allows us to test its correctness. + tq.setPassiveTorqueScale(0.0); + + //Test that the get and set functions work for + //maximum isometric torque + double tauMaxOld = tq.getMaximumActiveIsometricTorque(); + double tauMax = tauMaxOld*10.0; + tq.setMaximumActiveIsometricTorque(tauMax); + CHECK(abs( tq.getMaximumActiveIsometricTorque()-tauMax) + < TOL ); + + //getParametersC1C2C3C4C5C6() has been removed and so this + //test is no longer possible. It is the responsibility of + //the underlying active-torque-angle curve to ensure that + //it peaks at 1.0 + /* + RigidBodyDynamics::Math::VectorNd c1c2c3c4c5c6 = + tq.getParametersC1C2C3C4C5C6(); + double thetaAtTauMax = c1c2c3c4c5c6[2]; + */ + //TorqueMuscleInfo tqInfo; + + //getParametersC1C2C3C4C5C6() has been removed. It is the + //responsibility of the underlying curve test code to + //check for correctness. + //double torque = tq.calcJointTorque(thetaAtTauMax,0.0,1.0); + //err = abs(torque -tauMax); + //CHECK(err< TOL); + + //These tests have been updated because Anderson + //torque velocity curve had to be replaced because it + //can behave badly (e.g. the concentric curve of the ankle + //extensors of a young man does not interest the x axis.) + // + //The new curves do not pass exactly through the points + //0.5*tauMax and 0.75*tauMax, but within 5% of this + //value. + + //getParametersC1C2C3C4C5C6() has been removed. It is the + //responsibility of the underlying curve test code to + //check for correctness. + //double omegaAt75TauMax = c1c2c3c4c5c6[3]; + //torque = tq.calcJointTorque(thetaAtTauMax,omegaAt75TauMax,1.0); + //err = abs(torque - 0.75*tauMax)/tauMax; + //CHECK( err < 0.05); + + //double omegaAt50TauMax = c1c2c3c4c5c6[4]; + //torque = tq.calcJointTorque(thetaAtTauMax,omegaAt50TauMax,1.0); + //err = abs(torque -0.50*tauMax)/tauMax; + //CHECK(err < 0.05); + + +} + +TEST(calcTorqueMuscleInfoCorrectnessTests){ + + double jointAngleOffset = 0; + double signOfJointAngle = 1; + double signOfJointTorque = 1; + double signOfJointVelocity = signOfJointTorque; + + std::string name("test"); + + SubjectInformation subjectInfo; + subjectInfo.gender = GenderSet::Female; + subjectInfo.ageGroup = AgeGroupSet::SeniorOver65; + subjectInfo.heightInMeters = 1.732; + subjectInfo.massInKg = 69.0; + + Millard2016TorqueMuscle tq = + Millard2016TorqueMuscle(DataSet::Anderson2007, + subjectInfo, + Anderson2007::HipExtension, + jointAngleOffset, + signOfJointAngle, + signOfJointTorque, + name); + double jointAngle = 0.; + double jointVelocity = 0.; + double activation = 1.0; + + tq.setPassiveTorqueScale(0.5); + tq.calcJointTorque(0,0,1.0); + tq.setPassiveTorqueScale(1.0); + tq.calcJointTorque(0,0,1.0); + + double tauMax = tq.getMaximumActiveIsometricTorque(); + //RigidBodyDynamics::Math::VectorNd c1c2c3c4c5c6 = + // tq.getParametersC1C2C3C4C5C6(); + + //RigidBodyDynamics::Math::VectorNd b1k1b2k2 = + // tq.getParametersB1K1B2K2(); + + /* + int idx = -1; + if(b1k1b2k2[0] > 0){ + idx = 0; + }else if(b1k1b2k2[2] > 0){ + idx = 2; + } + */ + + /* + double b = b1k1b2k2[idx]; + double k = b1k1b2k2[idx+1]; + double thetaAtPassiveTauMax = log(tauMax/b)/k; + + double thetaAtTauMax = c1c2c3c4c5c6[2]; + double omegaAt75TauMax = c1c2c3c4c5c6[3]; + double omegaAt50TauMax = c1c2c3c4c5c6[4]; + + double jointAngleAtTauMax = + signOfJointAngle*thetaAtTauMax+jointAngleOffset; + */ + + double jointAngleAtTauMax = tq.getJointAngleAtMaximumActiveIsometricTorque(); + TorqueMuscleInfo tmi; + tq.calcTorqueMuscleInfo(jointAngleAtTauMax, + 0., + activation, + tmi); + + //Keypoint check: active force components + fiber kinematics + + CHECK(abs(tmi.activation-1) < EPSILON); + CHECK(abs(tmi.jointAngle-jointAngleAtTauMax)< TOL); + CHECK(abs(tmi.jointAngularVelocity-0.) < TOL); + + CHECK(abs(tmi.activation-1) < EPSILON); + //CHECK(abs(tmi.fiberAngle-thetaAtTauMax) < TOL); + CHECK(abs(tmi.fiberAngularVelocity-0.) < TOL); + + CHECK(abs(tmi.fiberActiveTorque - tauMax) < TOL); + CHECK(abs(tmi.fiberActiveTorqueAngleMultiplier-1.0) < TOL); + CHECK(abs(tmi.fiberActiveTorqueAngularVelocityMultiplier-1.0) muscleVector; + + bool exception = false; + + double angleTorqueSigns[][2] = {{-1, 1}, + {-1,-1}, + { 1,-1}, + { 1, 1}, + {-1, 1}, + {-1,-1}}; + + Millard2016TorqueMuscle tqMuscle; + std::stringstream tqName; + int tqIdx; + + for(int i=0; i < Anderson2007::LastJointTorque; ++i){ + + tqName.str(""); + tqName << DataSet.names[0] + < +#include +#include +#include +#include +#include +#include + +using namespace RigidBodyDynamics::Addons::Geometry; +using namespace RigidBodyDynamics::Addons::Muscle; +using namespace std; +//using namespace OpenSim; +//using namespace SimTK; + +/* +static double EPSILON = numeric_limits::epsilon(); + +static bool FLAG_PLOT_CURVES = false; +static string FILE_PATH = ""; +static double TOL_DX = 5e-3; +static double TOL_DX_BIG = 1e-2; +static double TOL_BIG = 1e-6; +static double TOL_SMALL = 1e-12; +*/ + + + +TEST(tendonCurve) +{ + //cout <<"**************************************************"< +#include +#include +#include +#include +#include +#include + +using namespace RigidBodyDynamics::Addons::Geometry; +using namespace RigidBodyDynamics::Addons::Muscle; +using namespace std; + +/* +static double EPSILON = numeric_limits::epsilon(); + +static bool FLAG_PLOT_CURVES = false; +static string FILE_PATH = ""; +static double TOL_DX = 5e-3; +static double TOL_DX_BIG = 1e-2; +static double TOL_BIG = 1e-6; +static double TOL_SMALL = 1e-12; +*/ + +TEST(Anderson2007ActiveTorqueAngleCurve) +{ + double subjectWeight = 75.0*9.81; + double subjectHeight = 1.75; + double scale = subjectHeight*subjectWeight; + + //These parameters are taken from table 3 for hip extension for + //men between the ages of 18-25 + double c1 = 0.161; //normalized maximum hip joint torque + double c2 = 0.958; // pi/(theta_max - theta_min) + double c3 = 0.932; //theta_max_iso_torque + double c4 = 1.578; //omega_1: angular velocity at 75% tq_iso_max + double c5 = 3.190; //omega_2: angular velocity at 50% tq_iso_max + double c6 = 0.242; //E, where eccentric slope = (1+E)*concentricSlope + //Passive torque angle curve parameters + double b1 =-1.210; // torque_passive = b1*exp(k1*theta) + double k1 =-6.351; // +b2*exp(k2*theta) + double b2 = 0.476; + double k2 = 5.910; + + + + //cout < tauMax){ + double tmp = tauMin; + tauMin = tauMax; + tauMax = tmp; + tauMinAngle = angleMax; + } + + CHECK( abs(tauMin) < TOL_SMALL); + CHECK( abs(tauMax - 1.0) < TOL_SMALL); + CHECK( abs(andersonTpCurve.calcDerivative(tauMinAngle,1)) < TOL_SMALL); + + //cout << " passed " << endl; + + //cout << " Continuity and Smoothness Testing " << endl; + bool areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + andersonTpCurve, + andersonTpCurveSample, + TOL_DX); + CHECK(areCurveDerivativesGood); + + bool curveIsContinuous = isCurveC2Continuous(andersonTpCurve, + andersonTpCurveSample, + TOL_BIG); + CHECK(curveIsContinuous); + + bool curveIsMonotonic = isCurveMontonic(andersonTpCurveSample); + CHECK(curveIsMonotonic); + //cout << " passed " << endl; + + + } + + SmoothSegmentedFunction andersonTpCurve = SmoothSegmentedFunction(); + TorqueMuscleFunctionFactory:: + createAnderson2007PassiveTorqueAngleCurve( + scale, + c1, + b1, + k1, + b2, + k2, + "test_passiveTorqueAngleCurve", + andersonTpCurve); + + if(FLAG_PLOT_CURVES){ + andersonTpCurve.printCurveToCSVFile( + FILE_PATH, + "anderson2007PassiveTorqueAngleCurve", + andersonTpCurve.getCurveDomain()[0]-0.1, + andersonTpCurve.getCurveDomain()[1]+0.1); + } + +} + +TEST(Anderson2007ActiveTorqueVelocityCurve) +{ + double subjectWeight = 75.0*9.81; + double subjectHeight = 1.75; + double scale = subjectHeight*subjectWeight; + + //These parameters are taken from table 3 for hip extension for + //men between the ages of 18-25 + double c1 = 0.161; //normalized maximum hip joint torque + double c2 = 0.958; // pi/(theta_max - theta_min) + double c3 = 0.932; //theta_max_iso_torque + double c4 = 1.578; //omega_1: angular velocity at 75% tq_iso_max + double c5 = 3.190; //omega_2: angular velocity at 50% tq_iso_max + double c6 = 0.242; //E, where eccentric slope = (1+E)*concentricSlope + //Passive torque angle curve parameters + double b1 =-1.210; // torque_passive = b1*exp(k1*theta) + double k1 =-6.351; // +b2*exp(k2*theta) + double b2 = 0.476; + double k2 = 5.910; + + //cout <= minEccentricMultiplier); + CHECK(maxTv <= maxEccentricMultiplier); + + CHECK(abs(andersonTvCurve.calcDerivative + (angularVelocityMax/angularVelocityMax,1)) wmaxC){ + CHECK( abs( tv.calcValue(w) ) < TOL_SMALL); + CHECK( abs( tv.calcDerivative(w,1) ) < TOL_SMALL); + CHECK( abs(tvX.calcDerivative(w,1) + - slopeAtConcentricOmegaMax ) < TOL_BIG); + }else if(w > 0 && w <= wmaxC){ + tvHill = (b*fiso-a*w)/(b+w); + errHill = abs(tv.calcValue(w)-tvHill); + //printf("%i. Err %f, ",i,errHill); + CHECK( errHill < 0.02); + errHill = abs(tvX.calcValue(w)-tvHill); + //printf(" Err %f\n",errHill); + CHECK( errHill < 0.02); + }else if(w < 0 & w > wmaxE){ + CHECK(tv.calcValue(w) > 1.0); + }else if(w < wmaxE){ + CHECK(abs( tv.calcValue(w) - tvAtEccentricOmegaMax ) < TOL_SMALL); + CHECK(abs( tv.calcDerivative(w,1) - 0.0 ) < TOL_SMALL); + //CHECK(abs( tvX.calcValue(w) - tvAtEccentricOmegaMax ) ); + CHECK(abs( tvX.calcDerivative(w,1) + - slopeAtEccentricOmegaMax ) < TOL_SMALL ); + } + } + + RigidBodyDynamics::Math::VectorNd curveDomain = tv.getCurveDomain(); + + double angularVelocityMin = curveDomain[0]; + double angularVelocityMax = curveDomain[1]; + + + RigidBodyDynamics::Math::MatrixNd tvCurveSample + = tv.calcSampledCurve( 6, + angularVelocityMin-0.1, + angularVelocityMax+0.1); + + bool areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + tv, + tvCurveSample, + TOL_DX); + + CHECK(areCurveDerivativesGood); + + bool curveIsContinuous = isCurveC2Continuous( tv, + tvCurveSample, + TOL_BIG); + CHECK(curveIsContinuous); + + bool curveIsMonotonic = isCurveMontonic(tvCurveSample); + CHECK(curveIsMonotonic); + + if(FLAG_PLOT_CURVES){ + tv.printCurveToCSVFile( + FILE_PATH, + "millard2016TorqueVelocityCurve", + -1.1, + 1.1); + } + tv.printCurveToCSVFile( + "", + "millard2016TorqueVelocityCurve", + -1.1, + 1.1); + +} + +//============================================================================== +TEST(PassiveTorqueAngleCurve) +{ + SmoothSegmentedFunction tp = SmoothSegmentedFunction(); + SmoothSegmentedFunction tpX = SmoothSegmentedFunction(); + double angleAtZeroTorque0 = 0; + double angleAtOneNormTorque0 = -M_PI; + + double angleAtZeroTorque1 = 0; + double angleAtOneNormTorque1 = M_PI; + + double stiffnessAtOneNormTorque1 = + 5.6/(angleAtOneNormTorque1-angleAtZeroTorque1); + double stiffnessAtLowTorque1 = + 0.05*stiffnessAtOneNormTorque1; + double curviness1 = 0.75; + + std::string curveName0("tpTest0"); + std::string curveName1("tpTest1"); + + + TorqueMuscleFunctionFactory::createPassiveTorqueAngleCurve( + angleAtZeroTorque0, + angleAtOneNormTorque0, + curveName0, + tp); + + TorqueMuscleFunctionFactory::createPassiveTorqueAngleCurve( + angleAtZeroTorque1, + angleAtOneNormTorque1, + stiffnessAtLowTorque1, + stiffnessAtOneNormTorque1, + curviness1, + curveName1, + tpX); + + CHECK( abs(tp.calcValue(angleAtZeroTorque0)) < TOL_SMALL); + CHECK( abs(tp.calcValue(angleAtOneNormTorque0)-1.0) < TOL_SMALL); + + CHECK( abs(tpX.calcValue(angleAtZeroTorque1)) < TOL_SMALL); + CHECK( abs(tpX.calcValue(angleAtOneNormTorque1) - 1.0) < TOL_SMALL); + CHECK( abs(tpX.calcDerivative(angleAtZeroTorque1,1)) < TOL_SMALL); + CHECK( abs(tpX.calcDerivative(angleAtOneNormTorque1,1) + -stiffnessAtOneNormTorque1) < TOL_SMALL); + + RigidBodyDynamics::Math::VectorNd curveDomain0 = tp.getCurveDomain(); + RigidBodyDynamics::Math::VectorNd curveDomain1 = tpX.getCurveDomain(); + + RigidBodyDynamics::Math::MatrixNd tpSample0 + = tp.calcSampledCurve( 6, + curveDomain0[0]-0.1, + curveDomain0[1]+0.1); + + RigidBodyDynamics::Math::MatrixNd tpSample1 + = tpX.calcSampledCurve( 6, + curveDomain1[0]-0.1, + curveDomain1[1]+0.1); + + bool areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + tp, + tpSample0, + TOL_DX); + + CHECK(areCurveDerivativesGood); + + areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + tpX, + tpSample1, + TOL_DX); + + CHECK(areCurveDerivativesGood); + bool curveIsContinuous = isCurveC2Continuous( tp, + tpSample0, + TOL_BIG); + CHECK(curveIsContinuous); + curveIsContinuous = isCurveC2Continuous(tpX, + tpSample1, + TOL_BIG); + CHECK(curveIsContinuous); + + bool curveIsMonotonic = isCurveMontonic(tpSample0); + CHECK(curveIsMonotonic); + + curveIsMonotonic = isCurveMontonic(tpSample1); + CHECK(curveIsMonotonic); + + + if(FLAG_PLOT_CURVES){ + tp.printCurveToCSVFile( + FILE_PATH, + "millard2016PassiveTorqueAngleCurve", + curveDomain0[0]-0.1, + curveDomain0[1]+0.1); + } +} + +//============================================================================== +TEST(ActiveTorqueAngleCurve) +{ + SmoothSegmentedFunction ta = SmoothSegmentedFunction(); + SmoothSegmentedFunction taX = SmoothSegmentedFunction(); + double angleAtOneNormTorque = 1.5; + double angularStandardDeviation = 1.0; + double angularStandardDeviationSq = + angularStandardDeviation*angularStandardDeviation; + + double minSlopeAtShoulders = 0.2; + double minValueAtShoulders = 0.1; + double xTrans = sqrt(-log(minValueAtShoulders)*2*angularStandardDeviationSq); + double delta = abs(xTrans+angleAtOneNormTorque); + double curviness = 0.75; + + std::string curveName0("tpTest0"); + std::string curveName1("tpTest1"); + + + TorqueMuscleFunctionFactory::createGaussianShapedActiveTorqueAngleCurve( + angleAtOneNormTorque, + angularStandardDeviation, + curveName0, + ta); + + TorqueMuscleFunctionFactory::createGaussianShapedActiveTorqueAngleCurve( + angleAtOneNormTorque, + angularStandardDeviation, + minSlopeAtShoulders, + minValueAtShoulders, + curviness, + curveName1, + taX); + + CHECK(abs(ta.calcValue(angleAtOneNormTorque)-1.0) < TOL_SMALL); + + CHECK(abs(ta.calcValue(angleAtOneNormTorque + +10.0*angularStandardDeviation)) < TOL_SMALL); + CHECK(abs(ta.calcValue(angleAtOneNormTorque + -10.0*angularStandardDeviation)) < TOL_SMALL); + + CHECK(abs(taX.calcValue(angleAtOneNormTorque)-1.0) < TOL_SMALL); + + double err = abs(taX.calcDerivative(angleAtOneNormTorque + delta,1) + + minSlopeAtShoulders); + CHECK(err < TOL_SMALL); + err = abs(taX.calcDerivative(angleAtOneNormTorque - delta,1) + - minSlopeAtShoulders); + CHECK(err < TOL_SMALL); + + + RigidBodyDynamics::Math::VectorNd curveDomain0 = ta.getCurveDomain(); + RigidBodyDynamics::Math::VectorNd curveDomain1 = taX.getCurveDomain(); + + RigidBodyDynamics::Math::MatrixNd taSample0 + = ta.calcSampledCurve( 6, + curveDomain0[0]-0.1, + curveDomain0[1]+0.1); + + RigidBodyDynamics::Math::MatrixNd taSample1 + = taX.calcSampledCurve( 6, + curveDomain1[0]-0.1, + curveDomain1[1]+0.1); + + bool areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + ta, + taSample0, + TOL_DX); + + + CHECK(areCurveDerivativesGood); + + areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + taX, + taSample1, + TOL_DX); + + CHECK(areCurveDerivativesGood); + bool curveIsContinuous = isCurveC2Continuous( ta, + taSample0, + TOL_BIG); + CHECK(curveIsContinuous); + curveIsContinuous = isCurveC2Continuous(taX, + taSample1, + TOL_BIG); + CHECK(curveIsContinuous); + + if(FLAG_PLOT_CURVES){ + ta.printCurveToCSVFile( + FILE_PATH, + "millard2016ActiveTorqueAngleCurve", + curveDomain0[0]-0.1, + curveDomain0[1]+0.1); + } + +} + +//============================================================================== +TEST(TendonTorqueAngleCurve) +{ + SmoothSegmentedFunction tt = SmoothSegmentedFunction(); + SmoothSegmentedFunction ttX = SmoothSegmentedFunction(); + + double angularStretchAtOneNormTorque = M_PI/2.0; + double stiffnessAtOneNormTorque = 2.5/angularStretchAtOneNormTorque; + double normTorqueAtToeEnd = 2.0/3.0; + double curviness = 0.5; + + std::string curveName0("ttTest0"); + std::string curveName1("ttTest1"); + + + TorqueMuscleFunctionFactory::createTendonTorqueAngleCurve( + angularStretchAtOneNormTorque, + curveName0, + tt); + + TorqueMuscleFunctionFactory::createTendonTorqueAngleCurve( + angularStretchAtOneNormTorque, + stiffnessAtOneNormTorque, + normTorqueAtToeEnd, + curviness, + curveName1, + ttX); + + CHECK(abs(tt.calcValue(0)) < TOL_SMALL); + CHECK(abs(ttX.calcValue(0)) < TOL_SMALL); + + CHECK(abs(tt.calcValue(angularStretchAtOneNormTorque)-1.0) < TOL_SMALL); + CHECK(abs(ttX.calcValue(angularStretchAtOneNormTorque)-1.0) < TOL_SMALL); + + CHECK(abs(ttX.calcDerivative(angularStretchAtOneNormTorque,1) + - stiffnessAtOneNormTorque) < TOL_SMALL); + + RigidBodyDynamics::Math::VectorNd curveDomain0 = tt.getCurveDomain(); + RigidBodyDynamics::Math::VectorNd curveDomain1 = ttX.getCurveDomain(); + + RigidBodyDynamics::Math::MatrixNd ttSample0 + = tt.calcSampledCurve( 6, + curveDomain0[0]-0.1, + curveDomain0[1]+0.1); + + RigidBodyDynamics::Math::MatrixNd ttSample1 + = ttX.calcSampledCurve( 6, + curveDomain1[0]-0.1, + curveDomain1[1]+0.1); + + bool areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + tt, + ttSample0, + TOL_DX); + + CHECK(areCurveDerivativesGood); + + areCurveDerivativesGood = + areCurveDerivativesCloseToNumericDerivatives( + ttX, + ttSample1, + TOL_DX); + + CHECK(areCurveDerivativesGood); + bool curveIsContinuous = isCurveC2Continuous( tt, + ttSample0, + TOL_BIG); + CHECK(curveIsContinuous); + curveIsContinuous = isCurveC2Continuous(ttX, + ttSample1, + TOL_BIG); + CHECK(curveIsContinuous); + + bool curveIsMonotonic = isCurveMontonic(ttSample0); + CHECK(curveIsMonotonic); + + curveIsMonotonic = isCurveMontonic(ttSample1); + CHECK(curveIsMonotonic); + + + if(FLAG_PLOT_CURVES){ + tt.printCurveToCSVFile( + FILE_PATH, + "millard2016ActiveTorqueAngleCurve", + curveDomain0[0]-0.1, + angularStretchAtOneNormTorque); + } + +} diff --git a/3rdparty/rbdl/addons/urdfreader/CMake/FindURDF.cmake b/3rdparty/rbdl/addons/urdfreader/CMake/FindURDF.cmake new file mode 100644 index 0000000..7ce4e85 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/CMake/FindURDF.cmake @@ -0,0 +1,88 @@ +# - Try to find URDF +# +# + +SET (URDF_FOUND FALSE) +SET (CONSOLE_BRIDGE_FOUND FALSE) +SET (URDFDOM_HEADERS_FOUND FALSE) +SET (URDFDOM_FOUND FALSE) + +FIND_PATH (CONSOLE_BRIDGE_DIR console_bridge/console.h + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (CONSOLE_BRIDGE_LIBRARY NAMES console_bridge PATHS + /usr/local/include + /usr/include + ) + +FIND_PATH (URDFDOM_HEADERS_DIR urdf_model/model.h + /usr/local/include + /usr/include + ) + +FIND_PATH (URDFDOM_DIR urdf_parser/urdf_parser.h + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (URDFDOM_MODEL_LIBRARY NAMES urdfdom_model PATHS + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (URDFDOM_WORLD_LIBRARY NAMES urdfdom_world PATHS + /usr/local/include + /usr/include + ) + +IF (NOT CONSOLE_BRIDGE_DIR OR NOT CONSOLE_BRIDGE_LIBRARY) + MESSAGE(ERROR "Could not find URDF: console_bridge not found") +ELSE (NOT CONSOLE_BRIDGE_DIR OR NOT CONSOLE_BRIDGE_LIBRARY) + SET (CONSOLE_BRIDGE_FOUND TRUE) +ENDIF (NOT CONSOLE_BRIDGE_DIR OR NOT CONSOLE_BRIDGE_LIBRARY) + +IF (NOT URDFDOM_HEADERS_DIR) + MESSAGE(ERROR "Could not find URDF: urdfdom_headers not found") +ELSE (NOT URDFDOM_HEADERS_DIR) + SET (URDFDOM_HEADERS_FOUND TRUE) +ENDIF (NOT URDFDOM_HEADERS_DIR) + +IF (NOT URDFDOM_DIR OR NOT URDFDOM_MODEL_LIBRARY OR NOT URDFDOM_WORLD_LIBRARY) + MESSAGE(ERROR "Could not find URDF: urdfdom_model or urdfdom_world library not found") +ELSE (NOT URDFDOM_DIR OR NOT URDFDOM_MODEL_LIBRARY OR NOT URDFDOM_WORLD_LIBRARY) + SET (URDFDOM_FOUND TRUE) +ENDIF (NOT URDFDOM_DIR OR NOT URDFDOM_MODEL_LIBRARY OR NOT URDFDOM_WORLD_LIBRARY) + +IF (CONSOLE_BRIDGE_FOUND AND URDFDOM_HEADERS_FOUND AND URDFDOM_FOUND) + SET (URDF_FOUND TRUE) +ENDIF (CONSOLE_BRIDGE_FOUND AND URDFDOM_HEADERS_FOUND AND URDFDOM_FOUND) + +IF (URDF_FOUND) + IF (NOT URDF_FIND_QUIETLY) + MESSAGE(STATUS "Found URDF console_bridge: ${CONSOLE_BRIDGE_LIBRARY}") + MESSAGE(STATUS "Found URDF urdfdom_headers: ${URDFDOM_HEADERS_DIR}") + MESSAGE(STATUS "Found URDF urdfdom: ${URDFDOM_LIBRARY}") + ENDIF (NOT URDF_FIND_QUIETLY) + SET (URDF_INCLUDE_DIRS + ${CONSOLE_BRIDGE_DIR} + ${URDFDOM_HEADERS_DIR} + ${URDFDOM_DIR} + ) + SET (URDF_LIBRARIES + ${CONSOLE_BRIDGE_LIBRARY} + ${URDFDOM_WORLD_LIBRARY} + ${URDFDOM_MODEL_LIBRARY} + ) + MESSAGE (STATUS "URDF Libraries: ${URDF_LIBRARIES}") +ELSE (URDF_FOUND) + IF (URDF_FIND_REQUIRED) + MESSAGE(FATAL_ERROR "Could not find URDF") + ENDIF (URDF_FIND_REQUIRED) +ENDIF (URDF_FOUND) + +MARK_AS_ADVANCED ( + ${URDF_INCLUDE_DIRS} + ${URDF_LIBRARIES} + ) diff --git a/3rdparty/rbdl/addons/urdfreader/CMake/pkg-config.cmake b/3rdparty/rbdl/addons/urdfreader/CMake/pkg-config.cmake new file mode 100644 index 0000000..113ca7b --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/CMake/pkg-config.cmake @@ -0,0 +1,369 @@ +# Copyright (C) 2010 Thomas Moulard, JRL, CNRS/AIST. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +INCLUDE(CMake/shared-library.cmake) + +FIND_PACKAGE(PkgConfig) + +# Additional pkg-config variables whose value will be imported +# during the dependency check. +SET(PKG_CONFIG_ADDITIONAL_VARIABLES datarootdir pkgdatarootdir docdir doxygendocdir) + +# _SETUP_PROJECT_PKG_CONFIG +# ------------------------- +# +# Prepare pkg-config pc file generation step. +# +MACRO(_SETUP_PROJECT_PKG_CONFIG) + # Pkg-config related commands. + SET(PKG_CONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") + SET(PKG_CONFIG_EXEC_PREFIX "${PKG_CONFIG_PREFIX}") + SET(PKG_CONFIG_LIBDIR "${PKG_CONFIG_EXEC_PREFIX}/lib") + SET(PKG_CONFIG_INCLUDEDIR "${PKG_CONFIG_PREFIX}/include") + SET(PKG_CONFIG_DATAROOTDIR "${PKG_CONFIG_PREFIX}/share") + SET(PKG_CONFIG_PKGDATAROOTDIR "${PKG_CONFIG_PREFIX}/share/${PROJECT_NAME}") + SET(PKG_CONFIG_DOCDIR "${PKG_CONFIG_DATAROOTDIR}/doc/${PROJECT_NAME}") + SET(PKG_CONFIG_DOXYGENDOCDIR "${PKG_CONFIG_DOCDIR}/doxygen-html") + + SET(PKG_CONFIG_PROJECT_NAME "${PROJECT_NAME}") + SET(PKG_CONFIG_DESCRIPTION "${PROJECT_DESCRIPTION}") + SET(PKG_CONFIG_URL "${PROJECT_URL}") + SET(PKG_CONFIG_VERSION "${PROJECT_VERSION}") + SET(PKG_CONFIG_REQUIRES "") + SET(PKG_CONFIG_CONFLICTS "") + SET(PKG_CONFIG_LIBS "${LIBDIR_KW}${CMAKE_INSTALL_PREFIX}/lib") + SET(PKG_CONFIG_LIBS_PRIVATE "") + SET(PKG_CONFIG_CFLAGS "-I${CMAKE_INSTALL_PREFIX}/include") + + SET(PKG_CONFIG_EXTRA "") + + # Where to install the pkg-config file? + SET(PKG_CONFIG_DIR "${PKG_CONFIG_LIBDIR}/pkgconfig") + + # Watch variables. + LIST(APPEND LOGGING_WATCHED_VARIABLES + PKG_CONFIG_FOUND + PKG_CONFIG_EXECUTABLE + PKG_CONFIG_PREFIX + PKG_CONFIG_EXEC_PREFIX + PKG_CONFIG_LIBDIR + PKG_CONFIG_INCLUDEDIR + PKG_CONFIG_DATAROOTDIR + PKG_CONFIG_PKGDATAROOTDIR + PKG_CONFIG_DOCDIR + PKG_CONFIG_DOXYGENDOCDIR + PKG_CONFIG_PROJECT_NAME + PKG_CONFIG_DESCRIPTION + PKG_CONFIG_URL + PKG_CONFIG_VERSION + PKG_CONFIG_REQUIRES + PKG_CONFIG_CONFLICTS + PKG_CONFIG_LIBS + PKG_CONFIG_LIBS_PRIVATE + PKG_CONFIG_CFLAGS + PKG_CONFIG_EXTRA + ) + + # Install it. + INSTALL( + FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" + DESTINATION lib/pkgconfig + PERMISSIONS OWNER_READ GROUP_READ WORLD_READ OWNER_WRITE) +ENDMACRO(_SETUP_PROJECT_PKG_CONFIG) + + +# _SETUP_PROJECT_PKG_CONFIG_FINALIZE +# ---------------------------------- +# +# Post-processing of the pkg-config step. +# +# The pkg-config file has to be generated at the end to allow end-user +# defined variables replacement. +# +MACRO(_SETUP_PROJECT_PKG_CONFIG_FINALIZE) + # Generate the pkg-config file. + CONFIGURE_FILE( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/pkg-config.pc.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc" + ) +ENDMACRO(_SETUP_PROJECT_PKG_CONFIG_FINALIZE) + + +# ADD_DEPENDENCY(PREFIX P_REQUIRED PKGCONFIG_STRING) +# ------------------------------------------------ +# +# Check for a dependency using pkg-config. Fail if the package cannot +# be found. +# +# P_REQUIRED : if set to 1 the package is required, otherwise it consider +# as optional. +# WARNING for optional package: +# if the package is detected its compile +# and linking options are still put in the required fields +# of the generated pc file. Indeed from the binary viewpoint +# the package becomes required. +# +# PKG_CONFIG_STRING : string passed to pkg-config to check the version. +# Typically, this string looks like: +# ``my-package >= 0.5'' +# +MACRO(ADD_DEPENDENCY P_REQUIRED PKG_CONFIG_STRING) + # Retrieve the left part of the equation to get package name. + STRING(REGEX MATCH "[^<>= ]+" LIBRARY_NAME "${PKG_CONFIG_STRING}") + # And transform it into a valid variable prefix. + # 1. replace invalid characters into underscores. + STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" PREFIX "${LIBRARY_NAME}") + # 2. make it uppercase. + STRING(TOUPPER "${PREFIX}" "PREFIX") + + # Force redetection each time CMake is launched. + # Rationale: these values are *NEVER* manually set, so information is never + # lost by overriding them. Moreover, changes in the pkg-config files are + # not seen as long as the cache is not destroyed, even if the .pc file + # is changed. This is a BAD behavior. + SET(${PREFIX}_FOUND 0) + + # Search for the package. + IF(${P_REQUIRED}) + MESSAGE(STATUS "${PKG_CONFIG_STRING} is required.") + PKG_CHECK_MODULES("${PREFIX}" REQUIRED "${PKG_CONFIG_STRING}") + ELSE(${P_REQUIRED}) + MESSAGE(STATUS "${PKG_CONFIG_STRING} is optional.") + PKG_CHECK_MODULES("${PREFIX}" "${PKG_CONFIG_STRING}") + ENDIF(${P_REQUIRED}) + + # Watch variables. + LIST(APPEND LOGGING_WATCHED_VARIABLES + ${PREFIX}_FOUND + ${PREFIX}_LIBRARIES + ${PREFIX}_LIBRARY_DIRS + ${PREFIX}_LDFLAGS + ${PREFIX}_LDFLAGS_OTHER + ${PREFIX}_INCLUDE_DIRS + ${PREFIX}_CFLAGS + ${PREFIX}_CFLAGS_OTHER + ${PREFIX} + ${PREFIX}_STATIC + ${PREFIX}_VERSION + ${PREFIX}_PREFIX + ${PREFIX}_INCLUDEDIR + ${PREFIX}_LIBDIR + ) + + # Get the values of additional variables. + FOREACH(VARIABLE ${PKG_CONFIG_ADDITIONAL_VARIABLES}) + # Upper-case version of the variable for CMake variable generation. + STRING(TOUPPER "${VARIABLE}" "VARIABLE_UC") + EXEC_PROGRAM( + "${PKG_CONFIG_EXECUTABLE}" ARGS + "--variable=${VARIABLE}" "${LIBRARY_NAME}" + OUTPUT_VARIABLE "${PREFIX}_${VARIABLE_UC}") + + # Watch additional variables. + LIST(APPEND LOGGING_WATCHED_VARIABLES ${PREFIX}_${VARIABLE_UC}) + ENDFOREACH(VARIABLE) + + #FIXME: spaces are replaced by semi-colon by mistakes, revert the change. + #I cannot see why CMake is doing that... + STRING(REPLACE ";" " " PKG_CONFIG_STRING "${PKG_CONFIG_STRING}") + + # Add the package to the dependency list if found + IF(${${PREFIX}_FOUND}) + _ADD_TO_LIST(PKG_CONFIG_REQUIRES "${PKG_CONFIG_STRING}" ",") + ENDIF() + + # Add the package to the cmake dependency list + # if cpack has been included. + # This is likely to disappear when Ubuntu 8.04 will + # disappear. + IF(COMMAND ADD_CMAKE_DEPENDENCY) + ADD_CMAKE_DEPENDENCY(${PKG_CONFIG_STRING}) + ENDIF(COMMAND ADD_CMAKE_DEPENDENCY) + + IF(${${PREFIX}_FOUND}) + MESSAGE(STATUS + "Pkg-config module ${LIBRARY_NAME} v${${PREFIX}_VERSION}" + " has been detected with success.") + ENDIF() + +ENDMACRO(ADD_DEPENDENCY) + +# ADD_REQUIRED_DEPENDENCY(PREFIX PKGCONFIG_STRING) +# ------------------------------------------------ +# +# Check for a dependency using pkg-config. Fail if the package cannot +# be found. +# +# PKG_CONFIG_STRING : string passed to pkg-config to check the version. +# Typically, this string looks like: +# ``my-package >= 0.5'' +# +MACRO(ADD_REQUIRED_DEPENDENCY PKG_CONFIG_STRING) + ADD_DEPENDENCY(1 ${PKG_CONFIG_STRING}) +ENDMACRO(ADD_REQUIRED_DEPENDENCY) + +# ADD_OPTIONAL_DEPENDENCY(PREFIX PKGCONFIG_STRING) +# ------------------------------------------------ +# +# Check for a dependency using pkg-config. Quiet if the package cannot +# be found. +# +# PKG_CONFIG_STRING : string passed to pkg-config to check the version. +# Typically, this string looks like: +# ``my-package >= 0.5'' +# +MACRO(ADD_OPTIONAL_DEPENDENCY PKG_CONFIG_STRING) + ADD_DEPENDENCY(0 ${PKG_CONFIG_STRING}) +ENDMACRO(ADD_OPTIONAL_DEPENDENCY) + +# PKG_CONFIG_APPEND_LIBRARY_DIR +# ----------------------------- +# +# This macro adds library directories in a portable way +# into the CMake file. +MACRO(PKG_CONFIG_APPEND_LIBRARY_DIR DIRS) + FOREACH(DIR ${DIRS}) + IF(DIR) + SET(PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS} ${LIBDIR_KW}${DIR}") + ENDIF(DIR) + ENDFOREACH(DIR ${DIRS}) +ENDMACRO(PKG_CONFIG_APPEND_LIBRARY_DIR DIR) + + +# PKG_CONFIG_APPEND_CFLAGS +# ------------------------ +# +# This macro adds CFLAGS in a portable way into the pkg-config file. +# +MACRO(PKG_CONFIG_APPEND_CFLAGS FLAGS) + FOREACH(FLAG ${FLAGS}) + IF(FLAG) + SET(PKG_CONFIG_CFLAGS "${PKG_CONFIG_CFLAGS} ${FLAG}") + ENDIF(FLAG) + ENDFOREACH(FLAG ${FLAGS}) +ENDMACRO(PKG_CONFIG_APPEND_CFLAGS) + + +# PKG_CONFIG_APPEND_LIBS_RAW +# ---------------------------- +# +# This macro adds raw value in the "Libs:" into the pkg-config file. +# +# Exception for mac OS X: +# In addition to the classical static and dynamic libraries (handled like +# unix does), mac systems can link against frameworks. +# Frameworks are directories gathering headers, libraries, shared resources... +# +# The syntax used to link with a framework is particular, hence a filter is +# added to convert the absolute path to a framework (e.g. /Path/to/Sample.framework) +# into the correct flags (-F/Path/to/ -framework Sample). +# +MACRO(PKG_CONFIG_APPEND_LIBS_RAW LIBS) + FOREACH(LIB ${LIBS}) + IF(LIB) + IF( APPLE AND ${LIB} MATCHES .framework) + GET_FILENAME_COMPONENT(framework_PATH ${LIB} PATH) + GET_FILENAME_COMPONENT(framework_NAME ${LIB} NAME_WE) + SET(PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS} -F${framework_PATH} -framework ${framework_NAME}") + ELSE( APPLE AND ${LIB} MATCHES .framework) + SET(PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS} ${LIB}") + ENDIF( APPLE AND ${LIB} MATCHES .framework) + ENDIF(LIB) + ENDFOREACH(LIB ${LIBS}) + STRING(REPLACE "\n" "" PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS}") +ENDMACRO(PKG_CONFIG_APPEND_LIBS_RAW) + + +# PKG_CONFIG_APPEND_LIBS +# ---------------------- +# +# This macro adds libraries in a portable way into the pkg-config +# file. +# +# Library prefix and suffix is automatically added. +# +MACRO(PKG_CONFIG_APPEND_LIBS LIBS) + FOREACH(LIB ${LIBS}) + IF(LIB) + SET(PKG_CONFIG_LIBS "${PKG_CONFIG_LIBS} ${LIBINCL_KW}${LIB}${LIB_EXT}") + ENDIF(LIB) + ENDFOREACH(LIB ${LIBS}) +ENDMACRO(PKG_CONFIG_APPEND_LIBS) + + +# PKG_CONFIG_USE_DEPENDENCY(TARGET DEPENDENCY) +# -------------------------------------------- +# +# This macro changes the target properties to properly search for +# headers, libraries and link against the required shared libraries +# when using a dependency detected through pkg-config. +# +# I.e. PKG_CONFIG_USE_DEPENDENCY(my-binary my-package) +# +MACRO(PKG_CONFIG_USE_DEPENDENCY TARGET DEPENDENCY) + # Transform the dependency into a valid variable prefix. + # 1. replace invalid characters into underscores. + STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" PREFIX "${DEPENDENCY}") + # 2. make it uppercase. + STRING(TOUPPER "${PREFIX}" "PREFIX") + + # Make sure we search for a previously detected package. + IF(NOT DEFINED ${PREFIX}_FOUND) + MESSAGE(FATAL_ERROR + "The package ${DEPENDENCY} has not been detected correctly.\n" + "Have you called ADD_REQUIRED_DEPENDENCY/ADD_OPTIONAL_DEPENDENCY?") + ENDIF() + IF(NOT ${PREFIX}_FOUND) + MESSAGE(FATAL_ERROR + "The package ${DEPENDENCY} has not been found.") + ENDIF() + + # Make sure we do not override previous flags. + GET_TARGET_PROPERTY(CFLAGS ${TARGET} COMPILE_FLAGS) + GET_TARGET_PROPERTY(LDFLAGS ${TARGET} LINK_FLAGS) + + # If there were no previous flags, get rid of the XYFLAGS-NOTFOUND + # in the variables. + IF(NOT CFLAGS) + SET(CFLAGS "") + ENDIF() + IF(NOT LDFLAGS) + SET(LDFLAGS "") + ENDIF() + + # Transform semi-colon seperated list in to space separated list. + FOREACH(FLAG ${${PREFIX}_CFLAGS}) + SET(CFLAGS "${CFLAGS} ${FLAG}") + ENDFOREACH() + + FOREACH(FLAG ${${PREFIX}_LDFLAGS}) + SET(LDFLAGS "${LDFLAGS} ${FLAG}") + ENDFOREACH() + + # Update the flags. + SET_TARGET_PROPERTIES(${TARGET} + PROPERTIES COMPILE_FLAGS "${CFLAGS}" LINK_FLAGS "${LDFLAGS}") + + IF(UNIX AND NOT APPLE) + TARGET_LINK_LIBRARIES(${TARGET} ${${PREFIX}_LDFLAGS}) + TARGET_LINK_LIBRARIES(${TARGET} ${${PREFIX}_LDFLAGS_OTHER}) + ENDIF(UNIX AND NOT APPLE) + + # Include/libraries paths seems to be filtered on Linux, add paths + # again. + INCLUDE_DIRECTORIES(${${PREFIX}_INCLUDE_DIRS}) + LINK_DIRECTORIES(${${PREFIX}_LIBRARY_DIRS}) +ENDMACRO(PKG_CONFIG_USE_DEPENDENCY TARGET DEPENDENCY) + diff --git a/3rdparty/rbdl/addons/urdfreader/CMake/ros.cmake b/3rdparty/rbdl/addons/urdfreader/CMake/ros.cmake new file mode 100644 index 0000000..2a7c358 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/CMake/ros.cmake @@ -0,0 +1,92 @@ +# This file was taken from: +# https://github.com/jrl-umi3218/jrl-cmakemodules/blob/master/ros.cmake + +MACRO(ADD_ROSPACK_DEPENDENCY PKG) + IF(PKG STREQUAL "") + MESSAGE(FATAL_ERROR "ADD_ROS_DEPENDENCY invalid call.") + ENDIF() + + # Transform package name into a valid variable prefix. + # 1. replace invalid characters into underscores. + STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" PREFIX "${PKG}") + # 2. make it uppercase. + STRING(TOUPPER "${PREFIX}" "PREFIX") + + SET(${PREFIX}_FOUND 0) + + FIND_PROGRAM(ROSPACK rospack) + IF(NOT ROSPACK) + MESSAGE(FATAL_ERROR "failed to find the rospack binary. Is ROS installed?") + ENDIF() + + MESSAGE(STATUS "Looking for ${PKG} using rospack...") + EXEC_PROGRAM("${ROSPACK} find ${PKG}" OUTPUT_VARIABLE ${PKG}_ROS_PREFIX) + IF(NOT ${PKG}_ROS_PREFIX) + MESSAGE(FATAL_ERROR "Failed to detect ${PKG}.") + ENDIF() + + SET(${PREFIX}_FOUND 1) + EXEC_PROGRAM("${ROSPACK} export --lang=cpp --attrib=cflags -q ${PKG}" + OUTPUT_VARIABLE "${PREFIX}_CFLAGS") + EXEC_PROGRAM("${ROSPACK} export --lang=cpp --attrib=lflags -q ${PKG}" + OUTPUT_VARIABLE "${PREFIX}_LIBS") + + # Add flags to package pkg-config file. + PKG_CONFIG_APPEND_CFLAGS (${${PREFIX}_CFLAGS}) + PKG_CONFIG_APPEND_LIBS_RAW (${${PREFIX}_LIBS}) +ENDMACRO() + +MACRO(ROSPACK_USE_DEPENDENCY TARGET PKG) + IF(PKG STREQUAL "") + MESSAGE(FATAL_ERROR "ADD_ROS_DEPENDENCY invalid call.") + ENDIF() + + # Transform package name into a valid variable prefix. + # 1. replace invalid characters into underscores. + STRING(REGEX REPLACE "[^a-zA-Z0-9]" "_" PREFIX "${PKG}") + # 2. make it uppercase. + STRING(TOUPPER "${PREFIX}" "PREFIX") + + # Make sure we do not override previous flags. + GET_TARGET_PROPERTY(CFLAGS "${TARGET}" COMPILE_FLAGS) + GET_TARGET_PROPERTY(LDFLAGS "${TARGET}" LINK_FLAGS) + + # If there were no previous flags, get rid of the XYFLAGS-NOTFOUND + # in the variables. + IF(NOT CFLAGS) + SET(CFLAGS "") + ENDIF() + IF(NOT LDFLAGS) + SET(LDFLAGS "") + ENDIF() + + # Transform semi-colon seperated list in to space separated list. + FOREACH(FLAG ${${PREFIX}_CFLAGS}) + SET(CFLAGS "${CFLAGS} ${FLAG}") + ENDFOREACH() + + FOREACH(FLAG ${${PREFIX}_LDFLAGS}) + SET(LDFLAGS "${LDFLAGS} ${FLAG}") + ENDFOREACH() + + # Filter out end of line in new flags. + STRING(REPLACE "\n" "" ${PREFIX}_CFLAGS "${${PREFIX}_CFLAGS}") + STRING(REPLACE "\n" "" ${PREFIX}_LIBS "${${PREFIX}_LIBS}") + + # Append new flags. + SET(CFLAGS "${CFLAGS} ${${PREFIX}_CFLAGS}") + SET(LDFLAGS "${LDFLAGS} ${${PREFIX}_LIBS}") + + # MESSAGE (STATUS "Linkerflags for ${TARGET}: ${LDFLAGS}") + + # Explicitly link against the shared object file + EXEC_PROGRAM("${ROSPACK} export find ${PKG}" + OUTPUT_VARIABLE "${PKG_FULL_PATH}") + + # SET (LDFLAGS "${LDFLAGS} ${PKG_FULL_PATH}/lib/lib${PKG}.so") + + # Update the flags. + SET_TARGET_PROPERTIES(${TARGET} + PROPERTIES COMPILE_FLAGS "${CFLAGS}" LINK_FLAGS "${LDFLAGS}") +ENDMACRO() + diff --git a/3rdparty/rbdl/addons/urdfreader/CMake/shared-library.cmake b/3rdparty/rbdl/addons/urdfreader/CMake/shared-library.cmake new file mode 100644 index 0000000..c8dbbf5 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/CMake/shared-library.cmake @@ -0,0 +1,28 @@ +# Copyright (C) 2010 Florent Lamiraux, Thomas Moulard, JRL, CNRS/AIST. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Shared library related constants +# (used for pkg-config file generation). +# FIXME: can't we get these information from CMake directly? +IF(WIN32) + SET(LIBDIR_KW "/LIBPATH:") + SET(LIBINCL_KW "") + SET(LIB_EXT ".lib") +ELSEIF(UNIX) + SET(LIBDIR_KW "-L") + SET(LIBINCL_KW "-l") + SET(LIB_EXT "") +ENDIF(WIN32) + diff --git a/3rdparty/rbdl/addons/urdfreader/CMakeLists.txt b/3rdparty/rbdl/addons/urdfreader/CMakeLists.txt new file mode 100644 index 0000000..f323753 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/CMakeLists.txt @@ -0,0 +1,116 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +CMAKE_POLICY(SET CMP0048 NEW) + +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake ) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX +) + +INCLUDE_DIRECTORIES ( + ${CMAKE_CURRENT_BINARY_DIR}/include/rbdl + ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/ +) + +SET ( URDFREADER_SOURCES + urdfreader.cc + ) + +IF (DEFINED ENV{ROS_ROOT}) + MESSAGE (STATUS "ROS found: $ENV{ROS_ROOT}") + find_package(catkin REQUIRED COMPONENTS urdf) + OPTION (RBDL_USE_ROS_URDF_LIBRARY "Use the URDF library provided by ROS" ON) +ELSE () + SET (RBDL_USE_ROS_URDF_LIBRARY FALSE) +ENDIF () + +IF (RBDL_USE_ROS_URDF_LIBRARY) + # find_package(Boost REQUIRED COMPONENTS system) + SET (URDFREADER_DEPENDENCIES + rbdl + ${urdf_LIBRARIES} + # ${Boost_SYSTEM_LIBRARY} + ) +ELSE() + SET (URDFREADER_SOURCES + ${URDFREADER_SOURCES} + thirdparty/urdf/urdfdom/urdf_parser/src/check_urdf.cpp + thirdparty/urdf/urdfdom/urdf_parser/src/pose.cpp + thirdparty/urdf/urdfdom/urdf_parser/src/model.cpp + thirdparty/urdf/urdfdom/urdf_parser/src/link.cpp + thirdparty/urdf/urdfdom/urdf_parser/src/joint.cpp + thirdparty/urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h + thirdparty/urdf/urdfdom_headers/urdf_exception/include/urdf_exception/exception.h + thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/pose.h + thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/model.h + thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/link.h + thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/joint.h + thirdparty/tinyxml/tinystr.cpp + thirdparty/tinyxml/tinyxml.cpp + thirdparty/tinyxml/tinyxmlerror.cpp + thirdparty/tinyxml/tinyxmlparser.cpp + thirdparty/urdf/boost_replacement/lexical_cast.h + thirdparty/urdf/boost_replacement/shared_ptr.h + thirdparty/urdf/boost_replacement/printf_console.cpp + thirdparty/urdf/boost_replacement/printf_console.h + thirdparty/urdf/boost_replacement/string_split.cpp + thirdparty/urdf/boost_replacement/string_split.h + ) + SET (URDFREADER_DEPENDENCIES + rbdl + ) +ENDIF() + +ADD_EXECUTABLE (rbdl_urdfreader_util rbdl_urdfreader_util.cc) + +IF (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl_urdfreader-static STATIC ${URDFREADER_SOURCES} ) + + IF (NOT WIN32) + SET_TARGET_PROPERTIES ( rbdl_urdfreader-static PROPERTIES PREFIX "lib") + ENDIF (NOT WIN32) + SET_TARGET_PROPERTIES ( rbdl_urdfreader-static PROPERTIES OUTPUT_NAME "rbdl_urdfreader") + + TARGET_LINK_LIBRARIES (rbdl_urdfreader-static + rbdl-static + ) + + TARGET_LINK_LIBRARIES (rbdl_urdfreader_util + rbdl_urdfreader-static + rbdl-static + ) + + INSTALL (TARGETS rbdl_urdfreader-static rbdl_urdfreader_util + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ARCHIVE DESTINATION lib + ) +ELSE (RBDL_BUILD_STATIC) + ADD_LIBRARY ( rbdl_urdfreader SHARED ${URDFREADER_SOURCES} ) + SET_TARGET_PROPERTIES ( rbdl_urdfreader PROPERTIES + VERSION ${RBDL_VERSION} + SOVERSION ${RBDL_SO_VERSION} + ) + + TARGET_LINK_LIBRARIES (rbdl_urdfreader + ${URDFREADER_DEPENDENCIES} + ) + + TARGET_LINK_LIBRARIES (rbdl_urdfreader_util + rbdl_urdfreader + ) + + INSTALL (TARGETS rbdl_urdfreader rbdl_urdfreader_util + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) +ENDIF (RBDL_BUILD_STATIC) + +FILE ( GLOB headers + "${CMAKE_CURRENT_SOURCE_DIR}/*.h" + ) + +INSTALL ( FILES ${headers} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/rbdl/addons/urdfreader + ) diff --git a/3rdparty/rbdl/addons/urdfreader/README.md b/3rdparty/rbdl/addons/urdfreader/README.md new file mode 100644 index 0000000..1a9de79 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/README.md @@ -0,0 +1,50 @@ +urdfreader - load models from (URDF Unified Robot Description Format) files +Copyright (c) 2012 Martin Felis + +Requirements +============ + +This addon depends on urdfdom to load and access the model data in the URDF +files. + +See https://github.com/ros/urdfdom for more details on how to +install urdfdom. + +Warning +======= + +This code is not properly tested as I do not have a proper urdf robot +model. If anyone has one and also some reference values that should come +out for the dynamics computations, please let me know. + +Licensing +========= + +This code is published under the zlib license, however some parts of the +CMake scripts are taken from other projects and are licensed under +different terms. + +Full license text: + +------- +urdfreader - load models from URDF (Unified Robot Description Format) files +Copyright (c) 2012-2015 Martin Felis + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. diff --git a/3rdparty/rbdl/addons/urdfreader/rbdl_urdfreader_util.cc b/3rdparty/rbdl/addons/urdfreader/rbdl_urdfreader_util.cc new file mode 100644 index 0000000..7df3dbb --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/rbdl_urdfreader_util.cc @@ -0,0 +1,69 @@ +#include +#include + +#include "urdfreader.h" + +#include + +using namespace std; + +bool verbose = false; +bool floatbase = false; +string filename = ""; + +void usage (const char* argv_0) { + cerr << "Usage: " << argv_0 << "[-v] [-m] [-d] " << endl; + cerr << " -v | --verbose enable additional output" << endl; + cerr << " -d | --dof-overview print an overview of the degress of freedom" << endl; + cerr << " -m | --model-hierarchy print the hierarchy of the model" << endl; + cerr << " -h | --help print this help" << endl; + exit (1); +} + +int main (int argc, char *argv[]) { + if (argc < 2 || argc > 4) { + usage(argv[0]); + } + + bool verbose = false; + bool dof_overview = false; + bool model_hierarchy = false; + + string filename = argv[1]; + + for (int i = 1; i < argc; i++) { + if (string(argv[i]) == "-v" || string (argv[i]) == "--verbose") + verbose = true; + else if (string(argv[i]) == "-d" || string (argv[i]) == "--dof-overview") + dof_overview = true; + else if (string(argv[i]) == "-m" || string (argv[i]) == "--model-hierarchy") + model_hierarchy = true; + else if (string(argv[i]) == "-f" || string (argv[i]) == "--floatbase") + floatbase = true; + else if (string(argv[i]) == "-h" || string (argv[i]) == "--help") + usage(argv[0]); + else + filename = argv[i]; + } + + RigidBodyDynamics::Model model; + + if (!RigidBodyDynamics::Addons::URDFReadFromFile(filename.c_str(), &model, floatbase, verbose)) { + cerr << "Loading of urdf model failed!" << endl; + return -1; + } + + cout << "Model loading successful!" << endl; + + if (dof_overview) { + cout << "Degree of freedom overview:" << endl; + cout << RigidBodyDynamics::Utils::GetModelDOFOverview(model); + } + + if (model_hierarchy) { + cout << "Model Hierarchy:" << endl; + cout << RigidBodyDynamics::Utils::GetModelHierarchy (model); + } + + return 0; +} diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/README.md b/3rdparty/rbdl/addons/urdfreader/thirdparty/README.md new file mode 100644 index 0000000..cd7a0d9 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/README.md @@ -0,0 +1,5 @@ +Code from the urdf directory was taken from the Bullet 3 source code +repository https://github.com/bulletphysics/bullet3 from the directory +examples/ThirdPartyLibs/urdf. + +The tinyxml code was obtained from http://sourceforge.net/projects/tinyxml/ diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/changes.txt b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/changes.txt new file mode 100644 index 0000000..15b51bd --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/changes.txt @@ -0,0 +1,299 @@ +Changes in version 1.0.1: +- Fixed comment tags which were outputing as ' include. Thanks + to Steve Lhomme for that. + +Changes in version 2.0.0 BETA +- Made the ToXXX() casts safe if 'this' is null. + When "LoadFile" is called with a filename, the value will correctly get set. + Thanks to Brian Yoder. +- Fixed bug where isalpha() and isalnum() would get called with a negative value for + high ascii numbers. Thanks to Alesky Aksenov. +- Fixed some errors codes that were not getting set. +- Made methods "const" that were not. +- Added a switch to enable or disable the ignoring of white space. ( TiXmlDocument::SetIgnoreWhiteSpace() ) +- Greater standardization and code re-use in the parser. +- Added a stream out operator. +- Added a stream in operator. +- Entity support, of predefined entites. &#x entities are untouched by input or output. +- Improved text out formatting. +- Fixed ReplaceChild bug, thanks to Tao Chen. + +Changes in version 2.0.1 +- Fixed hanging on loading a 0 length file. Thanks to Jeff Scozzafava. +- Fixed crashing on InsertBeforeChild and InsertAfterChild. Also possibility of bad links being + created by same function. Thanks to Frank De prins. +- Added missing licence text. Thanks to Lars Willemsens. +- Added include, at the suggestion of Steve Walters. + +Changes in version 2.1.0 +- Yves Berquin brings us the STL switch. The forum on SourceForge, and various emails to + me, have long debated all out STL vs. no STL at all. And now you can have it both ways. + TinyXml will compile either way. + +Changes in version 2.1.1 +- Compilation warnings. + +Changes in version 2.1.2 +- Uneeded code is not compiled in the STL case. +- Changed headers so that STL can be turned on or off in tinyxml.h + +Changes in version 2.1.3 +- Fixed non-const reference in API; now uses a pointer. +- Copy constructor of TiXmlString not checking for assignment to self. +- Nimrod Cohen found a truly evil bug in the STL implementation that occurs + when a string is converted to a c_str and then assigned to self. Search for + STL_STRING_BUG for a full description. I'm asserting this is a Microsoft STL + bug, since &string and string.c_str() should never be the same. Nevertheless, + the code works around it. +- Urivan Saaib pointed out a compiler conflict, where the C headers define + the isblank macro, which was wiping out the TiXmlString::isblank() method. + The method was unused and has been removed. + +Changes in version 2.1.4 +- Reworked the entity code. Entities were not correctly surving round trip input and output. + Will now automatically create entities for high ascii in output. + +Changes in version 2.1.5 +- Bug fix by kylotan : infinite loop on some input (tinyxmlparser.cpp rev 1.27) +- Contributed by Ivica Aracic (bytelord) : 1 new VC++ project to compile versions as static libraries (tinyxml_lib.dsp), + and an example usage in xmltest.dsp + (Patch request ID 678605) +- A suggestion by Ronald Fenner Jr (dormlock) to add #include and for Apple's Project Builder + (Patch request ID 697642) +- A patch from ohommes that allows to parse correctly dots in element names and attribute names + (Patch request 602600 and kylotan 701728) +- A patch from hermitgeek ( James ) and wasteland for improper error reporting +- Reviewed by Lee, with the following changes: + - Got sick of fighting the STL/non-STL thing in the windows build. Broke + them out as seperate projects. + - I have too long not included the dsw. Added. + - TinyXmlText had a protected Print. Odd. + - Made LinkEndChild public, with docs and appropriate warnings. + - Updated the docs. + +2.2.0 +- Fixed an uninitialized pointer in the TiXmlAttributes +- Fixed STL compilation problem in MinGW (and gcc 3?) - thanks Brian Yoder for finding this one +- Fixed a syntax error in TiXmlDeclaration - thanks Brian Yoder +- Fletcher Dunn proposed and submitted new error handling that tracked the row and column. Lee + modified it to not have performance impact. +- General cleanup suggestions from Fletcher Dunn. +- In error handling, general errors will no longer clear the error state of specific ones. +- Fix error in documentation : comments starting with ">) has now + been fixed. + +2.5.2 +- Lieven, and others, pointed out a missing const-cast that upset the Open Watcom compiler. + Should now be fixed. +- ErrorRow and ErrorCol should have been const, and weren't. Fixed thanks to Dmitry Polutov. + +2.5.3 +- zloe_zlo identified a missing string specialization for QueryValueAttribute() [ 1695429 ]. Worked + on this bug, but not sure how to fix it in a safe, cross-compiler way. +- increased warning level to 4 and turned on detect 64 bit portability issues for VC2005. + May address [ 1677737 ] VS2005: /Wp64 warnings +- grosheck identified several problems with the Document copy. Many thanks for [ 1660367 ] +- Nice catch, and suggested fix, be Gilad Novik on the Printer dropping entities. + "[ 1600650 ] Bug when printing xml text" is now fixed. +- A subtle fix from Nicos Gollan in the tinystring initializer: + [ 1581449 ] Fix initialiser of TiXmlString::nullrep_ +- Great catch, although there isn't a submitter for the bug. [ 1475201 ] TinyXML parses entities in comments. + Comments should not, in fact, parse entities. Fixed the code path and added tests. +- We were not catching all the returns from ftell. Thanks to Bernard for catching that. + +2.5.4 +- A TiXMLDocument can't be a sub-node. Block this from happening in the 'replace'. Thanks Noam. +- [ 1714831 ] TiXmlBase::location is not copied by copy-ctors, fix reported and suggested by Nicola Civran. +- Fixed possible memory overrun in the comment reading code - thanks gcarlton77 + +2.5.5 +- Alex van der Wal spotted incorrect types (lf) being used in print and scan. robertnestor pointed out some problems with the simple solution. Types updated. +- Johannes Hillert pointed out some bug typos. +- Christian Mueller identified inconsistent error handling with Attributes. +- olivier barthelemy also reported a problem with double truncation, also related to the %lf issue. +- zaelsius came up with a great (and simple) suggestion to fix QueryValueAttribute truncating strings. +- added some null pointer checks suggested by hansenk +- Sami Väisänen found a (rare) buffer overrun that could occur in parsing. +- vi tri filed a bug that led to a refactoring of the attribute setting mess (as well as adding a missing SetDoubleAttribute() ) +- removed TIXML_ERROR_OUT_OF_MEMORY. TinyXML does not systematically address OOO, and the notion it does is misleading. +- vanneto, keithmarshall, others all reported the warning from IsWhiteSpace() usage. Cleaned this up - many thanks to everyone who reported this one. +- tibur found a bug in end tag parsing + + +2.6.2 +- Switched over to VC 2010 +- Fixed up all the build issues arising from that. (Lots of latent build problems.) +- Removed the old, now unmaintained and likely not working, build files. +- Fixed some static analysis issues reported by orbitcowboy from cppcheck. +- Bayard 95 sent in analysis from a different analyzer - fixes applied from that as well. +- Tim Kosse sent a patch fixing an infinite loop. +- Ma Anguo identified a doc issue. +- Eddie Cohen identified a missing qualifier resulting in a compilation error on some systems. +- Fixed a line ending bug. (What year is this? Can we all agree on a format for text files? Please? ...oh well.) + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/readme.txt b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/readme.txt new file mode 100644 index 0000000..89d9e8d --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/readme.txt @@ -0,0 +1,530 @@ +/** @mainpage + +

TinyXML

+ +TinyXML is a simple, small, C++ XML parser that can be easily +integrated into other programs. + +

What it does.

+ +In brief, TinyXML parses an XML document, and builds from that a +Document Object Model (DOM) that can be read, modified, and saved. + +XML stands for "eXtensible Markup Language." It allows you to create +your own document markups. Where HTML does a very good job of marking +documents for browsers, XML allows you to define any kind of document +markup, for example a document that describes a "to do" list for an +organizer application. XML is a very structured and convenient format. +All those random file formats created to store application data can +all be replaced with XML. One parser for everything. + +The best place for the complete, correct, and quite frankly hard to +read spec is at +http://www.w3.org/TR/2004/REC-xml-20040204/. An intro to XML +(that I really like) can be found at +http://skew.org/xml/tutorial. + +There are different ways to access and interact with XML data. +TinyXML uses a Document Object Model (DOM), meaning the XML data is parsed +into a C++ objects that can be browsed and manipulated, and then +written to disk or another output stream. You can also construct an XML document +from scratch with C++ objects and write this to disk or another output +stream. + +TinyXML is designed to be easy and fast to learn. It is two headers +and four cpp files. Simply add these to your project and off you go. +There is an example file - xmltest.cpp - to get you started. + +TinyXML is released under the ZLib license, +so you can use it in open source or commercial code. The details +of the license are at the top of every source file. + +TinyXML attempts to be a flexible parser, but with truly correct and +compliant XML output. TinyXML should compile on any reasonably C++ +compliant system. It does not rely on exceptions or RTTI. It can be +compiled with or without STL support. TinyXML fully supports +the UTF-8 encoding, and the first 64k character entities. + + +

What it doesn't do.

+ +TinyXML doesn't parse or use DTDs (Document Type Definitions) or XSLs +(eXtensible Stylesheet Language.) There are other parsers out there +(check out www.sourceforge.org, search for XML) that are much more fully +featured. But they are also much bigger, take longer to set up in +your project, have a higher learning curve, and often have a more +restrictive license. If you are working with browsers or have more +complete XML needs, TinyXML is not the parser for you. + +The following DTD syntax will not parse at this time in TinyXML: + +@verbatim + + ]> +@endverbatim + +because TinyXML sees this as a !DOCTYPE node with an illegally +embedded !ELEMENT node. This may be addressed in the future. + +

Tutorials.

+ +For the impatient, here is a tutorial to get you going. A great way to get started, +but it is worth your time to read this (very short) manual completely. + +- @subpage tutorial0 + +

Code Status.

+ +TinyXML is mature, tested code. It is very stable. If you find +bugs, please file a bug report on the sourceforge web site +(www.sourceforge.net/projects/tinyxml). We'll get them straightened +out as soon as possible. + +There are some areas of improvement; please check sourceforge if you are +interested in working on TinyXML. + +

Related Projects

+ +TinyXML projects you may find useful! (Descriptions provided by the projects.) + +
    +
  • TinyXPath (http://tinyxpath.sourceforge.net). TinyXPath is a small footprint + XPath syntax decoder, written in C++.
  • +
  • TinyXML++ (http://code.google.com/p/ticpp/). TinyXML++ is a completely new + interface to TinyXML that uses MANY of the C++ strengths. Templates, + exceptions, and much better error handling.
  • +
+ +

Features

+ +

Using STL

+ +TinyXML can be compiled to use or not use STL. When using STL, TinyXML +uses the std::string class, and fully supports std::istream, std::ostream, +operator<<, and operator>>. Many API methods have both 'const char*' and +'const std::string&' forms. + +When STL support is compiled out, no STL files are included whatsoever. All +the string classes are implemented by TinyXML itself. API methods +all use the 'const char*' form for input. + +Use the compile time #define: + + TIXML_USE_STL + +to compile one version or the other. This can be passed by the compiler, +or set as the first line of "tinyxml.h". + +Note: If compiling the test code in Linux, setting the environment +variable TINYXML_USE_STL=YES/NO will control STL compilation. In the +Windows project file, STL and non STL targets are provided. In your project, +It's probably easiest to add the line "#define TIXML_USE_STL" as the first +line of tinyxml.h. + +

UTF-8

+ +TinyXML supports UTF-8 allowing to manipulate XML files in any language. TinyXML +also supports "legacy mode" - the encoding used before UTF-8 support and +probably best described as "extended ascii". + +Normally, TinyXML will try to detect the correct encoding and use it. However, +by setting the value of TIXML_DEFAULT_ENCODING in the header file, TinyXML +can be forced to always use one encoding. + +TinyXML will assume Legacy Mode until one of the following occurs: +
    +
  1. If the non-standard but common "UTF-8 lead bytes" (0xef 0xbb 0xbf) + begin the file or data stream, TinyXML will read it as UTF-8.
  2. +
  3. If the declaration tag is read, and it has an encoding="UTF-8", then + TinyXML will read it as UTF-8.
  4. +
  5. If the declaration tag is read, and it has no encoding specified, then TinyXML will + read it as UTF-8.
  6. +
  7. If the declaration tag is read, and it has an encoding="something else", then TinyXML + will read it as Legacy Mode. In legacy mode, TinyXML will work as it did before. It's + not clear what that mode does exactly, but old content should keep working.
  8. +
  9. Until one of the above criteria is met, TinyXML runs in Legacy Mode.
  10. +
+ +What happens if the encoding is incorrectly set or detected? TinyXML will try +to read and pass through text seen as improperly encoded. You may get some strange results or +mangled characters. You may want to force TinyXML to the correct mode. + +You may force TinyXML to Legacy Mode by using LoadFile( TIXML_ENCODING_LEGACY ) or +LoadFile( filename, TIXML_ENCODING_LEGACY ). You may force it to use legacy mode all +the time by setting TIXML_DEFAULT_ENCODING = TIXML_ENCODING_LEGACY. Likewise, you may +force it to TIXML_ENCODING_UTF8 with the same technique. + +For English users, using English XML, UTF-8 is the same as low-ASCII. You +don't need to be aware of UTF-8 or change your code in any way. You can think +of UTF-8 as a "superset" of ASCII. + +UTF-8 is not a double byte format - but it is a standard encoding of Unicode! +TinyXML does not use or directly support wchar, TCHAR, or Microsoft's _UNICODE at this time. +It is common to see the term "Unicode" improperly refer to UTF-16, a wide byte encoding +of unicode. This is a source of confusion. + +For "high-ascii" languages - everything not English, pretty much - TinyXML can +handle all languages, at the same time, as long as the XML is encoded +in UTF-8. That can be a little tricky, older programs and operating systems +tend to use the "default" or "traditional" code page. Many apps (and almost all +modern ones) can output UTF-8, but older or stubborn (or just broken) ones +still output text in the default code page. + +For example, Japanese systems traditionally use SHIFT-JIS encoding. +Text encoded as SHIFT-JIS can not be read by TinyXML. +A good text editor can import SHIFT-JIS and then save as UTF-8. + +The Skew.org link does a great +job covering the encoding issue. + +The test file "utf8test.xml" is an XML containing English, Spanish, Russian, +and Simplified Chinese. (Hopefully they are translated correctly). The file +"utf8test.gif" is a screen capture of the XML file, rendered in IE. Note that +if you don't have the correct fonts (Simplified Chinese or Russian) on your +system, you won't see output that matches the GIF file even if you can parse +it correctly. Also note that (at least on my Windows machine) console output +is in a Western code page, so that Print() or printf() cannot correctly display +the file. This is not a bug in TinyXML - just an OS issue. No data is lost or +destroyed by TinyXML. The console just doesn't render UTF-8. + + +

Entities

+TinyXML recognizes the pre-defined "character entities", meaning special +characters. Namely: + +@verbatim + & & + < < + > > + " " + ' ' +@endverbatim + +These are recognized when the XML document is read, and translated to there +UTF-8 equivalents. For instance, text with the XML of: + +@verbatim + Far & Away +@endverbatim + +will have the Value() of "Far & Away" when queried from the TiXmlText object, +and will be written back to the XML stream/file as an ampersand. Older versions +of TinyXML "preserved" character entities, but the newer versions will translate +them into characters. + +Additionally, any character can be specified by its Unicode code point: +The syntax " " or " " are both to the non-breaking space characher. + +

Printing

+TinyXML can print output in several different ways that all have strengths and limitations. + +- Print( FILE* ). Output to a std-C stream, which includes all C files as well as stdout. + - "Pretty prints", but you don't have control over printing options. + - The output is streamed directly to the FILE object, so there is no memory overhead + in the TinyXML code. + - used by Print() and SaveFile() + +- operator<<. Output to a c++ stream. + - Integrates with standart C++ iostreams. + - Outputs in "network printing" mode without line breaks. Good for network transmission + and moving XML between C++ objects, but hard for a human to read. + +- TiXmlPrinter. Output to a std::string or memory buffer. + - API is less concise + - Future printing options will be put here. + - Printing may change slightly in future versions as it is refined and expanded. + +

Streams

+With TIXML_USE_STL on TinyXML supports C++ streams (operator <<,>>) streams as well +as C (FILE*) streams. There are some differences that you may need to be aware of. + +C style output: + - based on FILE* + - the Print() and SaveFile() methods + + Generates formatted output, with plenty of white space, intended to be as + human-readable as possible. They are very fast, and tolerant of ill formed + XML documents. For example, an XML document that contains 2 root elements + and 2 declarations, will still print. + +C style input: + - based on FILE* + - the Parse() and LoadFile() methods + + A fast, tolerant read. Use whenever you don't need the C++ streams. + +C++ style output: + - based on std::ostream + - operator<< + + Generates condensed output, intended for network transmission rather than + readability. Depending on your system's implementation of the ostream class, + these may be somewhat slower. (Or may not.) Not tolerant of ill formed XML: + a document should contain the correct one root element. Additional root level + elements will not be streamed out. + +C++ style input: + - based on std::istream + - operator>> + + Reads XML from a stream, making it useful for network transmission. The tricky + part is knowing when the XML document is complete, since there will almost + certainly be other data in the stream. TinyXML will assume the XML data is + complete after it reads the root element. Put another way, documents that + are ill-constructed with more than one root element will not read correctly. + Also note that operator>> is somewhat slower than Parse, due to both + implementation of the STL and limitations of TinyXML. + +

White space

+The world simply does not agree on whether white space should be kept, or condensed. +For example, pretend the '_' is a space, and look at "Hello____world". HTML, and +at least some XML parsers, will interpret this as "Hello_world". They condense white +space. Some XML parsers do not, and will leave it as "Hello____world". (Remember +to keep pretending the _ is a space.) Others suggest that __Hello___world__ should become +Hello___world. + +It's an issue that hasn't been resolved to my satisfaction. TinyXML supports the +first 2 approaches. Call TiXmlBase::SetCondenseWhiteSpace( bool ) to set the desired behavior. +The default is to condense white space. + +If you change the default, you should call TiXmlBase::SetCondenseWhiteSpace( bool ) +before making any calls to Parse XML data, and I don't recommend changing it after +it has been set. + + +

Handles

+ +Where browsing an XML document in a robust way, it is important to check +for null returns from method calls. An error safe implementation can +generate a lot of code like: + +@verbatim +TiXmlElement* root = document.FirstChildElement( "Document" ); +if ( root ) +{ + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. +@endverbatim + +Handles have been introduced to clean this up. Using the TiXmlHandle class, +the previous code reduces to: + +@verbatim +TiXmlHandle docHandle( &document ); +TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); +if ( child2 ) +{ + // do something useful +@endverbatim + +Which is much easier to deal with. See TiXmlHandle for more information. + + +

Row and Column tracking

+Being able to track nodes and attributes back to their origin location +in source files can be very important for some applications. Additionally, +knowing where parsing errors occured in the original source can be very +time saving. + +TinyXML can tracks the row and column origin of all nodes and attributes +in a text file. The TiXmlBase::Row() and TiXmlBase::Column() methods return +the origin of the node in the source text. The correct tabs can be +configured in TiXmlDocument::SetTabSize(). + + +

Using and Installing

+ +To Compile and Run xmltest: + +A Linux Makefile and a Windows Visual C++ .dsw file is provided. +Simply compile and run. It will write the file demotest.xml to your +disk and generate output on the screen. It also tests walking the +DOM by printing out the number of nodes found using different +techniques. + +The Linux makefile is very generic and runs on many systems - it +is currently tested on mingw and +MacOSX. You do not need to run 'make depend'. The dependecies have been +hard coded. + +

Windows project file for VC6

+
    +
  • tinyxml: tinyxml library, non-STL
  • +
  • tinyxmlSTL: tinyxml library, STL
  • +
  • tinyXmlTest: test app, non-STL
  • +
  • tinyXmlTestSTL: test app, STL
  • +
+ +

Makefile

+At the top of the makefile you can set: + +PROFILE, DEBUG, and TINYXML_USE_STL. Details (such that they are) are in +the makefile. + +In the tinyxml directory, type "make clean" then "make". The executable +file 'xmltest' will be created. + + + +

To Use in an Application:

+ +Add tinyxml.cpp, tinyxml.h, tinyxmlerror.cpp, tinyxmlparser.cpp, tinystr.cpp, and tinystr.h to your +project or make file. That's it! It should compile on any reasonably +compliant C++ system. You do not need to enable exceptions or +RTTI for TinyXML. + + +

How TinyXML works.

+ +An example is probably the best way to go. Take: +@verbatim + + + + Go to the Toy store! + Do bills + +@endverbatim + +Its not much of a To Do list, but it will do. To read this file +(say "demo.xml") you would create a document, and parse it in: +@verbatim + TiXmlDocument doc( "demo.xml" ); + doc.LoadFile(); +@endverbatim + +And its ready to go. Now lets look at some lines and how they +relate to the DOM. + +@verbatim + +@endverbatim + + The first line is a declaration, and gets turned into the + TiXmlDeclaration class. It will be the first child of the + document node. + + This is the only directive/special tag parsed by TinyXML. + Generally directive tags are stored in TiXmlUnknown so the + commands wont be lost when it is saved back to disk. + +@verbatim + +@endverbatim + + A comment. Will become a TiXmlComment object. + +@verbatim + +@endverbatim + + The "ToDo" tag defines a TiXmlElement object. This one does not have + any attributes, but does contain 2 other elements. + +@verbatim + +@endverbatim + + Creates another TiXmlElement which is a child of the "ToDo" element. + This element has 1 attribute, with the name "priority" and the value + "1". + +@verbatim +Go to the +@endverbatim + + A TiXmlText. This is a leaf node and cannot contain other nodes. + It is a child of the "Item" TiXmlElement. + +@verbatim + +@endverbatim + + + Another TiXmlElement, this one a child of the "Item" element. + +Etc. + +Looking at the entire object tree, you end up with: +@verbatim +TiXmlDocument "demo.xml" + TiXmlDeclaration "version='1.0'" "standalone=no" + TiXmlComment " Our to do list data" + TiXmlElement "ToDo" + TiXmlElement "Item" Attribtutes: priority = 1 + TiXmlText "Go to the " + TiXmlElement "bold" + TiXmlText "Toy store!" + TiXmlElement "Item" Attributes: priority=2 + TiXmlText "Do bills" +@endverbatim + +

Documentation

+ +The documentation is build with Doxygen, using the 'dox' +configuration file. + +

License

+ +TinyXML is released under the zlib license: + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. + +

References

+ +The World Wide Web Consortium is the definitive standard body for +XML, and their web pages contain huge amounts of information. + +The definitive spec: +http://www.w3.org/TR/2004/REC-xml-20040204/ + +I also recommend "XML Pocket Reference" by Robert Eckstein and published by +OReilly...the book that got the whole thing started. + +

Contributors, Contacts, and a Brief History

+ +Thanks very much to everyone who sends suggestions, bugs, ideas, and +encouragement. It all helps, and makes this project fun. A special thanks +to the contributors on the web pages that keep it lively. + +So many people have sent in bugs and ideas, that rather than list here +we try to give credit due in the "changes.txt" file. + +TinyXML was originally written by Lee Thomason. (Often the "I" still +in the documentation.) Lee reviews changes and releases new versions, +with the help of Yves Berquin, Andrew Ellerton, and the tinyXml community. + +We appreciate your suggestions, and would love to know if you +use TinyXML. Hopefully you will enjoy it and find it useful. +Please post questions, comments, file bugs, or contact us at: + +www.sourceforge.net/projects/tinyxml + +Lee Thomason, Yves Berquin, Andrew Ellerton +*/ diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.cpp new file mode 100644 index 0000000..0665768 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.cpp @@ -0,0 +1,111 @@ +/* +www.sourceforge.net/projects/tinyxml + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TIXML_USE_STL + +#include "tinystr.h" + +// Error value for find primitive +const TiXmlString::size_type TiXmlString::npos = static_cast< TiXmlString::size_type >(-1); + + +// Null rep. +TiXmlString::Rep TiXmlString::nullrep_ = { 0, 0, { '\0' } }; + + +void TiXmlString::reserve (size_type cap) +{ + if (cap > capacity()) + { + TiXmlString tmp; + tmp.init(length(), cap); + memcpy(tmp.start(), data(), length()); + swap(tmp); + } +} + + +TiXmlString& TiXmlString::assign(const char* str, size_type len) +{ + size_type cap = capacity(); + if (len > cap || cap > 3*(len + 8)) + { + TiXmlString tmp; + tmp.init(len); + memcpy(tmp.start(), str, len); + swap(tmp); + } + else + { + memmove(start(), str, len); + set_size(len); + } + return *this; +} + + +TiXmlString& TiXmlString::append(const char* str, size_type len) +{ + size_type newsize = length() + len; + if (newsize > capacity()) + { + reserve (newsize + capacity()); + } + memmove(finish(), str, len); + set_size(newsize); + return *this; +} + + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b) +{ + TiXmlString tmp; + tmp.reserve(a.length() + b.length()); + tmp += a; + tmp += b; + return tmp; +} + +TiXmlString operator + (const TiXmlString & a, const char* b) +{ + TiXmlString tmp; + TiXmlString::size_type b_len = static_cast( strlen(b) ); + tmp.reserve(a.length() + b_len); + tmp += a; + tmp.append(b, b_len); + return tmp; +} + +TiXmlString operator + (const char* a, const TiXmlString & b) +{ + TiXmlString tmp; + TiXmlString::size_type a_len = static_cast( strlen(a) ); + tmp.reserve(a_len + b.length()); + tmp.append(a, a_len); + tmp += b; + return tmp; +} + + +#endif // TIXML_USE_STL diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.h new file mode 100644 index 0000000..89cca33 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinystr.h @@ -0,0 +1,305 @@ +/* +www.sourceforge.net/projects/tinyxml + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TIXML_USE_STL + +#ifndef TIXML_STRING_INCLUDED +#define TIXML_STRING_INCLUDED + +#include +#include + +/* The support for explicit isn't that universal, and it isn't really + required - it is used to check that the TiXmlString class isn't incorrectly + used. Be nice to old compilers and macro it here: +*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + #define TIXML_EXPLICIT explicit +#elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + #define TIXML_EXPLICIT explicit +#else + #define TIXML_EXPLICIT +#endif + + +/* + TiXmlString is an emulation of a subset of the std::string template. + Its purpose is to allow compiling TinyXML on compilers with no or poor STL support. + Only the member functions relevant to the TinyXML project have been implemented. + The buffer allocation is made by a simplistic power of 2 like mechanism : if we increase + a string and there's no more room, we allocate a buffer twice as big as we need. +*/ +class TiXmlString +{ + public : + // The size type used + typedef size_t size_type; + + // Error value for find primitive + static const size_type npos; // = -1; + + + // TiXmlString empty constructor + TiXmlString () : rep_(&nullrep_) + { + } + + // TiXmlString copy constructor + TiXmlString ( const TiXmlString & copy) : rep_(0) + { + init(copy.length()); + memcpy(start(), copy.data(), length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * copy) : rep_(0) + { + init( static_cast( strlen(copy) )); + memcpy(start(), copy, length()); + } + + // TiXmlString constructor, based on a string + TIXML_EXPLICIT TiXmlString ( const char * str, size_type len) : rep_(0) + { + init(len); + memcpy(start(), str, len); + } + + // TiXmlString destructor + ~TiXmlString () + { + quit(); + } + + TiXmlString& operator = (const char * copy) + { + return assign( copy, (size_type)strlen(copy)); + } + + TiXmlString& operator = (const TiXmlString & copy) + { + return assign(copy.start(), copy.length()); + } + + + // += operator. Maps to append + TiXmlString& operator += (const char * suffix) + { + return append(suffix, static_cast( strlen(suffix) )); + } + + // += operator. Maps to append + TiXmlString& operator += (char single) + { + return append(&single, 1); + } + + // += operator. Maps to append + TiXmlString& operator += (const TiXmlString & suffix) + { + return append(suffix.data(), suffix.length()); + } + + + // Convert a TiXmlString into a null-terminated char * + const char * c_str () const { return rep_->str; } + + // Convert a TiXmlString into a char * (need not be null terminated). + const char * data () const { return rep_->str; } + + // Return the length of a TiXmlString + size_type length () const { return rep_->size; } + + // Alias for length() + size_type size () const { return rep_->size; } + + // Checks if a TiXmlString is empty + bool empty () const { return rep_->size == 0; } + + // Return capacity of string + size_type capacity () const { return rep_->capacity; } + + + // single char extraction + const char& at (size_type index) const + { + assert( index < length() ); + return rep_->str[ index ]; + } + + // [] operator + char& operator [] (size_type index) const + { + assert( index < length() ); + return rep_->str[ index ]; + } + + // find a char in a string. Return TiXmlString::npos if not found + size_type find (char lookup) const + { + return find(lookup, 0); + } + + // find a char in a string from an offset. Return TiXmlString::npos if not found + size_type find (char tofind, size_type offset) const + { + if (offset >= length()) return npos; + + for (const char* p = c_str() + offset; *p != '\0'; ++p) + { + if (*p == tofind) return static_cast< size_type >( p - c_str() ); + } + return npos; + } + + void clear () + { + //Lee: + //The original was just too strange, though correct: + // TiXmlString().swap(*this); + //Instead use the quit & re-init: + quit(); + init(0,0); + } + + /* Function to reserve a big amount of data when we know we'll need it. Be aware that this + function DOES NOT clear the content of the TiXmlString if any exists. + */ + void reserve (size_type cap); + + TiXmlString& assign (const char* str, size_type len); + + TiXmlString& append (const char* str, size_type len); + + void swap (TiXmlString& other) + { + Rep* r = rep_; + rep_ = other.rep_; + other.rep_ = r; + } + + private: + + void init(size_type sz) { init(sz, sz); } + void set_size(size_type sz) { rep_->str[ rep_->size = sz ] = '\0'; } + char* start() const { return rep_->str; } + char* finish() const { return rep_->str + rep_->size; } + + struct Rep + { + size_type size, capacity; + char str[1]; + }; + + void init(size_type sz, size_type cap) + { + if (cap) + { + // Lee: the original form: + // rep_ = static_cast(operator new(sizeof(Rep) + cap)); + // doesn't work in some cases of new being overloaded. Switching + // to the normal allocation, although use an 'int' for systems + // that are overly picky about structure alignment. + const size_type bytesNeeded = sizeof(Rep) + cap; + const size_type intsNeeded = ( bytesNeeded + sizeof(int) - 1 ) / sizeof( int ); + rep_ = reinterpret_cast( new int[ intsNeeded ] ); + + rep_->str[ rep_->size = sz ] = '\0'; + rep_->capacity = cap; + } + else + { + rep_ = &nullrep_; + } + } + + void quit() + { + if (rep_ != &nullrep_) + { + // The rep_ is really an array of ints. (see the allocator, above). + // Cast it back before delete, so the compiler won't incorrectly call destructors. + delete [] ( reinterpret_cast( rep_ ) ); + } + } + + Rep * rep_; + static Rep nullrep_; + +} ; + + +inline bool operator == (const TiXmlString & a, const TiXmlString & b) +{ + return ( a.length() == b.length() ) // optimization on some platforms + && ( strcmp(a.c_str(), b.c_str()) == 0 ); // actual compare +} +inline bool operator < (const TiXmlString & a, const TiXmlString & b) +{ + return strcmp(a.c_str(), b.c_str()) < 0; +} + +inline bool operator != (const TiXmlString & a, const TiXmlString & b) { return !(a == b); } +inline bool operator > (const TiXmlString & a, const TiXmlString & b) { return b < a; } +inline bool operator <= (const TiXmlString & a, const TiXmlString & b) { return !(b < a); } +inline bool operator >= (const TiXmlString & a, const TiXmlString & b) { return !(a < b); } + +inline bool operator == (const TiXmlString & a, const char* b) { return strcmp(a.c_str(), b) == 0; } +inline bool operator == (const char* a, const TiXmlString & b) { return b == a; } +inline bool operator != (const TiXmlString & a, const char* b) { return !(a == b); } +inline bool operator != (const char* a, const TiXmlString & b) { return !(b == a); } + +TiXmlString operator + (const TiXmlString & a, const TiXmlString & b); +TiXmlString operator + (const TiXmlString & a, const char* b); +TiXmlString operator + (const char* a, const TiXmlString & b); + + +/* + TiXmlOutStream is an emulation of std::ostream. It is based on TiXmlString. + Only the operators that we need for TinyXML have been developped. +*/ +class TiXmlOutStream : public TiXmlString +{ +public : + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const TiXmlString & in) + { + *this += in; + return *this; + } + + // TiXmlOutStream << operator. + TiXmlOutStream & operator << (const char * in) + { + *this += in; + return *this; + } + +} ; + +#endif // TIXML_STRING_INCLUDED +#endif // TIXML_USE_STL diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.cpp new file mode 100644 index 0000000..9c161df --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.cpp @@ -0,0 +1,1886 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include + +#ifdef TIXML_USE_STL +#include +#include +#endif + +#include "tinyxml.h" + +FILE* TiXmlFOpen( const char* filename, const char* mode ); + +bool TiXmlBase::condenseWhiteSpace = true; + +// Microsoft compiler security +FILE* TiXmlFOpen( const char* filename, const char* mode ) +{ + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + FILE* fp = 0; + errno_t err = fopen_s( &fp, filename, mode ); + if ( !err && fp ) + return fp; + return 0; + #else + return fopen( filename, mode ); + #endif +} + +void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString ) +{ + int i=0; + + while( i<(int)str.length() ) + { + unsigned char c = (unsigned char) str[i]; + + if ( c == '&' + && i < ( (int)str.length() - 2 ) + && str[i+1] == '#' + && str[i+2] == 'x' ) + { + // Hexadecimal character reference. + // Pass through unchanged. + // © -- copyright symbol, for example. + // + // The -1 is a bug fix from Rob Laveaux. It keeps + // an overflow from happening if there is no ';'. + // There are actually 2 ways to exit this loop - + // while fails (error case) and break (semicolon found). + // However, there is no mechanism (currently) for + // this function to return an error. + while ( i<(int)str.length()-1 ) + { + outString->append( str.c_str() + i, 1 ); + ++i; + if ( str[i] == ';' ) + break; + } + } + else if ( c == '&' ) + { + outString->append( entity[0].str, entity[0].strLength ); + ++i; + } + else if ( c == '<' ) + { + outString->append( entity[1].str, entity[1].strLength ); + ++i; + } + else if ( c == '>' ) + { + outString->append( entity[2].str, entity[2].strLength ); + ++i; + } + else if ( c == '\"' ) + { + outString->append( entity[3].str, entity[3].strLength ); + ++i; + } + else if ( c == '\'' ) + { + outString->append( entity[4].str, entity[4].strLength ); + ++i; + } + else if ( c < 32 ) + { + // Easy pass at non-alpha/numeric/symbol + // Below 32 is symbolic. + char buf[ 32 ]; + + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) ); + #else + sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); + #endif + + //*ME: warning C4267: convert 'size_t' to 'int' + //*ME: Int-Cast to make compiler happy ... + outString->append( buf, (int)strlen( buf ) ); + ++i; + } + else + { + //char realc = (char) c; + //outString->append( &realc, 1 ); + *outString += (char) c; // somewhat more efficient function call. + ++i; + } + } +} + + +TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() +{ + parent = 0; + type = _type; + firstChild = 0; + lastChild = 0; + prev = 0; + next = 0; +} + + +TiXmlNode::~TiXmlNode() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } +} + + +void TiXmlNode::CopyTo( TiXmlNode* target ) const +{ + target->SetValue (value.c_str() ); + target->userData = userData; + target->location = location; +} + + +void TiXmlNode::Clear() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } + + firstChild = 0; + lastChild = 0; +} + + +TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) +{ + assert( node->parent == 0 || node->parent == this ); + assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); + + if ( node->Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + delete node; + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + node->parent = this; + + node->prev = lastChild; + node->next = 0; + + if ( lastChild ) + lastChild->next = node; + else + firstChild = node; // it was an empty list. + + lastChild = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) +{ + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + + return LinkEndChild( node ); +} + + +TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) +{ + if ( !beforeThis || beforeThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->next = beforeThis; + node->prev = beforeThis->prev; + if ( beforeThis->prev ) + { + beforeThis->prev->next = node; + } + else + { + assert( firstChild == beforeThis ); + firstChild = node; + } + beforeThis->prev = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) +{ + if ( !afterThis || afterThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::TINYXML_DOCUMENT ) + { + if ( GetDocument() ) + GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->prev = afterThis; + node->next = afterThis->next; + if ( afterThis->next ) + { + afterThis->next->prev = node; + } + else + { + assert( lastChild == afterThis ); + lastChild = node; + } + afterThis->next = node; + return node; +} + + +TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) +{ + if ( !replaceThis ) + return 0; + + if ( replaceThis->parent != this ) + return 0; + + if ( withThis.ToDocument() ) { + // A document can never be a child. Thanks to Noam. + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = withThis.Clone(); + if ( !node ) + return 0; + + node->next = replaceThis->next; + node->prev = replaceThis->prev; + + if ( replaceThis->next ) + replaceThis->next->prev = node; + else + lastChild = node; + + if ( replaceThis->prev ) + replaceThis->prev->next = node; + else + firstChild = node; + + delete replaceThis; + node->parent = this; + return node; +} + + +bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) +{ + if ( !removeThis ) { + return false; + } + + if ( removeThis->parent != this ) + { + assert( 0 ); + return false; + } + + if ( removeThis->next ) + removeThis->next->prev = removeThis->prev; + else + lastChild = removeThis->prev; + + if ( removeThis->prev ) + removeThis->prev->next = removeThis->next; + else + firstChild = removeThis->next; + + delete removeThis; + return true; +} + +const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = firstChild; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = lastChild; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild(); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling(); + } +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild( val ); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling( val ); + } +} + + +const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = next; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = prev; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +void TiXmlElement::RemoveAttribute( const char * name ) +{ + #ifdef TIXML_USE_STL + TIXML_STRING str( name ); + TiXmlAttribute* node = attributeSet.Find( str ); + #else + TiXmlAttribute* node = attributeSet.Find( name ); + #endif + if ( node ) + { + attributeSet.Remove( node ); + delete node; + } +} + +const TiXmlElement* TiXmlNode::FirstChildElement() const +{ + const TiXmlNode* node; + + for ( node = FirstChild(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = FirstChild( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement() const +{ + const TiXmlNode* node; + + for ( node = NextSibling(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = NextSibling( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlDocument* TiXmlNode::GetDocument() const +{ + const TiXmlNode* node; + + for( node = this; node; node = node->parent ) + { + if ( node->ToDocument() ) + return node->ToDocument(); + } + return 0; +} + + +TiXmlElement::TiXmlElement (const char * _value) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} + + +#ifdef TIXML_USE_STL +TiXmlElement::TiXmlElement( const std::string& _value ) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} +#endif + + +TiXmlElement::TiXmlElement( const TiXmlElement& copy) + : TiXmlNode( TiXmlNode::TINYXML_ELEMENT ) +{ + firstChild = lastChild = 0; + copy.CopyTo( this ); +} + + +TiXmlElement& TiXmlElement::operator=( const TiXmlElement& base ) +{ + ClearThis(); + base.CopyTo( this ); + return *this; +} + + +TiXmlElement::~TiXmlElement() +{ + ClearThis(); +} + + +void TiXmlElement::ClearThis() +{ + Clear(); + while( attributeSet.First() ) + { + TiXmlAttribute* node = attributeSet.First(); + attributeSet.Remove( node ); + delete node; + } +} + + +const char* TiXmlElement::Attribute( const char* name ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( node ) + return node->Value(); + return 0; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( attrib ) + return &attrib->ValueStr(); + return 0; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( i ) { + attrib->QueryIntValue( i ); + } + } + return result; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const char* result = 0; + + if ( attrib ) { + result = attrib->Value(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + const std::string* result = 0; + + if ( attrib ) { + result = &attrib->ValueStr(); + if ( d ) { + attrib->QueryDoubleValue( d ); + } + } + return result; +} +#endif + + +int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} + + +int TiXmlElement::QueryUnsignedAttribute( const char* name, unsigned* value ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + int ival = 0; + int result = node->QueryIntValue( &ival ); + *value = (unsigned)ival; + return result; +} + + +int TiXmlElement::QueryBoolAttribute( const char* name, bool* bval ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + int result = TIXML_WRONG_TYPE; + if ( StringEqual( node->Value(), "true", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "yes", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "1", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = true; + result = TIXML_SUCCESS; + } + else if ( StringEqual( node->Value(), "false", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "no", true, TIXML_ENCODING_UNKNOWN ) + || StringEqual( node->Value(), "0", true, TIXML_ENCODING_UNKNOWN ) ) + { + *bval = false; + result = TIXML_SUCCESS; + } + return result; +} + + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryIntValue( ival ); +} +#endif + + +int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryDoubleValue( dval ); +} + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const +{ + const TiXmlAttribute* attrib = attributeSet.Find( name ); + if ( !attrib ) + return TIXML_NO_ATTRIBUTE; + return attrib->QueryDoubleValue( dval ); +} +#endif + + +void TiXmlElement::SetAttribute( const char * name, int val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& name, int val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetIntValue( val ); + } +} +#endif + + +void TiXmlElement::SetDoubleAttribute( const char * name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetDoubleAttribute( const std::string& name, double val ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( name ); + if ( attrib ) { + attrib->SetDoubleValue( val ); + } +} +#endif + + +void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( cname ); + if ( attrib ) { + attrib->SetValue( cvalue ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& _name, const std::string& _value ) +{ + TiXmlAttribute* attrib = attributeSet.FindOrCreate( _name ); + if ( attrib ) { + attrib->SetValue( _value ); + } +} +#endif + + +void TiXmlElement::Print( FILE* cfile, int depth ) const +{ + int i; + assert( cfile ); + for ( i=0; iNext() ) + { + fprintf( cfile, " " ); + attrib->Print( cfile, depth ); + } + + // There are 3 different formatting approaches: + // 1) An element without children is printed as a node + // 2) An element with only a text child is printed as text + // 3) An element with children is printed on multiple lines. + TiXmlNode* node; + if ( !firstChild ) + { + fprintf( cfile, " />" ); + } + else if ( firstChild == lastChild && firstChild->ToText() ) + { + fprintf( cfile, ">" ); + firstChild->Print( cfile, depth + 1 ); + fprintf( cfile, "", value.c_str() ); + } + else + { + fprintf( cfile, ">" ); + + for ( node = firstChild; node; node=node->NextSibling() ) + { + if ( !node->ToText() ) + { + fprintf( cfile, "\n" ); + } + node->Print( cfile, depth+1 ); + } + fprintf( cfile, "\n" ); + for( i=0; i", value.c_str() ); + } +} + + +void TiXmlElement::CopyTo( TiXmlElement* target ) const +{ + // superclass: + TiXmlNode::CopyTo( target ); + + // Element class: + // Clone the attributes, then clone the children. + const TiXmlAttribute* attribute = 0; + for( attribute = attributeSet.First(); + attribute; + attribute = attribute->Next() ) + { + target->SetAttribute( attribute->Name(), attribute->Value() ); + } + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + +bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this, attributeSet.First() ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +TiXmlNode* TiXmlElement::Clone() const +{ + TiXmlElement* clone = new TiXmlElement( Value() ); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +const char* TiXmlElement::GetText() const +{ + const TiXmlNode* child = this->FirstChild(); + if ( child ) { + const TiXmlText* childText = child->ToText(); + if ( childText ) { + return childText->Value(); + } + } + return 0; +} + + +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + ClearError(); +} + +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} + + +#ifdef TIXML_USE_STL +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} +#endif + + +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::TINYXML_DOCUMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlDocument& TiXmlDocument::operator=( const TiXmlDocument& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) +{ + return LoadFile( Value(), encoding ); +} + + +bool TiXmlDocument::SaveFile() const +{ + return SaveFile( Value() ); +} + +bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) +{ + TIXML_STRING filename( _filename ); + value = filename; + + // reading in binary mode so that tinyxml can normalize the EOL + FILE* file = TiXmlFOpen( value.c_str (), "rb" ); + + if ( file ) + { + bool result = LoadFile( file, encoding ); + fclose( file ); + return result; + } + else + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } +} + +bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) +{ + if ( !file ) + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Delete the existing data: + Clear(); + location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = 0; + fseek( file, 0, SEEK_END ); + length = ftell( file ); + fseek( file, 0, SEEK_SET ); + + // Strange case, but good to handle up front. + if ( length <= 0 ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Subtle bug here. TinyXml did use fgets. But from the XML spec: + // 2.11 End-of-Line Handling + // + // + // ...the XML processor MUST behave as if it normalized all line breaks in external + // parsed entities (including the document entity) on input, before parsing, by translating + // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to + // a single #xA character. + // + // + // It is not clear fgets does that, and certainly isn't clear it works cross platform. + // Generally, you expect fgets to translate from the convention of the OS to the c/unix + // convention, and not work generally. + + /* + while( fgets( buf, sizeof(buf), file ) ) + { + data += buf; + } + */ + + char* buf = new char[ length+1 ]; + buf[0] = 0; + + if ( fread( buf, length, 1, file ) != 1 ) { + delete [] buf; + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Process the buffer in place to normalize new lines. (See comment above.) + // Copies from the 'p' to 'q' pointer, where p can advance faster if + // a newline-carriage return is hit. + // + // Wikipedia: + // Systems based on ASCII or a compatible character set use either LF (Line feed, '\n', 0x0A, 10 in decimal) or + // CR (Carriage return, '\r', 0x0D, 13 in decimal) individually, or CR followed by LF (CR+LF, 0x0D 0x0A)... + // * LF: Multics, Unix and Unix-like systems (GNU/Linux, AIX, Xenix, Mac OS X, FreeBSD, etc.), BeOS, Amiga, RISC OS, and others + // * CR+LF: DEC RT-11 and most other early non-Unix, non-IBM OSes, CP/M, MP/M, DOS, OS/2, Microsoft Windows, Symbian OS + // * CR: Commodore 8-bit machines, Apple II family, Mac OS up to version 9 and OS-9 + + const char* p = buf; // the read head + char* q = buf; // the write head + const char CR = 0x0d; + const char LF = 0x0a; + + buf[length] = 0; + while( *p ) { + assert( p < (buf+length) ); + assert( q <= (buf+length) ); + assert( q <= p ); + + if ( *p == CR ) { + *q++ = LF; + p++; + if ( *p == LF ) { // check for CR+LF (and skip LF) + p++; + } + } + else { + *q++ = *p++; + } + } + assert( q <= (buf+length) ); + *q = 0; + + Parse( buf, 0, encoding ); + + delete [] buf; + return !Error(); +} + + +bool TiXmlDocument::SaveFile( const char * filename ) const +{ + // The old c stuff lives on... + FILE* fp = TiXmlFOpen( filename, "w" ); + if ( fp ) + { + bool result = SaveFile( fp ); + fclose( fp ); + return result; + } + return false; +} + + +bool TiXmlDocument::SaveFile( FILE* fp ) const +{ + if ( useMicrosoftBOM ) + { + const unsigned char TIXML_UTF_LEAD_0 = 0xefU; + const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; + const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + fputc( TIXML_UTF_LEAD_0, fp ); + fputc( TIXML_UTF_LEAD_1, fp ); + fputc( TIXML_UTF_LEAD_2, fp ); + } + Print( fp, 0 ); + return (ferror(fp) == 0); +} + + +void TiXmlDocument::CopyTo( TiXmlDocument* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->error = error; + target->errorId = errorId; + target->errorDesc = errorDesc; + target->tabsize = tabsize; + target->errorLocation = errorLocation; + target->useMicrosoftBOM = useMicrosoftBOM; + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + + +TiXmlNode* TiXmlDocument::Clone() const +{ + TiXmlDocument* clone = new TiXmlDocument(); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlDocument::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + node->Print( cfile, depth ); + fprintf( cfile, "\n" ); + } +} + + +bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +const TiXmlAttribute* TiXmlAttribute::Next() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} + +/* +TiXmlAttribute* TiXmlAttribute::Next() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} +*/ + +const TiXmlAttribute* TiXmlAttribute::Previous() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} + +/* +TiXmlAttribute* TiXmlAttribute::Previous() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} +*/ + +void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + TIXML_STRING n, v; + + EncodeString( name, &n ); + EncodeString( value, &v ); + + if (value.find ('\"') == TIXML_STRING::npos) { + if ( cfile ) { + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; + } + } + else { + if ( cfile ) { + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; + } + } +} + + +int TiXmlAttribute::QueryIntValue( int* ival ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +int TiXmlAttribute::QueryDoubleValue( double* dval ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +void TiXmlAttribute::SetIntValue( int _value ) +{ + char buf [64]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); + #else + sprintf (buf, "%d", _value); + #endif + SetValue (buf); +} + +void TiXmlAttribute::SetDoubleValue( double _value ) +{ + char buf [256]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%g", _value); + #else + sprintf (buf, "%g", _value); + #endif + SetValue (buf); +} + +int TiXmlAttribute::IntValue() const +{ + return atoi (value.c_str ()); +} + +double TiXmlAttribute::DoubleValue() const +{ + return atof (value.c_str ()); +} + + +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) +{ + copy.CopyTo( this ); +} + + +TiXmlComment& TiXmlComment::operator=( const TiXmlComment& base ) +{ + Clear(); + base.CopyTo( this ); + return *this; +} + + +void TiXmlComment::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlComment::CopyTo( TiXmlComment* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlComment::Clone() const +{ + TiXmlComment* clone = new TiXmlComment(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlText::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + if ( cdata ) + { + int i; + fprintf( cfile, "\n" ); + for ( i=0; i\n", value.c_str() ); // unformatted output + } + else + { + TIXML_STRING buffer; + EncodeString( value, &buffer ); + fprintf( cfile, "%s", buffer.c_str() ); + } +} + + +void TiXmlText::CopyTo( TiXmlText* target ) const +{ + TiXmlNode::CopyTo( target ); + target->cdata = cdata; +} + + +bool TiXmlText::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlText::Clone() const +{ + TiXmlText* clone = 0; + clone = new TiXmlText( "" ); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlDeclaration::TiXmlDeclaration( const char * _version, + const char * _encoding, + const char * _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} + + +#ifdef TIXML_USE_STL +TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} +#endif + + +TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) + : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) +{ + copy.CopyTo( this ); +} + + +TiXmlDeclaration& TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +{ + Clear(); + copy.CopyTo( this ); + return *this; +} + + +void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + if ( cfile ) fprintf( cfile, "" ); + if ( str ) (*str) += "?>"; +} + + +void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->version = version; + target->encoding = encoding; + target->standalone = standalone; +} + + +bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlDeclaration::Clone() const +{ + TiXmlDeclaration* clone = new TiXmlDeclaration(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlUnknown::Print( FILE* cfile, int depth ) const +{ + for ( int i=0; i", value.c_str() ); +} + + +void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlUnknown::Clone() const +{ + TiXmlUnknown* clone = new TiXmlUnknown(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlAttributeSet::TiXmlAttributeSet() +{ + sentinel.next = &sentinel; + sentinel.prev = &sentinel; +} + + +TiXmlAttributeSet::~TiXmlAttributeSet() +{ + assert( sentinel.next == &sentinel ); + assert( sentinel.prev == &sentinel ); +} + + +void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) +{ + #ifdef TIXML_USE_STL + assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. + #else + assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. + #endif + + addMe->next = &sentinel; + addMe->prev = sentinel.prev; + + sentinel.prev->next = addMe; + sentinel.prev = addMe; +} + +void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) +{ + TiXmlAttribute* node; + + for( node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node == removeMe ) + { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; + return; + } + } + assert( 0 ); // we tried to remove a non-linked attribute. +} + + +#ifdef TIXML_USE_STL +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node->name == name ) + return node; + } + return 0; +} + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const std::string& _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); + } + return attrib; +} +#endif + + +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( strcmp( node->name.c_str(), name ) == 0 ) + return node; + } + return 0; +} + + +TiXmlAttribute* TiXmlAttributeSet::FindOrCreate( const char* _name ) +{ + TiXmlAttribute* attrib = Find( _name ); + if ( !attrib ) { + attrib = new TiXmlAttribute(); + Add( attrib ); + attrib->SetName( _name ); + } + return attrib; +} + + +#ifdef TIXML_USE_STL +std::istream& operator>> (std::istream & in, TiXmlNode & base) +{ + TIXML_STRING tag; + tag.reserve( 8 * 1000 ); + base.StreamIn( &in, &tag ); + + base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); + return in; +} +#endif + + +#ifdef TIXML_USE_STL +std::ostream& operator<< (std::ostream & out, const TiXmlNode & base) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out << printer.Str(); + + return out; +} + + +std::string& operator<< (std::string& out, const TiXmlNode& base ) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out.append( printer.Str() ); + + return out; +} +#endif + + +TiXmlHandle TiXmlHandle::FirstChild() const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement() const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild(); + for ( i=0; + child && iNextSibling(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild( value ); + for ( i=0; + child && iNextSibling( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement(); + for ( i=0; + child && iNextSiblingElement(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement( value ); + for ( i=0; + child && iNextSiblingElement( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +bool TiXmlPrinter::VisitEnter( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitExit( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) +{ + DoIndent(); + buffer += "<"; + buffer += element.Value(); + + for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ) + { + buffer += " "; + attrib->Print( 0, 0, &buffer ); + } + + if ( !element.FirstChild() ) + { + buffer += " />"; + DoLineBreak(); + } + else + { + buffer += ">"; + if ( element.FirstChild()->ToText() + && element.LastChild() == element.FirstChild() + && element.FirstChild()->ToText()->CDATA() == false ) + { + simpleTextPrint = true; + // no DoLineBreak()! + } + else + { + DoLineBreak(); + } + } + ++depth; + return true; +} + + +bool TiXmlPrinter::VisitExit( const TiXmlElement& element ) +{ + --depth; + if ( !element.FirstChild() ) + { + // nothing. + } + else + { + if ( simpleTextPrint ) + { + simpleTextPrint = false; + } + else + { + DoIndent(); + } + buffer += ""; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlText& text ) +{ + if ( text.CDATA() ) + { + DoIndent(); + buffer += ""; + DoLineBreak(); + } + else if ( simpleTextPrint ) + { + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + } + else + { + DoIndent(); + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration ) +{ + DoIndent(); + declaration.Print( 0, 0, &buffer ); + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlComment& comment ) +{ + DoIndent(); + buffer += ""; + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown ) +{ + DoIndent(); + buffer += "<"; + buffer += unknown.Value(); + buffer += ">"; + DoLineBreak(); + return true; +} + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.h new file mode 100644 index 0000000..a3589e5 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/tinyxml.h @@ -0,0 +1,1805 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TINYXML_INCLUDED +#define TINYXML_INCLUDED + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4530 ) +#pragma warning( disable : 4786 ) +#endif + +#include +#include +#include +#include +#include + +// Help out windows: +#if defined( _DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#ifdef TIXML_USE_STL + #include + #include + #include + #define TIXML_STRING std::string +#else + #include "tinystr.h" + #define TIXML_STRING TiXmlString +#endif + +// Deprecated library function hell. Compilers want to use the +// new safe versions. This probably doesn't fully address the problem, +// but it gets closer. There are too many compilers for me to fully +// test. If you get compilation troubles, undefine TIXML_SAFE +#define TIXML_SAFE + +#ifdef TIXML_SAFE + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + // Microsoft visual studio, version 2005 and higher. + #define TIXML_SNPRINTF _snprintf_s + #define TIXML_SSCANF sscanf_s + #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + //#pragma message( "Using _sn* functions." ) + #define TIXML_SNPRINTF _snprintf + #define TIXML_SSCANF sscanf + #elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #else + #define TIXML_SNPRINTF snprintf + #define TIXML_SSCANF sscanf + #endif +#endif + +class TiXmlDocument; +class TiXmlElement; +class TiXmlComment; +class TiXmlUnknown; +class TiXmlAttribute; +class TiXmlText; +class TiXmlDeclaration; +class TiXmlParsingData; + +const int TIXML_MAJOR_VERSION = 2; +const int TIXML_MINOR_VERSION = 6; +const int TIXML_PATCH_VERSION = 2; + +/* Internal structure for tracking location of items + in the XML file. +*/ +struct TiXmlCursor +{ + TiXmlCursor() { Clear(); } + void Clear() { row = col = -1; } + + int row; // 0 based. + int col; // 0 based. +}; + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a TiXmlVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its sibilings will be Visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. + + You should never change the document from a callback. + + @sa TiXmlNode::Accept() +*/ +class TiXmlVisitor +{ +public: + virtual ~TiXmlVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } + /// Visit a document. + virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } + + /// Visit an element. + virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } + /// Visit an element. + virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } + + /// Visit a declaration + virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } + /// Visit a text node + virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } + /// Visit a comment node + virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } + /// Visit an unknown node + virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } +}; + +// Only used by Attribute::Query functions +enum +{ + TIXML_SUCCESS, + TIXML_NO_ATTRIBUTE, + TIXML_WRONG_TYPE +}; + + +// Used by the parsing routines. +enum TiXmlEncoding +{ + TIXML_ENCODING_UNKNOWN, + TIXML_ENCODING_UTF8, + TIXML_ENCODING_LEGACY +}; + +const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; + +/** TiXmlBase is a base class for every class in TinyXml. + It does little except to establish that TinyXml classes + can be printed and provide some utility functions. + + In XML, the document and elements can contain + other elements and other types of nodes. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + A Decleration contains: Attributes (not on tree) + @endverbatim +*/ +class TiXmlBase +{ + friend class TiXmlNode; + friend class TiXmlElement; + friend class TiXmlDocument; + +public: + TiXmlBase() : userData(0) {} + virtual ~TiXmlBase() {} + + /** All TinyXml classes can print themselves to a filestream + or the string class (TiXmlString in non-STL mode, std::string + in STL mode.) Either or both cfile and str can be null. + + This is a formatted print, and will insert + tabs and newlines. + + (For an unformatted stream, use the << operator.) + */ + virtual void Print( FILE* cfile, int depth ) const = 0; + + /** The world does not agree on whether white space should be kept or + not. In order to make everyone happy, these global, static functions + are provided to set whether or not TinyXml will condense all white space + into a single space or not. The default is to condense. Note changing this + value is not thread safe. + */ + static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } + + /// Return the current white space setting. + static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } + + /** Return the position, in the original source file, of this node or attribute. + The row and column are 1-based. (That is the first row and first column is + 1,1). If the returns values are 0 or less, then the parser does not have + a row and column value. + + Generally, the row and column value will be set when the TiXmlDocument::Load(), + TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set + when the DOM was created from operator>>. + + The values reflect the initial load. Once the DOM is modified programmatically + (by adding or changing nodes and attributes) the new values will NOT update to + reflect changes in the document. + + There is a minor performance cost to computing the row and column. Computation + can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. + + @sa TiXmlDocument::SetTabSize() + */ + int Row() const { return location.row + 1; } + int Column() const { return location.col + 1; } ///< See Row() + + void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. + void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. + const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. + + // Table that returs, for a given lead byte, the total number of bytes + // in the UTF-8 sequence. + static const int utf8ByteTable[256]; + + virtual const char* Parse( const char* p, + TiXmlParsingData* data, + TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; + + /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, + or they will be transformed into entities! + */ + static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); + + enum + { + TIXML_NO_ERROR = 0, + TIXML_ERROR, + TIXML_ERROR_OPENING_FILE, + TIXML_ERROR_PARSING_ELEMENT, + TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, + TIXML_ERROR_READING_ELEMENT_VALUE, + TIXML_ERROR_READING_ATTRIBUTES, + TIXML_ERROR_PARSING_EMPTY, + TIXML_ERROR_READING_END_TAG, + TIXML_ERROR_PARSING_UNKNOWN, + TIXML_ERROR_PARSING_COMMENT, + TIXML_ERROR_PARSING_DECLARATION, + TIXML_ERROR_DOCUMENT_EMPTY, + TIXML_ERROR_EMBEDDED_NULL, + TIXML_ERROR_PARSING_CDATA, + TIXML_ERROR_DOCUMENT_TOP_ONLY, + + TIXML_ERROR_STRING_COUNT + }; + +protected: + + static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + + inline static bool IsWhiteSpace( char c ) + { + return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); + } + inline static bool IsWhiteSpace( int c ) + { + if ( c < 256 ) + return IsWhiteSpace( (char) c ); + return false; // Again, only truly correct for English/Latin...but usually works. + } + + #ifdef TIXML_USE_STL + static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); + static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); + #endif + + /* Reads an XML name into the string provided. Returns + a pointer just past the last character of the name, + or 0 if the function has an error. + */ + static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); + + /* Reads text. Returns a pointer past the given end tag. + Wickedly complex options, but it keeps the (sensitive) code in one place. + */ + static const char* ReadText( const char* in, // where to start + TIXML_STRING* text, // the string read + bool ignoreWhiteSpace, // whether to keep the white space + const char* endTag, // what ends this text + bool ignoreCase, // whether to ignore case in the end tag + TiXmlEncoding encoding ); // the current encoding + + // If an entity has been found, transform it into a character. + static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); + + // Get a character, while interpreting entities. + // The length can be from 0 to 4 bytes. + inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) + { + assert( p ); + if ( encoding == TIXML_ENCODING_UTF8 ) + { + *length = utf8ByteTable[ *((const unsigned char*)p) ]; + assert( *length >= 0 && *length < 5 ); + } + else + { + *length = 1; + } + + if ( *length == 1 ) + { + if ( *p == '&' ) + return GetEntity( p, _value, length, encoding ); + *_value = *p; + return p+1; + } + else if ( *length ) + { + //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), + // and the null terminator isn't needed + for( int i=0; p[i] && i<*length; ++i ) { + _value[i] = p[i]; + } + return p + (*length); + } + else + { + // Not valid text. + return 0; + } + } + + // Return true if the next characters in the stream are any of the endTag sequences. + // Ignore case only works for english, and should only be relied on when comparing + // to English words: StringEqual( p, "version", true ) is fine. + static bool StringEqual( const char* p, + const char* endTag, + bool ignoreCase, + TiXmlEncoding encoding ); + + static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; + + TiXmlCursor location; + + /// Field containing a generic user pointer + void* userData; + + // None of these methods are reliable for any language except English. + // Good for approximation, not great for accuracy. + static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); + static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); + inline static int ToLower( int v, TiXmlEncoding encoding ) + { + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( v < 128 ) return tolower( v ); + return v; + } + else + { + return tolower( v ); + } + } + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + +private: + TiXmlBase( const TiXmlBase& ); // not implemented. + void operator=( const TiXmlBase& base ); // not allowed. + + struct Entity + { + const char* str; + unsigned int strLength; + char chr; + }; + enum + { + NUM_ENTITY = 5, + MAX_ENTITY_LENGTH = 6 + + }; + static Entity entity[ NUM_ENTITY ]; + static bool condenseWhiteSpace; +}; + + +/** The parent class for everything in the Document Object Model. + (Except for attributes). + Nodes have siblings, a parent, and children. A node can be + in a document, or stand on its own. The type of a TiXmlNode + can be queried, and it can be cast to its more defined type. +*/ +class TiXmlNode : public TiXmlBase +{ + friend class TiXmlDocument; + friend class TiXmlElement; + +public: + #ifdef TIXML_USE_STL + + /** An input stream operator, for every class. Tolerant of newlines and + formatting, but doesn't expect them. + */ + friend std::istream& operator >> (std::istream& in, TiXmlNode& base); + + /** An output stream operator, for every class. Note that this outputs + without any newlines or formatting, as opposed to Print(), which + includes tabs and new lines. + + The operator<< and operator>> are not completely symmetric. Writing + a node to a stream is very well defined. You'll get a nice stream + of output, without any extra whitespace or newlines. + + But reading is not as well defined. (As it always is.) If you create + a TiXmlElement (for example) and read that from an input stream, + the text needs to define an element or junk will result. This is + true of all input streams, but it's worth keeping in mind. + + A TiXmlDocument will read nodes until it reads a root element, and + all the children of that root element. + */ + friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); + + /// Appends the XML node or attribute to a std::string. + friend std::string& operator<< (std::string& out, const TiXmlNode& base ); + + #endif + + /** The types of XML nodes supported by TinyXml. (All the + unsupported types are picked up by UNKNOWN.) + */ + enum NodeType + { + TINYXML_DOCUMENT, + TINYXML_ELEMENT, + TINYXML_COMMENT, + TINYXML_UNKNOWN, + TINYXML_TEXT, + TINYXML_DECLARATION, + TINYXML_TYPECOUNT + }; + + virtual ~TiXmlNode(); + + /** The meaning of 'value' changes for the specific type of + TiXmlNode. + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + + The subclasses will wrap this function. + */ + const char *Value() const { return value.c_str (); } + + #ifdef TIXML_USE_STL + /** Return Value() as a std::string. If you only use STL, + this is more efficient than calling Value(). + Only available in STL mode. + */ + const std::string& ValueStr() const { return value; } + #endif + + const TIXML_STRING& ValueTStr() const { return value; } + + /** Changes the value of the node. Defined as: + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + void SetValue(const char * _value) { value = _value;} + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Delete all the children of this node. Does not affect 'this'. + void Clear(); + + /// One step up the DOM. + TiXmlNode* Parent() { return parent; } + const TiXmlNode* Parent() const { return parent; } + + const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. + TiXmlNode* FirstChild() { return firstChild; } + const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. + /// The first child of this node with the matching 'value'. Will be null if none found. + TiXmlNode* FirstChild( const char * _value ) { + // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) + // call the method, cast the return back to non-const. + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); + } + const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. + TiXmlNode* LastChild() { return lastChild; } + + const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. + TiXmlNode* LastChild( const char * _value ) { + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. + #endif + + /** An alternate way to walk the children of a node. + One way to iterate over nodes is: + @verbatim + for( child = parent->FirstChild(); child; child = child->NextSibling() ) + @endverbatim + + IterateChildren does the same thing with the syntax: + @verbatim + child = 0; + while( child = parent->IterateChildren( child ) ) + @endverbatim + + IterateChildren takes the previous child as input and finds + the next one. If the previous child is null, it returns the + first. IterateChildren will return null when done. + */ + const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); + } + + /// This flavor of IterateChildren searches for children with a particular 'value' + const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + #endif + + /** Add a new node related to this. Adds a child past the LastChild. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); + + + /** Add a new node related to this. Adds a child past the LastChild. + + NOTE: the node to be added is passed by pointer, and will be + henceforth owned (and deleted) by tinyXml. This method is efficient + and avoids an extra copy, but should be used with care as it + uses a different memory model than the other insert functions. + + @sa InsertEndChild + */ + TiXmlNode* LinkEndChild( TiXmlNode* addThis ); + + /** Add a new node related to this. Adds a child before the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); + + /** Add a new node related to this. Adds a child after the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); + + /** Replace a child of this node. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); + + /// Delete a child of this node. + bool RemoveChild( TiXmlNode* removeThis ); + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling() const { return prev; } + TiXmlNode* PreviousSibling() { return prev; } + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling( const char * ) const; + TiXmlNode* PreviousSibling( const char *_prev ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Navigate to a sibling node. + const TiXmlNode* NextSibling() const { return next; } + TiXmlNode* NextSibling() { return next; } + + /// Navigate to a sibling node with the given 'value'. + const TiXmlNode* NextSibling( const char * ) const; + TiXmlNode* NextSibling( const char* _next ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement() const; + TiXmlElement* NextSiblingElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement( const char * ) const; + TiXmlElement* NextSiblingElement( const char *_next ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement() const; + TiXmlElement* FirstChildElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); + } + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement( const char * _value ) const; + TiXmlElement* FirstChildElement( const char * _value ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /** Query the type (as an enumerated value, above) of this node. + The possible types are: TINYXML_DOCUMENT, TINYXML_ELEMENT, TINYXML_COMMENT, + TINYXML_UNKNOWN, TINYXML_TEXT, and TINYXML_DECLARATION. + */ + int Type() const { return type; } + + /** Return a pointer to the Document this node lives in. + Returns null if not in a document. + */ + const TiXmlDocument* GetDocument() const; + TiXmlDocument* GetDocument() { + return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); + } + + /// Returns true if this node has no children. + bool NoChildren() const { return !firstChild; } + + virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + /** Create an exact duplicate of this node and return it. The memory must be deleted + by the caller. + */ + virtual TiXmlNode* Clone() const = 0; + + /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the TiXmlVisitor interface. + + This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + TiXmlPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( TiXmlVisitor* visitor ) const = 0; + +protected: + TiXmlNode( NodeType _type ); + + // Copy to the allocated object. Shared functionality between Clone, Copy constructor, + // and the assignment operator. + void CopyTo( TiXmlNode* target ) const; + + #ifdef TIXML_USE_STL + // The real work of the input operator. + virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; + #endif + + // Figure out what is at *p, and parse it. Returns null if it is not an xml node. + TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); + + TiXmlNode* parent; + NodeType type; + + TiXmlNode* firstChild; + TiXmlNode* lastChild; + + TIXML_STRING value; + + TiXmlNode* prev; + TiXmlNode* next; + +private: + TiXmlNode( const TiXmlNode& ); // not implemented. + void operator=( const TiXmlNode& base ); // not allowed. +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not TiXmlNodes, since they are not + part of the tinyXML document object model. There are other + suggested ways to look at this problem. +*/ +class TiXmlAttribute : public TiXmlBase +{ + friend class TiXmlAttributeSet; + +public: + /// Construct an empty attribute. + TiXmlAttribute() : TiXmlBase() + { + document = 0; + prev = next = 0; + } + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlAttribute( const std::string& _name, const std::string& _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + #endif + + /// Construct an attribute with a name and value. + TiXmlAttribute( const char * _name, const char * _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + + const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. + const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. + #ifdef TIXML_USE_STL + const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. + #endif + int IntValue() const; ///< Return the value of this attribute, converted to an integer. + double DoubleValue() const; ///< Return the value of this attribute, converted to a double. + + // Get the tinyxml string representation + const TIXML_STRING& NameTStr() const { return name; } + + /** QueryIntValue examines the value string. It is an alternative to the + IntValue() method with richer error checking. + If the value is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. + + A specialized but useful call. Note that for success it returns 0, + which is the opposite of almost all other TinyXml calls. + */ + int QueryIntValue( int* _value ) const; + /// QueryDoubleValue examines the value string. See QueryIntValue(). + int QueryDoubleValue( double* _value ) const; + + void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. + void SetValue( const char* _value ) { value = _value; } ///< Set the value. + + void SetIntValue( int _value ); ///< Set the value from an integer. + void SetDoubleValue( double _value ); ///< Set the value from a double. + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetName( const std::string& _name ) { name = _name; } + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Get the next sibling attribute in the DOM. Returns null at end. + const TiXmlAttribute* Next() const; + TiXmlAttribute* Next() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); + } + + /// Get the previous sibling attribute in the DOM. Returns null at beginning. + const TiXmlAttribute* Previous() const; + TiXmlAttribute* Previous() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); + } + + bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } + bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } + bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } + + /* Attribute parsing starts: first letter of the name + returns: the next char after the value end quote + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + // Prints this Attribute to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + + // [internal use] + // Set the document pointer so the attribute can report errors. + void SetDocument( TiXmlDocument* doc ) { document = doc; } + +private: + TiXmlAttribute( const TiXmlAttribute& ); // not implemented. + void operator=( const TiXmlAttribute& base ); // not allowed. + + TiXmlDocument* document; // A pointer back to a document, for error reporting. + TIXML_STRING name; + TIXML_STRING value; + TiXmlAttribute* prev; + TiXmlAttribute* next; +}; + + +/* A class used to manage a group of attributes. + It is only used internally, both by the ELEMENT and the DECLARATION. + + The set can be changed transparent to the Element and Declaration + classes that use it, but NOT transparent to the Attribute + which has to implement a next() and previous() method. Which makes + it a bit problematic and prevents the use of STL. + + This version is implemented with circular lists because: + - I like circular lists + - it demonstrates some independence from the (typical) doubly linked list. +*/ +class TiXmlAttributeSet +{ +public: + TiXmlAttributeSet(); + ~TiXmlAttributeSet(); + + void Add( TiXmlAttribute* attribute ); + void Remove( TiXmlAttribute* attribute ); + + const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + + TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* FindOrCreate( const char* _name ); + +# ifdef TIXML_USE_STL + TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* FindOrCreate( const std::string& _name ); +# endif + + +private: + //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), + //*ME: this class must be also use a hidden/disabled copy-constructor !!! + TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed + void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) + + TiXmlAttribute sentinel; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TiXmlElement : public TiXmlNode +{ +public: + /// Construct an element. + TiXmlElement (const char * in_value); + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlElement( const std::string& _value ); + #endif + + TiXmlElement( const TiXmlElement& ); + + TiXmlElement& operator=( const TiXmlElement& base ); + + virtual ~TiXmlElement(); + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + */ + const char* Attribute( const char* name ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an integer, + the integer value will be put in the return 'i', if 'i' + is non-null. + */ + const char* Attribute( const char* name, int* i ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an double, + the double value will be put in the return 'd', if 'd' + is non-null. + */ + const char* Attribute( const char* name, double* d ) const; + + /** QueryIntAttribute examines the attribute - it is an alternative to the + Attribute() method with richer error checking. + If the attribute is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. If the attribute + does not exist, then TIXML_NO_ATTRIBUTE is returned. + */ + int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryUnsignedAttribute examines the attribute - see QueryIntAttribute(). + int QueryUnsignedAttribute( const char* name, unsigned* _value ) const; + /** QueryBoolAttribute examines the attribute - see QueryIntAttribute(). + Note that '1', 'true', or 'yes' are considered true, while '0', 'false' + and 'no' are considered false. + */ + int QueryBoolAttribute( const char* name, bool* _value ) const; + /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). + int QueryDoubleAttribute( const char* name, double* _value ) const; + /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). + int QueryFloatAttribute( const char* name, float* _value ) const { + double d; + int result = QueryDoubleAttribute( name, &d ); + if ( result == TIXML_SUCCESS ) { + *_value = (float)d; + } + return result; + } + + #ifdef TIXML_USE_STL + /// QueryStringAttribute examines the attribute - see QueryIntAttribute(). + int QueryStringAttribute( const char* name, std::string* _value ) const { + const char* cstr = Attribute( name ); + if ( cstr ) { + *_value = std::string( cstr ); + return TIXML_SUCCESS; + } + return TIXML_NO_ATTRIBUTE; + } + + /** Template form of the attribute query which will try to read the + attribute into the specified type. Very easy, very powerful, but + be careful to make sure to call this with the correct type. + + NOTE: This method doesn't work correctly for 'string' types that contain spaces. + + @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE + */ + template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + std::stringstream sstream( node->ValueStr() ); + sstream >> *outValue; + if ( !sstream.fail() ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; + } + + int QueryValueAttribute( const std::string& name, std::string* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + *outValue = node->ValueStr(); + return TIXML_SUCCESS; + } + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char* name, const char * _value ); + + #ifdef TIXML_USE_STL + const std::string* Attribute( const std::string& name ) const; + const std::string* Attribute( const std::string& name, int* i ) const; + const std::string* Attribute( const std::string& name, double* d ) const; + int QueryIntAttribute( const std::string& name, int* _value ) const; + int QueryDoubleAttribute( const std::string& name, double* _value ) const; + + /// STL std::string form. + void SetAttribute( const std::string& name, const std::string& _value ); + ///< STL std::string form. + void SetAttribute( const std::string& name, int _value ); + ///< STL std::string form. + void SetDoubleAttribute( const std::string& name, double value ); + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char * name, int value ); + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetDoubleAttribute( const char * name, double value ); + + /** Deletes an attribute with the given name. + */ + void RemoveAttribute( const char * name ); + #ifdef TIXML_USE_STL + void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. + #endif + + const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. + TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } + const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. + TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the TiXmlText child + and accessing it directly. + + If the first child of 'this' is a TiXmlText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + + WARNING: GetText() accesses a child node - don't become confused with the + similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are + safe type casts on the referenced node. + */ + const char* GetText() const; + + /// Creates a new Element and returns it - the returned element is a copy. + virtual TiXmlNode* Clone() const; + // Print the Element to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: next char past '<' + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + + void CopyTo( TiXmlElement* target ) const; + void ClearThis(); // like clear, but initializes 'this' object as well + + // Used to be public [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + /* [internal use] + Reads the "value" of the element -- another element, or text. + This should terminate with the current end tag. + */ + const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + +private: + TiXmlAttributeSet attributeSet; +}; + + +/** An XML comment. +*/ +class TiXmlComment : public TiXmlNode +{ +public: + /// Constructs an empty comment. + TiXmlComment() : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) {} + /// Construct a comment from text. + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::TINYXML_COMMENT ) { + SetValue( _value ); + } + TiXmlComment( const TiXmlComment& ); + TiXmlComment& operator=( const TiXmlComment& base ); + + virtual ~TiXmlComment() {} + + /// Returns a copy of this Comment. + virtual TiXmlNode* Clone() const; + // Write this Comment to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: at the ! of the !-- + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlComment* target ) const; + + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif +// virtual void StreamOut( TIXML_OSTREAM * out ) const; + +private: + +}; + + +/** XML text. A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCDATA() and query it with CDATA(). +*/ +class TiXmlText : public TiXmlNode +{ + friend class TiXmlElement; +public: + /** Constructor for text element. By default, it is treated as + normal, encoded text. If you want it be output as a CDATA text + element, set the parameter _cdata to 'true' + */ + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + virtual ~TiXmlText() {} + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TINYXML_TEXT) + { + SetValue( initValue ); + cdata = false; + } + #endif + + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TINYXML_TEXT ) { copy.CopyTo( this ); } + TiXmlText& operator=( const TiXmlText& base ) { base.CopyTo( this ); return *this; } + + // Write this text object to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /// Queries whether this represents text using a CDATA section. + bool CDATA() const { return cdata; } + /// Turns on or off a CDATA representation of text. + void SetCDATA( bool _cdata ) { cdata = _cdata; } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + /// [internal use] Creates a new Element and returns it. + virtual TiXmlNode* Clone() const; + void CopyTo( TiXmlText* target ) const; + + bool Blank() const; // returns true if all white space and new lines + // [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + bool cdata; // true if this should be input and output as a CDATA style text element +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXml will happily read or write files without a declaration, + however. There are 3 possible attributes to the declaration: + version, encoding, and standalone. + + Note: In this version of the code, the attributes are + handled as special cases, not generic attributes, simply + because there can only be at most 3 and they are always the same. +*/ +class TiXmlDeclaration : public TiXmlNode +{ +public: + /// Construct an empty declaration. + TiXmlDeclaration() : TiXmlNode( TiXmlNode::TINYXML_DECLARATION ) {} + +#ifdef TIXML_USE_STL + /// Constructor. + TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ); +#endif + + /// Construct. + TiXmlDeclaration( const char* _version, + const char* _encoding, + const char* _standalone ); + + TiXmlDeclaration( const TiXmlDeclaration& copy ); + TiXmlDeclaration& operator=( const TiXmlDeclaration& copy ); + + virtual ~TiXmlDeclaration() {} + + /// Version. Will return an empty string if none was found. + const char *Version() const { return version.c_str (); } + /// Encoding. Will return an empty string if none was found. + const char *Encoding() const { return encoding.c_str (); } + /// Is this a standalone document? + const char *Standalone() const { return standalone.c_str (); } + + /// Creates a copy of this Declaration and returns it. + virtual TiXmlNode* Clone() const; + // Print this declaration to a FILE stream. + virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlDeclaration* target ) const; + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + + TIXML_STRING version; + TIXML_STRING encoding; + TIXML_STRING standalone; +}; + + +/** Any tag that tinyXml doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into TiXmlUnknowns. +*/ +class TiXmlUnknown : public TiXmlNode +{ +public: + TiXmlUnknown() : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) {} + virtual ~TiXmlUnknown() {} + + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::TINYXML_UNKNOWN ) { copy.CopyTo( this ); } + TiXmlUnknown& operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); return *this; } + + /// Creates a copy of this Unknown and returns it. + virtual TiXmlNode* Clone() const; + // Print this Unknown to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected: + void CopyTo( TiXmlUnknown* target ) const; + + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + +}; + + +/** Always the top level node. A document binds together all the + XML pieces. It can be saved, loaded, and printed to the screen. + The 'value' of a document node is the xml file name. +*/ +class TiXmlDocument : public TiXmlNode +{ +public: + /// Create an empty document, that has no name. + TiXmlDocument(); + /// Create a document with a name. The name of the document is also the filename of the xml. + TiXmlDocument( const char * documentName ); + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlDocument( const std::string& documentName ); + #endif + + TiXmlDocument( const TiXmlDocument& copy ); + TiXmlDocument& operator=( const TiXmlDocument& copy ); + + virtual ~TiXmlDocument() {} + + /** Load a file using the current document value. + Returns true if successful. Will delete any existing + document data before loading. + */ + bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the current document value. Returns true if successful. + bool SaveFile() const; + /// Load a file using the given filename. Returns true if successful. + bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given filename. Returns true if successful. + bool SaveFile( const char * filename ) const; + /** Load a file using the given FILE*. Returns true if successful. Note that this method + doesn't stream - the entire object pointed at by the FILE* + will be interpreted as an XML file. TinyXML doesn't stream in XML from the current + file location. Streaming may be added in the future. + */ + bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given FILE*. Returns true if successful. + bool SaveFile( FILE* ) const; + + #ifdef TIXML_USE_STL + bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. + { + return LoadFile( filename.c_str(), encoding ); + } + bool SaveFile( const std::string& filename ) const ///< STL std::string version. + { + return SaveFile( filename.c_str() ); + } + #endif + + /** Parse the given null terminated block of xml data. Passing in an encoding to this + method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml + to use that encoding, regardless of what TinyXml might otherwise try to detect. + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + + /** Get the root element -- the only top level element -- of the document. + In well formed XML, there should only be one. TinyXml is tolerant of + multiple elements at the document level. + */ + const TiXmlElement* RootElement() const { return FirstChildElement(); } + TiXmlElement* RootElement() { return FirstChildElement(); } + + /** If an error occurs, Error will be set to true. Also, + - The ErrorId() will contain the integer identifier of the error (not generally useful) + - The ErrorDesc() method will return the name of the error. (very useful) + - The ErrorRow() and ErrorCol() will return the location of the error (if known) + */ + bool Error() const { return error; } + + /// Contains a textual (english) description of the error if one occurs. + const char * ErrorDesc() const { return errorDesc.c_str (); } + + /** Generally, you probably want the error string ( ErrorDesc() ). But if you + prefer the ErrorId, this function will fetch it. + */ + int ErrorId() const { return errorId; } + + /** Returns the location (if known) of the error. The first column is column 1, + and the first row is row 1. A value of 0 means the row and column wasn't applicable + (memory errors, for example, have no row/column) or the parser lost the error. (An + error in the error reporting, in that case.) + + @sa SetTabSize, Row, Column + */ + int ErrorRow() const { return errorLocation.row+1; } + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + + /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) + to report the correct values for row and column. It does not change the output + or input in any way. + + By calling this method, with a tab size + greater than 0, the row and column of each node and attribute is stored + when the file is loaded. Very useful for tracking the DOM back in to + the source file. + + The tab size is required for calculating the location of nodes. If not + set, the default of 4 is used. The tabsize is set per document. Setting + the tabsize to 0 disables row/column tracking. + + Note that row and column tracking is not supported when using operator>>. + + The tab size needs to be enabled before the parse or load. Correct usage: + @verbatim + TiXmlDocument doc; + doc.SetTabSize( 8 ); + doc.Load( "myfile.xml" ); + @endverbatim + + @sa Row, Column + */ + void SetTabSize( int _tabsize ) { tabsize = _tabsize; } + + int TabSize() const { return tabsize; } + + /** If you have handled the error, it can be reset with this call. The error + state is automatically cleared if you Parse a new XML block. + */ + void ClearError() { error = false; + errorId = 0; + errorDesc = ""; + errorLocation.row = errorLocation.col = 0; + //errorLocation.last = 0; + } + + /** Write the document to standard out using formatted printing ("pretty print"). */ + void Print() const { Print( stdout, 0 ); } + + /* Write the document to a string using formatted printing ("pretty print"). This + will allocate a character array (new char[]) and return it as a pointer. The + calling code pust call delete[] on the return char* to avoid a memory leak. + */ + //char* PrintToMemory() const; + + /// Print this Document to a FILE stream. + virtual void Print( FILE* cfile, int depth = 0 ) const; + // [internal use] + void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + + virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + // [internal use] + virtual TiXmlNode* Clone() const; + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + void CopyTo( TiXmlDocument* target ) const; + + bool error; + int errorId; + TIXML_STRING errorDesc; + int tabsize; + TiXmlCursor errorLocation; + bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. +}; + + +/** + A TiXmlHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + TiXmlElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity + of such code. A TiXmlHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + TiXmlHandle docHandle( &document ); + TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + TiXmlHandle handleCopy = handle; + @endverbatim + + What they should not be used for is iteration: + + @verbatim + int i=0; + while ( true ) + { + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); + if ( !child ) + break; + // do something + ++i; + } + @endverbatim + + It seems reasonable, but it is in fact two embedded while loops. The Child method is + a linear walk to find the element, so this code would iterate much more than it needs + to. Instead, prefer: + + @verbatim + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); + + for( child; child; child=child->NextSiblingElement() ) + { + // do something + } + @endverbatim +*/ +class TiXmlHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } + /// Copy constructor + TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { if ( &ref != this ) this->node = ref.node; return *this; } + + /// Return a handle to the first child node. + TiXmlHandle FirstChild() const; + /// Return a handle to the first child node with the given name. + TiXmlHandle FirstChild( const char * value ) const; + /// Return a handle to the first child element. + TiXmlHandle FirstChildElement() const; + /// Return a handle to the first child element with the given name. + TiXmlHandle FirstChildElement( const char * value ) const; + + /** Return a handle to the "index" child with the given name. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( const char* value, int index ) const; + /** Return a handle to the "index" child. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( int index ) const; + /** Return a handle to the "index" child element with the given name. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( const char* value, int index ) const; + /** Return a handle to the "index" child element. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( int index ) const; + + #ifdef TIXML_USE_STL + TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } + TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } + + TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } + TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } + #endif + + /** Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* ToNode() const { return node; } + /** Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } + /** Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } + /** Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } + + /** @deprecated use ToNode. + Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* Node() const { return ToNode(); } + /** @deprecated use ToElement. + Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* Element() const { return ToElement(); } + /** @deprecated use ToText() + Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* Text() const { return ToText(); } + /** @deprecated use ToUnknown() + Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* Unknown() const { return ToUnknown(); } + +private: + TiXmlNode* node; +}; + + +/** Print to memory functionality. The TiXmlPrinter is useful when you need to: + + -# Print to memory (especially in non-STL mode) + -# Control formatting (line endings, etc.) + + When constructed, the TiXmlPrinter is in its default "pretty printing" mode. + Before calling Accept() you can call methods to control the printing + of the XML document. After TiXmlNode::Accept() is called, the printed document can + be accessed via the CStr(), Str(), and Size() methods. + + TiXmlPrinter uses the Visitor API. + @verbatim + TiXmlPrinter printer; + printer.SetIndent( "\t" ); + + doc.Accept( &printer ); + fprintf( stdout, "%s", printer.CStr() ); + @endverbatim +*/ +class TiXmlPrinter : public TiXmlVisitor +{ +public: + TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), + buffer(), indent( " " ), lineBreak( "\n" ) {} + + virtual bool VisitEnter( const TiXmlDocument& doc ); + virtual bool VisitExit( const TiXmlDocument& doc ); + + virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); + virtual bool VisitExit( const TiXmlElement& element ); + + virtual bool Visit( const TiXmlDeclaration& declaration ); + virtual bool Visit( const TiXmlText& text ); + virtual bool Visit( const TiXmlComment& comment ); + virtual bool Visit( const TiXmlUnknown& unknown ); + + /** Set the indent characters for printing. By default 4 spaces + but tab (\t) is also useful, or null/empty string for no indentation. + */ + void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } + /// Query the indention string. + const char* Indent() { return indent.c_str(); } + /** Set the line breaking string. By default set to newline (\n). + Some operating systems prefer other characters, or can be + set to the null/empty string for no indenation. + */ + void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } + /// Query the current line breaking string. + const char* LineBreak() { return lineBreak.c_str(); } + + /** Switch over to "stream printing" which is the most dense formatting without + linebreaks. Common when the XML is needed for network transmission. + */ + void SetStreamPrinting() { indent = ""; + lineBreak = ""; + } + /// Return the result. + const char* CStr() { return buffer.c_str(); } + /// Return the length of the result string. + size_t Size() { return buffer.size(); } + + #ifdef TIXML_USE_STL + /// Return the result. + const std::string& Str() { return buffer; } + #endif + +private: + void DoIndent() { + for( int i=0; i +#include + +#include "tinyxml.h" + +//#define DEBUG_PARSER +#if defined( DEBUG_PARSER ) +# if defined( DEBUG ) && defined( _MSC_VER ) +# include +# define TIXML_LOG OutputDebugString +# else +# define TIXML_LOG printf +# endif +#endif + +// Note tha "PutString" hardcodes the same list. This +// is less flexible than it appears. Changing the entries +// or order will break putstring. +TiXmlBase::Entity TiXmlBase::entity[ TiXmlBase::NUM_ENTITY ] = +{ + { "&", 5, '&' }, + { "<", 4, '<' }, + { ">", 4, '>' }, + { """, 6, '\"' }, + { "'", 6, '\'' } +}; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// Including the basic of this table, which determines the #bytes in the +// sequence from the lead byte. 1 placed for invalid sequences -- +// although the result will be junk, pass it through as much as possible. +// Beware of the non-characters in UTF-8: +// ef bb bf (Microsoft "lead bytes") +// ef bf be +// ef bf bf + +const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +const int TiXmlBase::utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte + 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; + + +void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) + *length = 1; + else if ( input < 0x800 ) + *length = 2; + else if ( input < 0x10000 ) + *length = 3; + else if ( input < 0x200000 ) + *length = 4; + else + { *length = 0; return; } // This code won't covert this correctly anyway. + + output += *length; + + // Scary scary fall throughs. + switch (*length) + { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + } +} + + +/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalpha( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalpha( anyByte ); +// } +} + + +/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalnum( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalnum( anyByte ); +// } +} + + +class TiXmlParsingData +{ + friend class TiXmlDocument; + public: + void Stamp( const char* now, TiXmlEncoding encoding ); + + const TiXmlCursor& Cursor() const { return cursor; } + + private: + // Only used by the document! + TiXmlParsingData( const char* start, int _tabsize, int row, int col ) + { + assert( start ); + stamp = start; + tabsize = _tabsize; + cursor.row = row; + cursor.col = col; + } + + TiXmlCursor cursor; + const char* stamp; + int tabsize; +}; + + +void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) +{ + assert( now ); + + // Do nothing if the tabsize is 0. + if ( tabsize < 1 ) + { + return; + } + + // Get the current row, column. + int row = cursor.row; + int col = cursor.col; + const char* p = stamp; + assert( p ); + + while ( p < now ) + { + // Treat p as unsigned, so we have a happy compiler. + const unsigned char* pU = (const unsigned char*)p; + + // Code contributed by Fletcher Dunn: (modified by lee) + switch (*pU) { + case 0: + // We *should* never get here, but in case we do, don't + // advance past the terminating null character, ever + return; + + case '\r': + // bump down to the next line + ++row; + col = 0; + // Eat the character + ++p; + + // Check for \r\n sequence, and treat this as a single character + if (*p == '\n') { + ++p; + } + break; + + case '\n': + // bump down to the next line + ++row; + col = 0; + + // Eat the character + ++p; + + // Check for \n\r sequence, and treat this as a single + // character. (Yes, this bizarre thing does occur still + // on some arcane platforms...) + if (*p == '\r') { + ++p; + } + break; + + case '\t': + // Eat the character + ++p; + + // Skip to next tab stop + col = (col / tabsize + 1) * tabsize; + break; + + case TIXML_UTF_LEAD_0: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( *(p+1) && *(p+2) ) + { + // In these cases, don't advance the column. These are + // 0-width spaces. + if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) + p += 3; + else + { p +=3; ++col; } // A normal character. + } + } + else + { + ++p; + ++col; + } + break; + + default: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // Eat the 1 to 4 byte utf8 character. + int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)]; + if ( step == 0 ) + step = 1; // Error case from bad encoding, but handle gracefully. + p += step; + + // Just advance one column, of course. + ++col; + } + else + { + ++p; + ++col; + } + break; + } + } + cursor.row = row; + cursor.col = col; + assert( cursor.row >= -1 ); + assert( cursor.col >= -1 ); + stamp = p; + assert( stamp ); +} + + +const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) +{ + if ( !p || !*p ) + { + return 0; + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + while ( *p ) + { + const unsigned char* pU = (const unsigned char*)p; + + // Skip the stupid Microsoft UTF-8 Byte order marks + if ( *(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==TIXML_UTF_LEAD_1 + && *(pU+2)==TIXML_UTF_LEAD_2 ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbeU ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbfU ) + { + p += 3; + continue; + } + + if ( IsWhiteSpace( *p ) ) // Still using old rules for white space. + ++p; + else + break; + } + } + else + { + while ( *p && IsWhiteSpace( *p ) ) + ++p; + } + + return p; +} + +#ifdef TIXML_USE_STL +/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ) +{ + for( ;; ) + { + if ( !in->good() ) return false; + + int c = in->peek(); + // At this scope, we can't get to a document. So fail silently. + if ( !IsWhiteSpace( c ) || c <= 0 ) + return true; + + *tag += (char) in->get(); + } +} + +/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag ) +{ + //assert( character > 0 && character < 128 ); // else it won't work in utf-8 + while ( in->good() ) + { + int c = in->peek(); + if ( c == character ) + return true; + if ( c <= 0 ) // Silent failure: can't get document at this scope + return false; + + in->get(); + *tag += (char) c; + } + return false; +} +#endif + +// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The +// "assign" optimization removes over 10% of the execution time. +// +const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) +{ + // Oddly, not supported on some comilers, + //name->clear(); + // So use this: + *name = ""; + assert( p ); + + // Names start with letters or underscores. + // Of course, in unicode, tinyxml has no idea what a letter *is*. The + // algorithm is generous. + // + // After that, they can be letters, underscores, numbers, + // hyphens, or colons. (Colons are valid ony for namespaces, + // but tinyxml can't tell namespaces from names.) + if ( p && *p + && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) + { + const char* start = p; + while( p && *p + && ( IsAlphaNum( (unsigned char ) *p, encoding ) + || *p == '_' + || *p == '-' + || *p == '.' + || *p == ':' ) ) + { + //(*name) += *p; // expensive + ++p; + } + if ( p-start > 0 ) { + name->assign( start, p-start ); + } + return p; + } + return 0; +} + +const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) +{ + // Presume an entity, and pull it out. + TIXML_STRING ent; + int i; + *length = 0; + + if ( *(p+1) && *(p+1) == '#' && *(p+2) ) + { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) + { + // Hexadecimal. + if ( !*(p+3) ) return 0; + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != 'x' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else if ( *q >= 'a' && *q <= 'f' ) + ucs += mult * (*q - 'a' + 10); + else if ( *q >= 'A' && *q <= 'F' ) + ucs += mult * (*q - 'A' + 10 ); + else + return 0; + mult *= 16; + --q; + } + } + else + { + // Decimal. + if ( !*(p+2) ) return 0; + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != '#' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else + return 0; + mult *= 10; + --q; + } + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + } + else + { + *value = (char)ucs; + *length = 1; + } + return p + delta + 1; + } + + // Now try to match it. + for( i=0; iappend( cArr, len ); + } + } + else + { + bool whitespace = false; + + // Remove leading white space: + p = SkipWhiteSpace( p, encoding ); + while ( p && *p + && !StringEqual( p, endTag, caseInsensitive, encoding ) ) + { + if ( *p == '\r' || *p == '\n' ) + { + whitespace = true; + ++p; + } + else if ( IsWhiteSpace( *p ) ) + { + whitespace = true; + ++p; + } + else + { + // If we've found whitespace, add it before the + // new character. Any whitespace just becomes a space. + if ( whitespace ) + { + (*text) += ' '; + whitespace = false; + } + int len; + char cArr[4] = { 0, 0, 0, 0 }; + p = GetChar( p, cArr, &len, encoding ); + if ( len == 1 ) + (*text) += cArr[0]; // more efficient + else + text->append( cArr, len ); + } + } + } + if ( p && *p ) + p += strlen( endTag ); + return ( p && *p ) ? p : 0; +} + +#ifdef TIXML_USE_STL + +void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + // The basic issue with a document is that we don't know what we're + // streaming. Read something presumed to be a tag (and hope), then + // identify it, and call the appropriate stream method on the tag. + // + // This "pre-streaming" will never read the closing ">" so the + // sub-tag can orient itself. + + if ( !StreamTo( in, '<', tag ) ) + { + SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + while ( in->good() ) + { + int tagIndex = (int) tag->length(); + while ( in->good() && in->peek() != '>' ) + { + int c = in->get(); + if ( c <= 0 ) + { + SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + break; + } + (*tag) += (char) c; + } + + if ( in->good() ) + { + // We now have something we presume to be a node of + // some sort. Identify it, and call the node to + // continue streaming. + TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); + + if ( node ) + { + node->StreamIn( in, tag ); + bool isElement = node->ToElement() != 0; + delete node; + node = 0; + + // If this is the root element, we're done. Parsing will be + // done by the >> operator. + if ( isElement ) + { + return; + } + } + else + { + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + } + } + // We should have returned sooner. + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); +} + +#endif + +const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) +{ + ClearError(); + + // Parse away, at the document level. Since a document + // contains nothing but other tags, most of what happens + // here is skipping white space. + if ( !p || !*p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + // Note that, for a document, this needs to come + // before the while space skip, so that parsing + // starts from the pointer we are given. + location.Clear(); + if ( prevData ) + { + location.row = prevData->cursor.row; + location.col = prevData->cursor.col; + } + else + { + location.row = 0; + location.col = 0; + } + TiXmlParsingData data( p, TabSize(), location.row, location.col ); + location = data.Cursor(); + + if ( encoding == TIXML_ENCODING_UNKNOWN ) + { + // Check for the Microsoft UTF-8 lead bytes. + const unsigned char* pU = (const unsigned char*)p; + if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0 + && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1 + && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 ) + { + encoding = TIXML_ENCODING_UTF8; + useMicrosoftBOM = true; + } + } + + p = SkipWhiteSpace( p, encoding ); + if ( !p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + while ( p && *p ) + { + TiXmlNode* node = Identify( p, encoding ); + if ( node ) + { + p = node->Parse( p, &data, encoding ); + LinkEndChild( node ); + } + else + { + break; + } + + // Did we get encoding info? + if ( encoding == TIXML_ENCODING_UNKNOWN + && node->ToDeclaration() ) + { + TiXmlDeclaration* dec = node->ToDeclaration(); + const char* enc = dec->Encoding(); + assert( enc ); + + if ( *enc == 0 ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice + else + encoding = TIXML_ENCODING_LEGACY; + } + + p = SkipWhiteSpace( p, encoding ); + } + + // Was this empty? + if ( !firstChild ) { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding ); + return 0; + } + + // All is well. + return p; +} + +void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + // The first error in a chain is more accurate - don't set again! + if ( error ) + return; + + assert( err > 0 && err < TIXML_ERROR_STRING_COUNT ); + error = true; + errorId = err; + errorDesc = errorString[ errorId ]; + + errorLocation.Clear(); + if ( pError && data ) + { + data->Stamp( pError, encoding ); + errorLocation = data->Cursor(); + } +} + + +TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) +{ + TiXmlNode* returnNode = 0; + + p = SkipWhiteSpace( p, encoding ); + if( !p || !*p || *p != '<' ) + { + return 0; + } + + p = SkipWhiteSpace( p, encoding ); + + if ( !p || !*p ) + { + return 0; + } + + // What is this thing? + // - Elements start with a letter or underscore, but xml is reserved. + // - Comments: "; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // [ 1475201 ] TinyXML parses entities in comments + // Oops - ReadText doesn't work, because we don't want to parse the entities. + // p = ReadText( p, &value, false, endTag, false, encoding ); + // + // from the XML spec: + /* + [Definition: Comments may appear anywhere in a document outside other markup; in addition, + they may appear within the document type declaration at places allowed by the grammar. + They are not part of the document's character data; an XML processor MAY, but need not, + make it possible for an application to retrieve the text of comments. For compatibility, + the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity + references MUST NOT be recognized within comments. + + An example of a comment: + + + */ + + value = ""; + // Keep all the white space. + while ( p && *p && !StringEqual( p, endTag, false, encoding ) ) + { + value.append( p, 1 ); + ++p; + } + if ( p && *p ) + p += strlen( endTag ); + + return p; +} + + +const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) return 0; + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + // Read the name, the '=' and the value. + const char* pErr = p; + p = ReadName( p, &name, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); + return 0; + } + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p || *p != '=' ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + ++p; // skip '=' + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + const char* end; + const char SINGLE_QUOTE = '\''; + const char DOUBLE_QUOTE = '\"'; + + if ( *p == SINGLE_QUOTE ) + { + ++p; + end = "\'"; // single quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else if ( *p == DOUBLE_QUOTE ) + { + ++p; + end = "\""; // double quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else + { + // All attribute values should be in single or double quotes. + // But this is such a common error that the parser will try + // its best, even without them. + value = ""; + while ( p && *p // existence + && !IsWhiteSpace( *p ) // whitespace + && *p != '/' && *p != '>' ) // tag end + { + if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { + // [ 1451649 ] Attribute values with trailing quotes not handled correctly + // We did not have an opening quote but seem to have a + // closing one. Give up and throw an error. + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + value += *p; + ++p; + } + } + return p; +} + +#ifdef TIXML_USE_STL +void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->peek(); + if ( !cdata && (c == '<' ) ) + { + return; + } + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + (*tag) += (char) c; + in->get(); // "commits" the peek made above + + if ( cdata && c == '>' && tag->size() >= 3 ) { + size_t len = tag->size(); + if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) { + // terminator of cdata. + return; + } + } + } +} +#endif + +const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + value = ""; + TiXmlDocument* document = GetDocument(); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + + const char* const startTag = ""; + + if ( cdata || StringEqual( p, startTag, false, encoding ) ) + { + cdata = true; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + if ( document ) + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // Keep all the white space, ignore the encoding, etc. + while ( p && *p + && !StringEqual( p, endTag, false, encoding ) + ) + { + value += *p; + ++p; + } + + TIXML_STRING dummy; + p = ReadText( p, &dummy, false, endTag, false, encoding ); + return p; + } + else + { + bool ignoreWhite = true; + + const char* end = "<"; + p = ReadText( p, &value, ignoreWhite, end, false, encoding ); + if ( p && *p ) + return p-1; // don't truncate the '<' + return 0; + } +} + +#ifdef TIXML_USE_STL +void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c; + + if ( c == '>' ) + { + // All is well. + return; + } + } +} +#endif + +const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) +{ + p = SkipWhiteSpace( p, _encoding ); + // Find the beginning, find the end, and look for + // the stuff in-between. + TiXmlDocument* document = GetDocument(); + if ( !p || !*p || !StringEqual( p, "SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); + return 0; + } + if ( data ) + { + data->Stamp( p, _encoding ); + location = data->Cursor(); + } + p += 5; + + version = ""; + encoding = ""; + standalone = ""; + + while ( p && *p ) + { + if ( *p == '>' ) + { + ++p; + return p; + } + + p = SkipWhiteSpace( p, _encoding ); + if ( StringEqual( p, "version", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + version = attrib.Value(); + } + else if ( StringEqual( p, "encoding", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + encoding = attrib.Value(); + } + else if ( StringEqual( p, "standalone", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + standalone = attrib.Value(); + } + else + { + // Read over whatever it is. + while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) + ++p; + } + } + return 0; +} + +bool TiXmlText::Blank() const +{ + for ( unsigned i=0; i + + The world has many languages + Мир имеет много Ñзыков + el mundo tiene muchos idiomas + 世界有很多语言 + <РуÑÑкий название="name" ценноÑÑ‚ÑŒ="value"><имеет> + <汉语 åå­—="name" 价值="value">世界有很多语言 + "Mëtæl!" + <ä>Umlaut Element + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/utf8testverify.xml b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/utf8testverify.xml new file mode 100644 index 0000000..91a319d --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/tinyxml/utf8testverify.xml @@ -0,0 +1,11 @@ + + + The world has many languages + Мир имеет много Ñзыков + el mundo tiene muchos idiomas + 世界有很多语言 + <РуÑÑкий название="name" ценноÑÑ‚ÑŒ="value"><имеет> + <汉语 åå­—="name" 价值="value">世界有很多语言 + "Mëtæl!" + <ä>Umlaut Element + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/lexical_cast.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/lexical_cast.h new file mode 100644 index 0000000..4086469 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/lexical_cast.h @@ -0,0 +1,27 @@ +#ifndef BOOST_REPLACEMENT_LEXICAL_CAST_H +#define BOOST_REPLACEMENT_LEXICAL_CAST_H + +#include + +namespace boost +{ + + template T lexical_cast(const char* txt) + { + double result = atof(txt); + return result; + }; + + struct bad_lexical_cast + { + const char* what() + { + return ("bad lexical cast\n"); + } + + }; + +} //namespace boost + +#endif + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.cpp new file mode 100644 index 0000000..ed38915 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.cpp @@ -0,0 +1,28 @@ +#include "printf_console.h" +#include + +#include + + +void logError(const char* msg, const char* arg0, const char* arg1, const char* arg2) +{ + LOG << msg << " " << arg0 << " " << arg1 << " " << arg2 << std::endl; +} + +void logDebug(const char* msg, float v0, float v1) +{ + LOG << msg << " " << v0 << " " << v1 << std::endl; +}; +void logDebug(const char* msg, const char* msg1, const char* arg1) +{ + LOG << msg << " " << msg1 << " " << arg1 << std::endl; +} + +void logInform(const char* msg, const char* arg0) +{ + LOG << msg << " " << arg0 << std::endl; +} +void logWarn(const char* msg,int id, const char* arg0) +{ + LOG << msg << " " << id << " " << arg0 << std::endl; +} diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.h new file mode 100644 index 0000000..247aab2 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/printf_console.h @@ -0,0 +1,13 @@ +#ifndef PRINTF_CONSOLE_H +#define PRINTF_CONSOLE_H + + +void logError(const char* msg="", const char* arg0="", const char* arg1="", const char* arg2=""); +void logDebug(const char* msg, float v0, float v1); +void logDebug(const char* msg, const char* msg1="", const char* arg1=""); +void logInform(const char* msg, const char* arg0=""); +void logWarn(const char* msg,int id, const char* arg0=""); + +#endif + + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/shared_ptr.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/shared_ptr.h new file mode 100644 index 0000000..5d29732 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/shared_ptr.h @@ -0,0 +1,210 @@ +/* +Bullet Continuous Collision Detection and Physics Library Maya Plugin +Copyright (c) 2008 Walt Disney Studios + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising +from the use of this software. +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it freely, +subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must +not be misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +Written by: Nicola Candussi + +Modified by Francisco Gochez +Dec 2011 - Added deferencing operator +*/ + +//my_shared_ptr + +#ifndef DYN_SHARED_PTR_H +#define DYN_SHARED_PTR_H + +#define DYN_SHARED_PTR_THREAD_SAFE + + +#include + +#ifdef _WIN32 +#include + +class my_shared_count { +public: + my_shared_count(): m_count(1) { } + ~my_shared_count() { } + + long increment() + { +#ifdef DYN_SHARED_PTR_THREAD_SAFE + return InterlockedIncrement(&m_count); +#else + return ++m_count; +#endif + } + + long decrement() { +#ifdef DYN_SHARED_PTR_THREAD_SAFE + return InterlockedDecrement(&m_count); +#else + return ++m_count; +#endif + } + + long use_count() { return m_count; } + +private: + long m_count; +}; +#else //ifdef WIN32 + + +#include + +class my_shared_count { +public: + my_shared_count(): m_count(1) { +#ifdef DYN_SHARED_PTR_THREAD_SAFE + pthread_mutex_init(&m_mutex, 0); +#endif + } + ~my_shared_count() { +#ifdef DYN_SHARED_PTR_THREAD_SAFE + pthread_mutex_destroy(&m_mutex); +#endif + } + + long increment() + { +#ifdef DYN_SHARED_PTR_THREAD_SAFE + pthread_mutex_lock(&m_mutex); +#endif + long c = ++m_count; +#ifdef DYN_SHARED_PTR_THREAD_SAFE + pthread_mutex_unlock(&m_mutex); +#endif + return c; + } + + long decrement() { +#ifdef DYN_SHARED_PTR_THREAD_SAFE + pthread_mutex_lock(&m_mutex); +#endif + long c = --m_count; +#ifdef DYN_SHARED_PTR_THREAD_SAFE + pthread_mutex_unlock(&m_mutex); +#endif + return c; + } + + long use_count() { return m_count; } + +private: + long m_count; + mutable pthread_mutex_t m_mutex; +}; + +#endif + + +template +class my_shared_ptr +{ +public: + my_shared_ptr(): m_ptr(NULL), m_count(NULL) { } + my_shared_ptr(my_shared_ptr const& other): + m_ptr(other.m_ptr), + m_count(other.m_count) + { + if(other.m_count != NULL) other.m_count->increment(); + } + + template + my_shared_ptr(my_shared_ptr const& other): + m_ptr(other.m_ptr), + m_count(other.m_count) + { + if(other.m_count != NULL) other.m_count->increment(); + } + + my_shared_ptr(T const* other): m_ptr(const_cast(other)), m_count(NULL) + { + if(other != NULL) m_count = new my_shared_count; + } + + ~my_shared_ptr() + { + giveup_ownership(); + } + + void reset(T const* other) + { + if(m_ptr == other) return; + giveup_ownership(); + m_ptr = const_cast(other); + if(other != NULL) m_count = new my_shared_count; + else m_count = NULL; + } + + T* get() { return m_ptr; } + T const* get() const { return m_ptr; } + T* operator->() { return m_ptr; } + T const* operator->() const { return m_ptr; } + operator bool() const { return m_ptr != NULL; } + T& operator*() const + { + assert(m_ptr != 0); + return *m_ptr; + } + + bool operator<(my_shared_ptr const& rhs) const { return m_ptr < rhs.m_ptr; } + + my_shared_ptr& operator=(my_shared_ptr const& other) { + if(m_ptr == other.m_ptr) return *this; + giveup_ownership(); + m_ptr = other.m_ptr; + m_count = other.m_count; + if(other.m_count != NULL) m_count->increment(); + return *this; + } + + template + my_shared_ptr& operator=(my_shared_ptr& other) { + if(m_ptr == other.m_ptr) return *this; + giveup_ownership(); + m_ptr = other.m_ptr; + m_count = other.m_count; + if(other.m_count != NULL) m_count->increment(); + return *this; + } + +protected: + + template friend class my_shared_ptr; + void giveup_ownership() + { + if(m_count != NULL) { + if( m_count->decrement() == 0) { + delete m_ptr; + m_ptr = NULL; + delete m_count; + m_count = NULL; + } + } + } + +protected: + T *m_ptr; + my_shared_count *m_count; + +}; + + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/string_split.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/string_split.cpp new file mode 100644 index 0000000..6c8a294 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/boost_replacement/string_split.cpp @@ -0,0 +1,253 @@ + + +#include +//#include +#include +#include +#include + +#include "string_split.h" + +namespace boost +{ + void split( std::vector&pieces, const std::string& vector_str, std::vector separators) + { + assert(separators.size()==1); + if (separators.size()==1) + { + char** strArray = str_split(vector_str.c_str(),separators[0].c_str()); + int numSubStr = str_array_len(strArray); + for (int i=0;i is_any_of(const char* seps) + { + std::vector strArray; + + int numSeps = strlen(seps); + for (int i=0;i +#include +#include + +namespace boost +{ + void split( std::vector&pieces, const std::string& vector_str, std::vector separators); + std::vector is_any_of(const char* seps); +}; + +///The string split C code is by Lars Wirzenius +///See http://stackoverflow.com/questions/2531605/how-to-split-a-string-with-a-delimiter-larger-than-one-single-char + + +/* Split a string into substrings. Return dynamic array of dynamically + allocated substrings, or NULL if there was an error. Caller is + expected to free the memory, for example with str_array_free. */ +char** str_split(const char* input, const char* sep); + +/* Free a dynamic array of dynamic strings. */ +void str_array_free(char** array); + +/* Return length of a NULL-delimited array of strings. */ +size_t str_array_len(char** array); + +#endif //STRING_SPLIT_H + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/LICENSE b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/LICENSE new file mode 100644 index 0000000..e80920e --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/LICENSE @@ -0,0 +1,15 @@ +Software License Agreement (Apache License) + +Copyright 2011 John Hsu + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/README.txt b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/README.txt new file mode 100644 index 0000000..4e3bff6 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/README.txt @@ -0,0 +1,7 @@ +The URDF (U-Robot Description Format) library + provides core data structures and a simple XML parsers + for populating the class data structures from an URDF file. + +For now, the details of the URDF specifications reside on + http://ros.org/wiki/urdf + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h new file mode 100644 index 0000000..336af0f --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h @@ -0,0 +1,63 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + +#ifndef URDF_PARSER_URDF_PARSER_H +#define URDF_PARSER_URDF_PARSER_H + +#include + +#include +#include "tinyxml/tinyxml.h" + +//#include + +#ifndef M_PI +#define M_PI 3.1415925438 +#endif //M_PI + + +#include + + + + +namespace urdf{ + + my_shared_ptr parseURDF(const std::string &xml_string); + +} + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/check_urdf.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/check_urdf.cpp new file mode 100644 index 0000000..67e9eb1 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/check_urdf.cpp @@ -0,0 +1,137 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + +#include "urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h" +#include +#include + +using namespace urdf; + +void printTree(my_shared_ptr link,int level = 0) +{ + level+=2; + int count = 0; + for (std::vector >::const_iterator child = link->child_links.begin(); child != link->child_links.end(); child++) + { + if (*child) + { + for(int j=0;jname << std::endl; + // first grandchild + printTree(*child,level); + } + else + { + for(int j=0;jname << " has a null child!" << *child << std::endl; + } + } + +} + + +#define MSTRINGIFY(A) #A + + +const char* urdf_char = MSTRINGIFY( + + + + + + + + + + + + + + + + + + + + +); + + +int main(int argc, char** argv) +{ + + std::string xml_string; + + if (argc < 2){ + std::cerr << "No URDF file name provided, using a dummy test URDF" << std::endl; + + xml_string = std::string(urdf_char); + + } else + { + + + std::fstream xml_file(argv[1], std::fstream::in); + while ( xml_file.good() ) + { + std::string line; + std::getline( xml_file, line); + xml_string += (line + "\n"); + } + xml_file.close(); + } + + my_shared_ptr robot = parseURDF(xml_string); + if (!robot){ + std::cerr << "ERROR: Model Parsing the xml failed" << std::endl; + return -1; + } + std::cout << "robot name is: " << robot->getName() << std::endl; + + // get info from parser + std::cout << "---------- Successfully Parsed XML ---------------" << std::endl; + // get root link + my_shared_ptr root_link=robot->getRoot(); + if (!root_link) return -1; + + std::cout << "root Link: " << root_link->name << " has " << root_link->child_links.size() << " child(ren)" << std::endl; + + + // print entire tree + printTree(root_link); + return 0; +} + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/joint.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/joint.cpp new file mode 100644 index 0000000..bde0c5f --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/joint.cpp @@ -0,0 +1,579 @@ +/********************************************************************* +* Software Ligcense Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + +#include +#include +#ifdef URDF_USE_BOOST +#include +#else +#include +#endif +#include + +#ifdef URDF_USE_CONSOLE_BRIDGE + #include +#else + #include "urdf/boost_replacement/printf_console.h" +#endif + +#include +#include + +namespace urdf{ + +bool parsePose(Pose &pose, TiXmlElement* xml); + +bool parseJointDynamics(JointDynamics &jd, TiXmlElement* config) +{ + jd.clear(); + + // Get joint damping + const char* damping_str = config->Attribute("damping"); + if (damping_str == NULL){ + logDebug("urdfdom.joint_dynamics: no damping, defaults to 0"); + jd.damping = 0; + } + else + { + try + { + jd.damping = boost::lexical_cast(damping_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("damping value (%s) is not a float: %s",damping_str, e.what()); + return false; + } + } + + // Get joint friction + const char* friction_str = config->Attribute("friction"); + if (friction_str == NULL){ + logDebug("urdfdom.joint_dynamics: no friction, defaults to 0"); + jd.friction = 0; + } + else + { + try + { + jd.friction = boost::lexical_cast(friction_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("friction value (%s) is not a float: %s",friction_str, e.what()); + return false; + } + } + + if (damping_str == NULL && friction_str == NULL) + { + logError("joint dynamics element specified with no damping and no friction"); + return false; + } + else{ + logDebug("urdfdom.joint_dynamics: damping %f and friction %f", jd.damping, jd.friction); + return true; + } +} + +bool parseJointLimits(JointLimits &jl, TiXmlElement* config) +{ + jl.clear(); + + // Get lower joint limit + const char* lower_str = config->Attribute("lower"); + if (lower_str == NULL){ + logDebug("urdfdom.joint_limit: no lower, defaults to 0"); + jl.lower = 0; + } + else + { + try + { + jl.lower = boost::lexical_cast(lower_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("lower value (%s) is not a float: %s", lower_str, e.what()); + return false; + } + } + + // Get upper joint limit + const char* upper_str = config->Attribute("upper"); + if (upper_str == NULL){ + logDebug("urdfdom.joint_limit: no upper, , defaults to 0"); + jl.upper = 0; + } + else + { + try + { + jl.upper = boost::lexical_cast(upper_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("upper value (%s) is not a float: %s",upper_str, e.what()); + return false; + } + } + + // Get joint effort limit + const char* effort_str = config->Attribute("effort"); + if (effort_str == NULL){ + logError("joint limit: no effort"); + return false; + } + else + { + try + { + jl.effort = boost::lexical_cast(effort_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("effort value (%s) is not a float: %s",effort_str, e.what()); + return false; + } + } + + // Get joint velocity limit + const char* velocity_str = config->Attribute("velocity"); + if (velocity_str == NULL){ + logError("joint limit: no velocity"); + return false; + } + else + { + try + { + jl.velocity = boost::lexical_cast(velocity_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("velocity value (%s) is not a float: %s",velocity_str, e.what()); + return false; + } + } + + return true; +} + +bool parseJointSafety(JointSafety &js, TiXmlElement* config) +{ + js.clear(); + + // Get soft_lower_limit joint limit + const char* soft_lower_limit_str = config->Attribute("soft_lower_limit"); + if (soft_lower_limit_str == NULL) + { + logDebug("urdfdom.joint_safety: no soft_lower_limit, using default value"); + js.soft_lower_limit = 0; + } + else + { + try + { + js.soft_lower_limit = boost::lexical_cast(soft_lower_limit_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("soft_lower_limit value (%s) is not a float: %s",soft_lower_limit_str, e.what()); + return false; + } + } + + // Get soft_upper_limit joint limit + const char* soft_upper_limit_str = config->Attribute("soft_upper_limit"); + if (soft_upper_limit_str == NULL) + { + logDebug("urdfdom.joint_safety: no soft_upper_limit, using default value"); + js.soft_upper_limit = 0; + } + else + { + try + { + js.soft_upper_limit = boost::lexical_cast(soft_upper_limit_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("soft_upper_limit value (%s) is not a float: %s",soft_upper_limit_str, e.what()); + return false; + } + } + + // Get k_position_ safety "position" gain - not exactly position gain + const char* k_position_str = config->Attribute("k_position"); + if (k_position_str == NULL) + { + logDebug("urdfdom.joint_safety: no k_position, using default value"); + js.k_position = 0; + } + else + { + try + { + js.k_position = boost::lexical_cast(k_position_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("k_position value (%s) is not a float: %s",k_position_str, e.what()); + return false; + } + } + // Get k_velocity_ safety velocity gain + const char* k_velocity_str = config->Attribute("k_velocity"); + if (k_velocity_str == NULL) + { + logError("joint safety: no k_velocity"); + return false; + } + else + { + try + { + js.k_velocity = boost::lexical_cast(k_velocity_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("k_velocity value (%s) is not a float: %s",k_velocity_str, e.what()); + return false; + } + } + + return true; +} + +bool parseJointCalibration(JointCalibration &jc, TiXmlElement* config) +{ + jc.clear(); + + // Get rising edge position + const char* rising_position_str = config->Attribute("rising"); + if (rising_position_str == NULL) + { + logDebug("urdfdom.joint_calibration: no rising, using default value"); + jc.rising.reset(0); + } + else + { + try + { + jc.rising.reset(new double(boost::lexical_cast(rising_position_str))); + } + catch (boost::bad_lexical_cast &e) + { + logError("risingvalue (%s) is not a float: %s",rising_position_str, e.what()); + return false; + } + } + + // Get falling edge position + const char* falling_position_str = config->Attribute("falling"); + if (falling_position_str == NULL) + { + logDebug("urdfdom.joint_calibration: no falling, using default value"); + jc.falling.reset(0); + } + else + { + try + { + jc.falling.reset(new double(boost::lexical_cast(falling_position_str))); + } + catch (boost::bad_lexical_cast &e) + { + logError("fallingvalue (%s) is not a float: %s",falling_position_str, e.what()); + return false; + } + } + + return true; +} + +bool parseJointMimic(JointMimic &jm, TiXmlElement* config) +{ + jm.clear(); + + // Get name of joint to mimic + const char* joint_name_str = config->Attribute("joint"); + + if (joint_name_str == NULL) + { + logError("joint mimic: no mimic joint specified"); + return false; + } + else + jm.joint_name = joint_name_str; + + // Get mimic multiplier + const char* multiplier_str = config->Attribute("multiplier"); + + if (multiplier_str == NULL) + { + logDebug("urdfdom.joint_mimic: no multiplier, using default value of 1"); + jm.multiplier = 1; + } + else + { + try + { + jm.multiplier = boost::lexical_cast(multiplier_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("multiplier value (%s) is not a float: %s",multiplier_str, e.what()); + return false; + } + } + + + // Get mimic offset + const char* offset_str = config->Attribute("offset"); + if (offset_str == NULL) + { + logDebug("urdfdom.joint_mimic: no offset, using default value of 0"); + jm.offset = 0; + } + else + { + try + { + jm.offset = boost::lexical_cast(offset_str); + } + catch (boost::bad_lexical_cast &e) + { + logError("offset value (%s) is not a float: %s",offset_str, e.what()); + return false; + } + } + + return true; +} + +bool parseJoint(Joint &joint, TiXmlElement* config) +{ + joint.clear(); + + // Get Joint Name + const char *name = config->Attribute("name"); + if (!name) + { + logError("unnamed joint found"); + return false; + } + joint.name = name; + + // Get transform from Parent Link to Joint Frame + TiXmlElement *origin_xml = config->FirstChildElement("origin"); + if (!origin_xml) + { + logDebug("urdfdom: Joint [%s] missing origin tag under parent describing transform from Parent Link to Joint Frame, (using Identity transform).", joint.name.c_str()); + joint.parent_to_joint_origin_transform.clear(); + } + else + { + if (!parsePose(joint.parent_to_joint_origin_transform, origin_xml)) + { + joint.parent_to_joint_origin_transform.clear(); + logError("Malformed parent origin element for joint [%s]", joint.name.c_str()); + return false; + } + } + + // Get Parent Link + TiXmlElement *parent_xml = config->FirstChildElement("parent"); + if (parent_xml) + { + const char *pname = parent_xml->Attribute("link"); + if (!pname) + { + logInform("no parent link name specified for Joint link [%s]. this might be the root?", joint.name.c_str()); + } + else + { + joint.parent_link_name = std::string(pname); + } + } + + // Get Child Link + TiXmlElement *child_xml = config->FirstChildElement("child"); + if (child_xml) + { + const char *pname = child_xml->Attribute("link"); + if (!pname) + { + logInform("no child link name specified for Joint link [%s].", joint.name.c_str()); + } + else + { + joint.child_link_name = std::string(pname); + } + } + + // Get Joint type + const char* type_char = config->Attribute("type"); + if (!type_char) + { + logError("joint [%s] has no type, check to see if it's a reference.", joint.name.c_str()); + return false; + } + + std::string type_str = type_char; + if (type_str == "planar") + joint.type = Joint::PLANAR; + else if (type_str == "floating") + joint.type = Joint::FLOATING; + else if (type_str == "revolute") + joint.type = Joint::REVOLUTE; + else if (type_str == "continuous") + joint.type = Joint::CONTINUOUS; + else if (type_str == "prismatic") + joint.type = Joint::PRISMATIC; + else if (type_str == "fixed") + joint.type = Joint::FIXED; + else + { + logError("Joint [%s] has no known type [%s]", joint.name.c_str(), type_str.c_str()); + return false; + } + + // Get Joint Axis + if (joint.type != Joint::FLOATING && joint.type != Joint::FIXED) + { + // axis + TiXmlElement *axis_xml = config->FirstChildElement("axis"); + if (!axis_xml){ + logDebug("urdfdom: no axis elemement for Joint link [%s], defaulting to (1,0,0) axis", joint.name.c_str()); + joint.axis = Vector3(1.0, 0.0, 0.0); + } + else{ + if (axis_xml->Attribute("xyz")){ + try { + joint.axis.init(axis_xml->Attribute("xyz")); + } + catch (ParseError &e) { + joint.axis.clear(); + logError("Malformed axis element for joint [%s]: %s", joint.name.c_str(), e.what()); + return false; + } + } + } + } + + // Get limit + TiXmlElement *limit_xml = config->FirstChildElement("limit"); + if (limit_xml) + { + joint.limits.reset(new JointLimits()); + if (!parseJointLimits(*joint.limits, limit_xml)) + { + logError("Could not parse limit element for joint [%s]", joint.name.c_str()); + joint.limits.reset(0); + return false; + } + } + else if (joint.type == Joint::REVOLUTE) + { + logError("Joint [%s] is of type REVOLUTE but it does not specify limits", joint.name.c_str()); + return false; + } + else if (joint.type == Joint::PRISMATIC) + { + logError("Joint [%s] is of type PRISMATIC without limits", joint.name.c_str()); + return false; + } + + // Get safety + TiXmlElement *safety_xml = config->FirstChildElement("safety_controller"); + if (safety_xml) + { + joint.safety.reset(new JointSafety()); + if (!parseJointSafety(*joint.safety, safety_xml)) + { + logError("Could not parse safety element for joint [%s]", joint.name.c_str()); + joint.safety.reset(0); + return false; + } + } + + // Get calibration + TiXmlElement *calibration_xml = config->FirstChildElement("calibration"); + if (calibration_xml) + { + joint.calibration.reset(new JointCalibration()); + if (!parseJointCalibration(*joint.calibration, calibration_xml)) + { + logError("Could not parse calibration element for joint [%s]", joint.name.c_str()); + joint.calibration.reset(0); + return false; + } + } + + // Get Joint Mimic + TiXmlElement *mimic_xml = config->FirstChildElement("mimic"); + if (mimic_xml) + { + joint.mimic.reset(new JointMimic()); + if (!parseJointMimic(*joint.mimic, mimic_xml)) + { + logError("Could not parse mimic element for joint [%s]", joint.name.c_str()); + joint.mimic.reset(0); + return false; + } + } + + // Get Dynamics + TiXmlElement *prop_xml = config->FirstChildElement("dynamics"); + if (prop_xml) + { + joint.dynamics.reset(new JointDynamics()); + if (!parseJointDynamics(*joint.dynamics, prop_xml)) + { + logError("Could not parse joint_dynamics element for joint [%s]", joint.name.c_str()); + joint.dynamics.reset(0); + return false; + } + } + + return true; +} + + + + +} diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/link.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/link.cpp new file mode 100644 index 0000000..a224146 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/link.cpp @@ -0,0 +1,505 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + + +#include +#include +//#include +//#include +#ifdef URDF_USE_BOOST +#include +#else +#include +#endif + +#include +#include +#ifdef URDF_USE_CONSOLE_BRIDGE +#include +#else +#include "urdf/boost_replacement/printf_console.h" +#endif + + + +namespace urdf{ + +bool parsePose(Pose &pose, TiXmlElement* xml); + +bool parseMaterial(Material &material, TiXmlElement *config, bool only_name_is_ok) +{ + bool has_rgb = false; + bool has_filename = false; + + material.clear(); + + if (!config->Attribute("name")) + { + logError("Material must contain a name attribute"); + return false; + } + + material.name = config->Attribute("name"); + + // texture + TiXmlElement *t = config->FirstChildElement("texture"); + if (t) + { + if (t->Attribute("filename")) + { + material.texture_filename = t->Attribute("filename"); + has_filename = true; + } + } + + // color + TiXmlElement *c = config->FirstChildElement("color"); + if (c) + { + if (c->Attribute("rgba")) { + + try { + material.color.init(c->Attribute("rgba")); + has_rgb = true; + } + catch (ParseError &e) { + material.color.clear(); + logError(std::string("Material [" + material.name + "] has malformed color rgba values: " + e.what()).c_str()); + } + } + } + + if (!has_rgb && !has_filename) { + if (!only_name_is_ok) // no need for an error if only name is ok + { + if (!has_rgb) logError(std::string("Material ["+material.name+"] color has no rgba").c_str()); + if (!has_filename) logError(std::string("Material ["+material.name+"] not defined in file").c_str()); + } + return false; + } + return true; +} + + +bool parseSphere(Sphere &s, TiXmlElement *c) +{ + s.clear(); + + s.type = Geometry::SPHERE; + if (!c->Attribute("radius")) + { + logError("Sphere shape must have a radius attribute"); + return false; + } + + try + { + s.radius = boost::lexical_cast(c->Attribute("radius")); + } + catch (boost::bad_lexical_cast &e) + { + // std::stringstream stm; + // stm << "radius [" << c->Attribute("radius") << "] is not a valid float: " << e.what(); + // logError(stm.str().c_str()); + logError("radius issue"); + return false; + } + + return true; +} + +bool parseBox(Box &b, TiXmlElement *c) +{ + b.clear(); + + b.type = Geometry::BOX; + if (!c->Attribute("size")) + { + logError("Box shape has no size attribute"); + return false; + } + try + { + b.dim.init(c->Attribute("size")); + } + catch (ParseError &e) + { + b.dim.clear(); + logError(e.what()); + return false; + } + return true; +} + +bool parseCylinder(Cylinder &y, TiXmlElement *c) +{ + y.clear(); + + y.type = Geometry::CYLINDER; + if (!c->Attribute("length") || + !c->Attribute("radius")) + { + logError("Cylinder shape must have both length and radius attributes"); + return false; + } + + try + { + y.length = boost::lexical_cast(c->Attribute("length")); + } + catch (boost::bad_lexical_cast &e) + { + // std::stringstream stm; + // stm << "length [" << c->Attribute("length") << "] is not a valid float"; + //logError(stm.str().c_str()); + logError("length"); + return false; + } + + try + { + y.radius = boost::lexical_cast(c->Attribute("radius")); + } + catch (boost::bad_lexical_cast &e) + { + // std::stringstream stm; + // stm << "radius [" << c->Attribute("radius") << "] is not a valid float"; + //logError(stm.str().c_str()); + logError("radius"); + return false; + } + return true; +} + + +bool parseMesh(Mesh &m, TiXmlElement *c) +{ + m.clear(); + + m.type = Geometry::MESH; + if (!c->Attribute("filename")) { + logError("Mesh must contain a filename attribute"); + return false; + } + + m.filename = c->Attribute("filename"); + + if (c->Attribute("scale")) { + try { + m.scale.init(c->Attribute("scale")); + } + catch (ParseError &e) { + m.scale.clear(); + logError("Mesh scale was specified, but could not be parsed: %s", e.what()); + return false; + } + } + else + { + m.scale.x = m.scale.y = m.scale.z = 1; + } + return true; +} + +my_shared_ptr parseGeometry(TiXmlElement *g) +{ + my_shared_ptr geom; + if (!g) return geom; + + TiXmlElement *shape = g->FirstChildElement(); + if (!shape) + { + logError("Geometry tag contains no child element."); + return geom; + } + + const std::string type_name = shape->ValueTStr().c_str(); + if (type_name == "sphere") + { + Sphere *s = new Sphere(); + geom.reset(s); + if (parseSphere(*s, shape)) + return geom; + } + else if (type_name == "box") + { + Box *b = new Box(); + geom.reset(b); + if (parseBox(*b, shape)) + return geom; + } + else if (type_name == "cylinder") + { + Cylinder *c = new Cylinder(); + geom.reset(c); + if (parseCylinder(*c, shape)) + return geom; + } + else if (type_name == "mesh") + { + Mesh *m = new Mesh(); + geom.reset(m); + if (parseMesh(*m, shape)) + return geom; + } + else + { + logError("Unknown geometry type '%s'", type_name.c_str()); + return geom; + } + + return my_shared_ptr(); +} + +bool parseInertial(Inertial &i, TiXmlElement *config) +{ + i.clear(); + + // Origin + TiXmlElement *o = config->FirstChildElement("origin"); + if (o) + { + if (!parsePose(i.origin, o)) + return false; + } + + TiXmlElement *mass_xml = config->FirstChildElement("mass"); + if (!mass_xml) + { + logError("Inertial element must have a mass element"); + return false; + } + if (!mass_xml->Attribute("value")) + { + logError("Inertial: mass element must have value attribute"); + return false; + } + + try + { + i.mass = boost::lexical_cast(mass_xml->Attribute("value")); + } + catch (boost::bad_lexical_cast &e) + { + // std::stringstream stm; + // stm << "Inertial: mass [" << mass_xml->Attribute("value") + // << "] is not a float"; + //logError(stm.str().c_str()); + logError("Inertial mass issue"); + return false; + } + + TiXmlElement *inertia_xml = config->FirstChildElement("inertia"); + if (!inertia_xml) + { + logError("Inertial element must have inertia element"); + return false; + } + if (!(inertia_xml->Attribute("ixx") && inertia_xml->Attribute("ixy") && inertia_xml->Attribute("ixz") && + inertia_xml->Attribute("iyy") && inertia_xml->Attribute("iyz") && + inertia_xml->Attribute("izz"))) + { + logError("Inertial: inertia element must have ixx,ixy,ixz,iyy,iyz,izz attributes"); + return false; + } + try + { + i.ixx = boost::lexical_cast(inertia_xml->Attribute("ixx")); + i.ixy = boost::lexical_cast(inertia_xml->Attribute("ixy")); + i.ixz = boost::lexical_cast(inertia_xml->Attribute("ixz")); + i.iyy = boost::lexical_cast(inertia_xml->Attribute("iyy")); + i.iyz = boost::lexical_cast(inertia_xml->Attribute("iyz")); + i.izz = boost::lexical_cast(inertia_xml->Attribute("izz")); + } + catch (boost::bad_lexical_cast &e) + { + /* std::stringstream stm; + stm << "Inertial: one of the inertia elements is not a valid double:" + << " ixx [" << inertia_xml->Attribute("ixx") << "]" + << " ixy [" << inertia_xml->Attribute("ixy") << "]" + << " ixz [" << inertia_xml->Attribute("ixz") << "]" + << " iyy [" << inertia_xml->Attribute("iyy") << "]" + << " iyz [" << inertia_xml->Attribute("iyz") << "]" + << " izz [" << inertia_xml->Attribute("izz") << "]"; + logError(stm.str().c_str()); + */ + logError("Inertia error"); + + return false; + } + return true; +} + +bool parseVisual(Visual &vis, TiXmlElement *config) +{ + vis.clear(); + + // Origin + TiXmlElement *o = config->FirstChildElement("origin"); + if (o) { + if (!parsePose(vis.origin, o)) + return false; + } + + // Geometry + TiXmlElement *geom = config->FirstChildElement("geometry"); + vis.geometry = parseGeometry(geom); + if (!vis.geometry) + return false; + + const char *name_char = config->Attribute("name"); + if (name_char) + vis.name = name_char; + + // Material + TiXmlElement *mat = config->FirstChildElement("material"); + if (mat) { + // get material name + if (!mat->Attribute("name")) { + logError("Visual material must contain a name attribute"); + return false; + } + vis.material_name = mat->Attribute("name"); + + // try to parse material element in place + vis.material.reset(new Material()); + if (!parseMaterial(*vis.material, mat, true)) + { + logDebug("urdfdom: material has only name, actual material definition may be in the model"); + } + } + + return true; +} + +bool parseCollision(Collision &col, TiXmlElement* config) +{ + col.clear(); + + // Origin + TiXmlElement *o = config->FirstChildElement("origin"); + if (o) { + if (!parsePose(col.origin, o)) + return false; + } + + // Geometry + TiXmlElement *geom = config->FirstChildElement("geometry"); + col.geometry = parseGeometry(geom); + if (!col.geometry) + return false; + + const char *name_char = config->Attribute("name"); + if (name_char) + col.name = name_char; + + return true; +} + +bool parseLink(Link &link, TiXmlElement* config) +{ + + link.clear(); + + const char *name_char = config->Attribute("name"); + if (!name_char) + { + logError("No name given for the link."); + return false; + } + link.name = std::string(name_char); + + // Inertial (optional) + TiXmlElement *i = config->FirstChildElement("inertial"); + if (i) + { + link.inertial.reset(new Inertial()); + if (!parseInertial(*link.inertial, i)) + { + logError("Could not parse inertial element for Link [%s]", link.name.c_str()); + return false; + } + } + + // Multiple Visuals (optional) + for (TiXmlElement* vis_xml = config->FirstChildElement("visual"); vis_xml; vis_xml = vis_xml->NextSiblingElement("visual")) + { + + my_shared_ptr vis; + vis.reset(new Visual()); + if (parseVisual(*vis, vis_xml)) + { + link.visual_array.push_back(vis); + } + else + { + vis.reset(0); + logError("Could not parse visual element for Link [%s]", link.name.c_str()); + return false; + } + } + + // Visual (optional) + // Assign the first visual to the .visual ptr, if it exists + if (!link.visual_array.empty()) + link.visual = link.visual_array[0]; + + // Multiple Collisions (optional) + for (TiXmlElement* col_xml = config->FirstChildElement("collision"); col_xml; col_xml = col_xml->NextSiblingElement("collision")) + { + my_shared_ptr col; + col.reset(new Collision()); + if (parseCollision(*col, col_xml)) + { + link.collision_array.push_back(col); + } + else + { + col.reset(0); + logError("Could not parse collision element for Link [%s]", link.name.c_str()); + return false; + } + } + + // Collision (optional) + // Assign the first collision to the .collision ptr, if it exists + if (!link.collision_array.empty()) + link.collision = link.collision_array[0]; + return true; + +} + +} diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/model.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/model.cpp new file mode 100644 index 0000000..e8562d0 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/model.cpp @@ -0,0 +1,240 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ +//#include +#include +#include "urdf/urdfdom/urdf_parser/include/urdf_parser/urdf_parser.h" +#ifdef URDF_USE_CONSOLE_BRIDGE + #include +#else + #include "urdf/boost_replacement/printf_console.h" +#endif + +namespace urdf{ + +bool parseMaterial(Material &material, TiXmlElement *config, bool only_name_is_ok); +bool parseLink(Link &link, TiXmlElement *config); +bool parseJoint(Joint &joint, TiXmlElement *config); + + +my_shared_ptr parseURDF(const std::string &xml_string) +{ + my_shared_ptr model(new ModelInterface); + model->clear(); + + TiXmlDocument xml_doc; + xml_doc.Parse(xml_string.c_str()); + if (xml_doc.Error()) + { + logError(xml_doc.ErrorDesc()); + xml_doc.ClearError(); + model.reset(0); + return model; + } + + TiXmlElement *robot_xml = xml_doc.FirstChildElement("robot"); + if (!robot_xml) + { + logError("Could not find the 'robot' element in the xml file"); + model.reset(0); + return model; + } + + // Get robot name + const char *name = robot_xml->Attribute("name"); + if (!name) + { + logError("No name given for the robot."); + model.reset(0); + return model; + } + model->name_ = std::string(name); + + // Get all Material elements + for (TiXmlElement* material_xml = robot_xml->FirstChildElement("material"); material_xml; material_xml = material_xml->NextSiblingElement("material")) + { + my_shared_ptr material; + material.reset(new Material); + + try { + parseMaterial(*material, material_xml, false); // material needs to be fully defined here + if (model->getMaterial(material->name)) + { + logError("material '%s' is not unique.", material->name.c_str()); + material.reset(0); + model.reset(0); + return model; + } + else + { + model->materials_.insert(make_pair(material->name,material)); + logDebug("urdfdom: successfully added a new material '%s'", material->name.c_str()); + } + } + catch (ParseError &e) { + logError("material xml is not initialized correctly"); + material.reset(0); + model.reset(0); + return model; + } + } + + // Get all Link elements + for (TiXmlElement* link_xml = robot_xml->FirstChildElement("link"); link_xml; link_xml = link_xml->NextSiblingElement("link")) + { + my_shared_ptr link; + link.reset(new Link); + model->m_numLinks++; + + try { + parseLink(*link, link_xml); + if (model->getLink(link->name)) + { + logError("link '%s' is not unique.", link->name.c_str()); + model.reset(0); + return model; + } + else + { + // set link visual material + logDebug("urdfdom: setting link '%s' material", link->name.c_str()); + if (link->visual) + { + if (!link->visual->material_name.empty()) + { + if (model->getMaterial(link->visual->material_name)) + { + logDebug("urdfdom: setting link '%s' material to '%s'", link->name.c_str(),link->visual->material_name.c_str()); + link->visual->material = model->getMaterial( link->visual->material_name.c_str() ); + } + else + { + if (link->visual->material) + { + logDebug("urdfdom: link '%s' material '%s' defined in Visual.", link->name.c_str(),link->visual->material_name.c_str()); + model->materials_.insert(make_pair(link->visual->material->name,link->visual->material)); + } + else + { + logError("link '%s' material '%s' undefined.", link->name.c_str(),link->visual->material_name.c_str()); + model.reset(0); + return model; + } + } + } + } + + model->links_.insert(make_pair(link->name,link)); + logDebug("urdfdom: successfully added a new link '%s'", link->name.c_str()); + } + } + catch (ParseError &e) { + logError("link xml is not initialized correctly"); + model.reset(0); + return model; + } + } + if (model->links_.empty()){ + logError("No link elements found in urdf file"); + model.reset(0); + return model; + } + + // Get all Joint elements + for (TiXmlElement* joint_xml = robot_xml->FirstChildElement("joint"); joint_xml; joint_xml = joint_xml->NextSiblingElement("joint")) + { + my_shared_ptr joint; + joint.reset(new Joint); + model->m_numJoints++; + + if (parseJoint(*joint, joint_xml)) + { + if (model->getJoint(joint->name)) + { + logError("joint '%s' is not unique.", joint->name.c_str()); + model.reset(0); + return model; + } + else + { + model->joints_.insert(make_pair(joint->name,joint)); + logDebug("urdfdom: successfully added a new joint '%s'", joint->name.c_str()); + } + } + else + { + logError("joint xml is not initialized correctly"); + model.reset(0); + return model; + } + } + + + // every link has children links and joints, but no parents, so we create a + // local convenience data structure for keeping child->parent relations + std::map parent_link_tree; + parent_link_tree.clear(); + + // building tree: name mapping + try + { + model->initTree(parent_link_tree); + } + catch(ParseError &e) + { + logError("Failed to build tree: %s", e.what()); + model.reset(0); + return model; + } + + // find the root link + try + { + model->initRoot(parent_link_tree); + } + catch(ParseError &e) + { + logError("Failed to find root link: %s", e.what()); + model.reset(0); + return model; + } + + return model; +} + + + +} + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/pose.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/pose.cpp new file mode 100644 index 0000000..e90247c --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/pose.cpp @@ -0,0 +1,91 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen, John Hsu */ + + +#include +#include +#include +//#include +#include + +#ifdef URDF_USE_CONSOLE_BRIDGE +#include +#else +#include "urdf/boost_replacement/printf_console.h" +#endif + +#include +#include + + +namespace urdf{ + +bool parsePose(Pose &pose, TiXmlElement* xml) +{ + pose.clear(); + if (xml) + { + const char* xyz_str = xml->Attribute("xyz"); + if (xyz_str != NULL) + { + try { + pose.position.init(xyz_str); + } + catch (ParseError &e) { + logError(e.what()); + return false; + } + } + + const char* rpy_str = xml->Attribute("rpy"); + if (rpy_str != NULL) + { + try { + pose.rotation.init(rpy_str); + } + catch (ParseError &e) { + logError(e.what()); + return false; + } + } + } + return true; +} + + +} + + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/twist.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/twist.cpp new file mode 100644 index 0000000..4980e17 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/twist.cpp @@ -0,0 +1,85 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + + +#include +#include +#include +#include +#include +#include +#include + +namespace urdf{ + +bool parseTwist(Twist &twist, TiXmlElement* xml) +{ + twist.clear(); + if (xml) + { + const char* linear_char = xml->Attribute("linear"); + if (linear_char != NULL) + { + try { + twist.linear.init(linear_char); + } + catch (ParseError &e) { + twist.linear.clear(); + logError("Malformed linear string [%s]: %s", linear_char, e.what()); + return false; + } + } + + const char* angular_char = xml->Attribute("angular"); + if (angular_char != NULL) + { + try { + twist.angular.init(angular_char); + } + catch (ParseError &e) { + twist.angular.clear(); + logError("Malformed angular [%s]: %s", angular_char, e.what()); + return false; + } + } + } + return true; +} + +} + + + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_model_state.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_model_state.cpp new file mode 100644 index 0000000..f30b845 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_model_state.cpp @@ -0,0 +1,154 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + + +#include +#include +#include +#include +#include +#include +#include + +namespace urdf{ + +bool parseModelState(ModelState &ms, TiXmlElement* config) +{ + ms.clear(); + + const char *name_char = config->Attribute("name"); + if (!name_char) + { + logError("No name given for the model_state."); + return false; + } + ms.name = std::string(name_char); + + const char *time_stamp_char = config->Attribute("time_stamp"); + if (time_stamp_char) + { + try { + double sec = boost::lexical_cast(time_stamp_char); + ms.time_stamp.set(sec); + } + catch (boost::bad_lexical_cast &e) { + logError("Parsing time stamp [%s] failed: %s", time_stamp_char, e.what()); + return false; + } + } + + TiXmlElement *joint_state_elem = config->FirstChildElement("joint_state"); + if (joint_state_elem) + { + boost::shared_ptr joint_state; + joint_state.reset(new JointState()); + + const char *joint_char = joint_state_elem->Attribute("joint"); + if (joint_char) + joint_state->joint = std::string(joint_char); + else + { + logError("No joint name given for the model_state."); + return false; + } + + // parse position + const char *position_char = joint_state_elem->Attribute("position"); + if (position_char) + { + + std::vector pieces; + boost::split( pieces, position_char, boost::is_any_of(" ")); + for (unsigned int i = 0; i < pieces.size(); ++i){ + if (pieces[i] != ""){ + try { + joint_state->position.push_back(boost::lexical_cast(pieces[i].c_str())); + } + catch (boost::bad_lexical_cast &e) { + throw ParseError("position element ("+ pieces[i] +") is not a valid float"); + } + } + } + } + + // parse velocity + const char *velocity_char = joint_state_elem->Attribute("velocity"); + if (velocity_char) + { + + std::vector pieces; + boost::split( pieces, velocity_char, boost::is_any_of(" ")); + for (unsigned int i = 0; i < pieces.size(); ++i){ + if (pieces[i] != ""){ + try { + joint_state->velocity.push_back(boost::lexical_cast(pieces[i].c_str())); + } + catch (boost::bad_lexical_cast &e) { + throw ParseError("velocity element ("+ pieces[i] +") is not a valid float"); + } + } + } + } + + // parse effort + const char *effort_char = joint_state_elem->Attribute("effort"); + if (effort_char) + { + + std::vector pieces; + boost::split( pieces, effort_char, boost::is_any_of(" ")); + for (unsigned int i = 0; i < pieces.size(); ++i){ + if (pieces[i] != ""){ + try { + joint_state->effort.push_back(boost::lexical_cast(pieces[i].c_str())); + } + catch (boost::bad_lexical_cast &e) { + throw ParseError("effort element ("+ pieces[i] +") is not a valid float"); + } + } + } + } + + // add to vector + ms.joint_states.push_back(joint_state); + } +}; + + + +} + + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_sensor.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_sensor.cpp new file mode 100644 index 0000000..85a886d --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/urdf_sensor.cpp @@ -0,0 +1,364 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + + +#include +#include +#include +#include +#include +#include +#include + +namespace urdf{ + +bool parsePose(Pose &pose, TiXmlElement* xml); + +bool parseCamera(Camera &camera, TiXmlElement* config) +{ + camera.clear(); + camera.type = VisualSensor::CAMERA; + + TiXmlElement *image = config->FirstChildElement("image"); + if (image) + { + const char* width_char = image->Attribute("width"); + if (width_char) + { + try + { + camera.width = boost::lexical_cast(width_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Camera image width [%s] is not a valid int: %s", width_char, e.what()); + return false; + } + } + else + { + logError("Camera sensor needs an image width attribute"); + return false; + } + + const char* height_char = image->Attribute("height"); + if (height_char) + { + try + { + camera.height = boost::lexical_cast(height_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Camera image height [%s] is not a valid int: %s", height_char, e.what()); + return false; + } + } + else + { + logError("Camera sensor needs an image height attribute"); + return false; + } + + const char* format_char = image->Attribute("format"); + if (format_char) + camera.format = std::string(format_char); + else + { + logError("Camera sensor needs an image format attribute"); + return false; + } + + const char* hfov_char = image->Attribute("hfov"); + if (hfov_char) + { + try + { + camera.hfov = boost::lexical_cast(hfov_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Camera image hfov [%s] is not a valid float: %s", hfov_char, e.what()); + return false; + } + } + else + { + logError("Camera sensor needs an image hfov attribute"); + return false; + } + + const char* near_char = image->Attribute("near"); + if (near_char) + { + try + { + camera.near = boost::lexical_cast(near_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Camera image near [%s] is not a valid float: %s", near_char, e.what()); + return false; + } + } + else + { + logError("Camera sensor needs an image near attribute"); + return false; + } + + const char* far_char = image->Attribute("far"); + if (far_char) + { + try + { + camera.far = boost::lexical_cast(far_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Camera image far [%s] is not a valid float: %s", far_char, e.what()); + return false; + } + } + else + { + logError("Camera sensor needs an image far attribute"); + return false; + } + + } + else + { + logError("Camera sensor has no element"); + return false; + } + return true; +} + +bool parseRay(Ray &ray, TiXmlElement* config) +{ + ray.clear(); + ray.type = VisualSensor::RAY; + + TiXmlElement *horizontal = config->FirstChildElement("horizontal"); + if (horizontal) + { + const char* samples_char = horizontal->Attribute("samples"); + if (samples_char) + { + try + { + ray.horizontal_samples = boost::lexical_cast(samples_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray horizontal samples [%s] is not a valid float: %s", samples_char, e.what()); + return false; + } + } + + const char* resolution_char = horizontal->Attribute("resolution"); + if (resolution_char) + { + try + { + ray.horizontal_resolution = boost::lexical_cast(resolution_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray horizontal resolution [%s] is not a valid float: %s", resolution_char, e.what()); + return false; + } + } + + const char* min_angle_char = horizontal->Attribute("min_angle"); + if (min_angle_char) + { + try + { + ray.horizontal_min_angle = boost::lexical_cast(min_angle_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray horizontal min_angle [%s] is not a valid float: %s", min_angle_char, e.what()); + return false; + } + } + + const char* max_angle_char = horizontal->Attribute("max_angle"); + if (max_angle_char) + { + try + { + ray.horizontal_max_angle = boost::lexical_cast(max_angle_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray horizontal max_angle [%s] is not a valid float: %s", max_angle_char, e.what()); + return false; + } + } + } + + TiXmlElement *vertical = config->FirstChildElement("vertical"); + if (vertical) + { + const char* samples_char = vertical->Attribute("samples"); + if (samples_char) + { + try + { + ray.vertical_samples = boost::lexical_cast(samples_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray vertical samples [%s] is not a valid float: %s", samples_char, e.what()); + return false; + } + } + + const char* resolution_char = vertical->Attribute("resolution"); + if (resolution_char) + { + try + { + ray.vertical_resolution = boost::lexical_cast(resolution_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray vertical resolution [%s] is not a valid float: %s", resolution_char, e.what()); + return false; + } + } + + const char* min_angle_char = vertical->Attribute("min_angle"); + if (min_angle_char) + { + try + { + ray.vertical_min_angle = boost::lexical_cast(min_angle_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray vertical min_angle [%s] is not a valid float: %s", min_angle_char, e.what()); + return false; + } + } + + const char* max_angle_char = vertical->Attribute("max_angle"); + if (max_angle_char) + { + try + { + ray.vertical_max_angle = boost::lexical_cast(max_angle_char); + } + catch (boost::bad_lexical_cast &e) + { + logError("Ray vertical max_angle [%s] is not a valid float: %s", max_angle_char, e.what()); + return false; + } + } + } +} + +boost::shared_ptr parseVisualSensor(TiXmlElement *g) +{ + boost::shared_ptr visual_sensor; + + // get sensor type + TiXmlElement *sensor_xml; + if (g->FirstChildElement("camera")) + { + Camera *camera = new Camera(); + visual_sensor.reset(camera); + sensor_xml = g->FirstChildElement("camera"); + if (!parseCamera(*camera, sensor_xml)) + visual_sensor.reset(); + } + else if (g->FirstChildElement("ray")) + { + Ray *ray = new Ray(); + visual_sensor.reset(ray); + sensor_xml = g->FirstChildElement("ray"); + if (!parseRay(*ray, sensor_xml)) + visual_sensor.reset(); + } + else + { + logError("No know sensor types [camera|ray] defined in block"); + } + return visual_sensor; +} + + +bool parseSensor(Sensor &sensor, TiXmlElement* config) +{ + sensor.clear(); + + const char *name_char = config->Attribute("name"); + if (!name_char) + { + logError("No name given for the sensor."); + return false; + } + sensor.name = std::string(name_char); + + // parse parent_link_name + const char *parent_link_name_char = config->Attribute("parent_link_name"); + if (!parent_link_name_char) + { + logError("No parent_link_name given for the sensor."); + return false; + } + sensor.parent_link_name = std::string(parent_link_name_char); + + // parse origin + TiXmlElement *o = config->FirstChildElement("origin"); + if (o) + { + if (!parsePose(sensor.origin, o)) + return false; + } + + // parse sensor + sensor.sensor = parseVisualSensor(config); + return true; +} + + +} + + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/world.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/world.cpp new file mode 100644 index 0000000..ddc27c5 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/src/world.cpp @@ -0,0 +1,71 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace urdf{ + +bool parseWorld(World &world, TiXmlElement* config) +{ + + // to be implemented + + return true; +} + +bool exportWorld(World &world, TiXmlElement* xml) +{ + TiXmlElement * world_xml = new TiXmlElement("world"); + world_xml->SetAttribute("name", world.name); + + // to be implemented + // exportModels(*world.models, world_xml); + + xml->LinkEndChild(world_xml); + + return true; +} + +} diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/test/memtest.cpp b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/test/memtest.cpp new file mode 100644 index 0000000..d835eb3 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom/urdf_parser/test/memtest.cpp @@ -0,0 +1,20 @@ +#include "urdf_parser/urdf_parser.h" +#include +#include + +int main(int argc, char** argv){ + while (true){ + std::string xml_string; + std::fstream xml_file(argv[1], std::fstream::in); + while ( xml_file.good() ) + { + std::string line; + std::getline( xml_file, line); + xml_string += (line + "\n"); + } + xml_file.close(); + + + urdf::parseURDF(xml_string); + } +} diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/LICENSE b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/LICENSE new file mode 100644 index 0000000..e80920e --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/LICENSE @@ -0,0 +1,15 @@ +Software License Agreement (Apache License) + +Copyright 2011 John Hsu + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/README.txt b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/README.txt new file mode 100644 index 0000000..6a841d5 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/README.txt @@ -0,0 +1,6 @@ +The URDF (U-Robot Description Format) headers + provides core data structure headers for URDF. + +For now, the details of the URDF specifications reside on + http://ros.org/wiki/urdf + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_exception/include/urdf_exception/exception.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_exception/include/urdf_exception/exception.h new file mode 100644 index 0000000..24222f1 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_exception/include/urdf_exception/exception.h @@ -0,0 +1,53 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +// URDF exceptions +#ifndef URDF_INTERFACE_EXCEPTION_H_ +#define URDF_INTERFACE_EXCEPTION_H_ + +#include +#include + +namespace urdf +{ + +class ParseError: public std::runtime_error +{ +public: + ParseError(const std::string &error_msg) : std::runtime_error(error_msg) {}; +}; + +} + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/color.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/color.h new file mode 100644 index 0000000..9c15dd7 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/color.h @@ -0,0 +1,101 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Josh Faust */ + +#ifndef URDF_INTERFACE_COLOR_H +#define URDF_INTERFACE_COLOR_H + +#include +#include +#include +//#include +//#include + +namespace urdf +{ + +class Color +{ +public: + Color() {this->clear();}; + float r; + float g; + float b; + float a; + + void clear() + { + r = g = b = 0.0f; + a = 1.0f; + } + bool init(const std::string &vector_str) + { + this->clear(); + std::vector pieces; + std::vector rgba; + + boost::split( pieces, vector_str, boost::is_any_of(" ")); + for (unsigned int i = 0; i < pieces.size(); ++i) + { + if (!pieces[i].empty()) + { + try + { + rgba.push_back(boost::lexical_cast(pieces[i].c_str())); + } + catch (boost::bad_lexical_cast &e) + { + return false; + } + } + } + + if (rgba.size() != 4) + { + return false; + } + this->r = rgba[0]; + this->g = rgba[1]; + this->b = rgba[2]; + this->a = rgba[3]; + + return true; + }; +}; + +} + +#endif + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/joint.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/joint.h new file mode 100644 index 0000000..cd889dc --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/joint.h @@ -0,0 +1,234 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + +#ifndef URDF_INTERFACE_JOINT_H +#define URDF_INTERFACE_JOINT_H + +#include +#include +#ifdef URDF_USE_BOOST +#include +#define my_shared_ptr my_shared_ptr +#else +#include +#endif + +#include + + +namespace urdf{ + +class Link; + +class JointDynamics +{ +public: + JointDynamics() { this->clear(); }; + double damping; + double friction; + + void clear() + { + damping = 0; + friction = 0; + }; +}; + +class JointLimits +{ +public: + JointLimits() { this->clear(); }; + double lower; + double upper; + double effort; + double velocity; + + void clear() + { + lower = 0; + upper = 0; + effort = 0; + velocity = 0; + }; +}; + +/// \brief Parameters for Joint Safety Controllers +class JointSafety +{ +public: + /// clear variables on construction + JointSafety() { this->clear(); }; + + /// + /// IMPORTANT: The safety controller support is very much PR2 specific, not intended for generic usage. + /// + /// Basic safety controller operation is as follows + /// + /// current safety controllers will take effect on joints outside the position range below: + /// + /// position range: [JointSafety::soft_lower_limit + JointLimits::velocity / JointSafety::k_position, + /// JointSafety::soft_uppper_limit - JointLimits::velocity / JointSafety::k_position] + /// + /// if (joint_position is outside of the position range above) + /// velocity_limit_min = -JointLimits::velocity + JointSafety::k_position * (joint_position - JointSafety::soft_lower_limit) + /// velocity_limit_max = JointLimits::velocity + JointSafety::k_position * (joint_position - JointSafety::soft_upper_limit) + /// else + /// velocity_limit_min = -JointLimits::velocity + /// velocity_limit_max = JointLimits::velocity + /// + /// velocity range: [velocity_limit_min + JointLimits::effort / JointSafety::k_velocity, + /// velocity_limit_max - JointLimits::effort / JointSafety::k_velocity] + /// + /// if (joint_velocity is outside of the velocity range above) + /// effort_limit_min = -JointLimits::effort + JointSafety::k_velocity * (joint_velocity - velocity_limit_min) + /// effort_limit_max = JointLimits::effort + JointSafety::k_velocity * (joint_velocity - velocity_limit_max) + /// else + /// effort_limit_min = -JointLimits::effort + /// effort_limit_max = JointLimits::effort + /// + /// Final effort command sent to the joint is saturated by [effort_limit_min,effort_limit_max] + /// + /// Please see wiki for more details: http://www.ros.org/wiki/pr2_controller_manager/safety_limits + /// + double soft_upper_limit; + double soft_lower_limit; + double k_position; + double k_velocity; + + void clear() + { + soft_upper_limit = 0; + soft_lower_limit = 0; + k_position = 0; + k_velocity = 0; + }; +}; + + +class JointCalibration +{ +public: + JointCalibration() { this->clear(); }; + double reference_position; + my_shared_ptr rising, falling; + + void clear() + { + reference_position = 0; + }; +}; + +class JointMimic +{ +public: + JointMimic() { this->clear(); }; + double offset; + double multiplier; + std::string joint_name; + + void clear() + { + offset = 0.0; + multiplier = 0.0; + joint_name.clear(); + }; +}; + + +class Joint +{ +public: + + Joint() { this->clear(); }; + + std::string name; + enum + { + UNKNOWN, REVOLUTE, CONTINUOUS, PRISMATIC, FLOATING, PLANAR, FIXED + } type; + + /// \brief type_ meaning of axis_ + /// ------------------------------------------------------ + /// UNKNOWN unknown type + /// REVOLUTE rotation axis + /// PRISMATIC translation axis + /// FLOATING N/A + /// PLANAR plane normal axis + /// FIXED N/A + Vector3 axis; + + /// child Link element + /// child link frame is the same as the Joint frame + std::string child_link_name; + + /// parent Link element + /// origin specifies the transform from Parent Link to Joint Frame + std::string parent_link_name; + /// transform from Parent Link frame to Joint frame + Pose parent_to_joint_origin_transform; + + /// Joint Dynamics + my_shared_ptr dynamics; + + /// Joint Limits + my_shared_ptr limits; + + /// Unsupported Hidden Feature + my_shared_ptr safety; + + /// Unsupported Hidden Feature + my_shared_ptr calibration; + + /// Option to Mimic another Joint + my_shared_ptr mimic; + + void clear() + { + this->axis.clear(); + this->child_link_name.clear(); + this->parent_link_name.clear(); + this->parent_to_joint_origin_transform.clear(); + this->dynamics.reset(0); + this->limits.reset(0); + this->safety.reset(0); + this->calibration.reset(0); + this->type = UNKNOWN; + }; +}; + +} + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/link.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/link.h new file mode 100644 index 0000000..22e64cf --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/link.h @@ -0,0 +1,262 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + +#ifndef URDF_INTERFACE_LINK_H +#define URDF_INTERFACE_LINK_H + +#include +#include +#include + + +#ifdef URDF_USE_BOOST + #include + #include +#else + #include +#endif + +#include "joint.h" +#include "color.h" +#include "pose.h" + +namespace urdf{ + +class Geometry +{ +public: + enum {SPHERE, BOX, CYLINDER, MESH} type; + + virtual ~Geometry(void) + { + } +}; + +class Sphere : public Geometry +{ +public: + Sphere() { this->clear(); type = SPHERE; }; + double radius; + + void clear() + { + radius = 0; + }; +}; + +class Box : public Geometry +{ +public: + Box() { this->clear(); type = BOX; } + Vector3 dim; + + void clear() + { + this->dim.clear(); + }; +}; + +class Cylinder : public Geometry +{ +public: + Cylinder() { this->clear(); type = CYLINDER; }; + double length; + double radius; + + void clear() + { + length = 0; + radius = 0; + }; +}; + +class Mesh : public Geometry +{ +public: + Mesh() { this->clear(); type = MESH; }; + std::string filename; + Vector3 scale; + + void clear() + { + filename.clear(); + // default scale + scale.x = 1; + scale.y = 1; + scale.z = 1; + }; +}; + +class Material +{ +public: + Material() { this->clear(); }; + std::string name; + std::string texture_filename; + Color color; + + void clear() + { + color.clear(); + texture_filename.clear(); + name.clear(); + }; +}; + +class Inertial +{ +public: + Inertial() { this->clear(); }; + Pose origin; + double mass; + double ixx,ixy,ixz,iyy,iyz,izz; + + void clear() + { + origin.clear(); + mass = 0; + ixx = ixy = ixz = iyy = iyz = izz = 0; + }; +}; + +class Visual +{ +public: + Visual() { this->clear(); }; + Pose origin; + my_shared_ptr geometry; + + std::string material_name; + my_shared_ptr material; + + void clear() + { + origin.clear(); + material_name.clear(); + material.reset(0); + geometry.reset(0); + name.clear(); + }; + + std::string name; +}; + +class Collision +{ +public: + Collision() { this->clear(); }; + Pose origin; + my_shared_ptr geometry; + + void clear() + { + origin.clear(); + geometry.reset(0); + name.clear(); + }; + + std::string name; + +}; + + +class Link +{ +public: + Link() { this->clear(); }; + + std::string name; + + /// inertial element + my_shared_ptr inertial; + + /// visual element + my_shared_ptr visual; + + /// collision element + my_shared_ptr collision; + + /// if more than one collision element is specified, all collision elements are placed in this array (the collision member points to the first element of the array) + std::vector > collision_array; + + /// if more than one visual element is specified, all visual elements are placed in this array (the visual member points to the first element of the array) + std::vector > visual_array; + + /// Parent Joint element + /// explicitly stating "parent" because we want directional-ness for tree structure + /// every link can have one parent + my_shared_ptr parent_joint; + + std::vector > child_joints; + std::vector > child_links; + + mutable int m_link_index; + + const Link* getParent() const + {return parent_link_;} + + void setParent(const my_shared_ptr &parent) + { + parent_link_ = parent.get(); + } + + void clear() + { + this->name.clear(); + this->inertial.reset(0); + this->visual.reset(0); + this->collision.reset(0); + this->parent_joint.reset(0); + this->child_joints.clear(); + this->child_links.clear(); + this->collision_array.clear(); + this->visual_array.clear(); + this->m_link_index=-1; + this->parent_link_ = NULL; + }; + +private: +// boost::weak_ptr parent_link_; + const Link* parent_link_; + +}; + + + + +} + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/model.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/model.h new file mode 100644 index 0000000..8e93d94 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/model.h @@ -0,0 +1,220 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + +#ifndef URDF_INTERFACE_MODEL_H +#define URDF_INTERFACE_MODEL_H + +#include +#include +//#include +#include +#include //printf +#include + +namespace urdf { + +class ModelInterface +{ +public: + my_shared_ptr getRoot(void) const{return this->root_link_;}; + my_shared_ptr getLink(const std::string& name) const + { + my_shared_ptr ptr; + if (this->links_.find(name) == this->links_.end()) + ptr.reset(0); + else + ptr = this->links_.find(name)->second; + return ptr; + }; + + my_shared_ptr getJoint(const std::string& name) const + { + my_shared_ptr ptr; + if (this->joints_.find(name) == this->joints_.end()) + ptr.reset(0); + else + ptr = this->joints_.find(name)->second; + return ptr; + }; + + + const std::string& getName() const {return name_;}; + void getLinks(std::vector >& links) const + { + for (std::map >::const_iterator link = this->links_.begin();link != this->links_.end(); link++) + { + links.push_back(link->second); + } + }; + + void clear() + { + m_numLinks=0; + m_numJoints = 0; + name_.clear(); + this->links_.clear(); + this->joints_.clear(); + this->materials_.clear(); + this->root_link_.reset(0); + }; + + /// non-const getLink() + void getLink(const std::string& name,my_shared_ptr &link) const + { + my_shared_ptr ptr; + if (this->links_.find(name) == this->links_.end()) + ptr.reset(0); + else + ptr = this->links_.find(name)->second; + link = ptr; + }; + + /// non-const getMaterial() + my_shared_ptr getMaterial(const std::string& name) const + { + my_shared_ptr ptr; + if (this->materials_.find(name) == this->materials_.end()) + ptr.reset(0); + else + ptr = this->materials_.find(name)->second; + return ptr; + }; + + void initTree(std::map &parent_link_tree) + { + // loop through all joints, for every link, assign children links and children joints + for (std::map >::iterator joint = this->joints_.begin();joint != this->joints_.end(); joint++) + { + std::string parent_link_name = joint->second->parent_link_name; + std::string child_link_name = joint->second->child_link_name; + + if (parent_link_name.empty() || child_link_name.empty()) + { + assert(0); + + // throw ParseError("Joint [" + joint->second->name + "] is missing a parent and/or child link specification."); + } + else + { + // find child and parent links + my_shared_ptr child_link, parent_link; + this->getLink(child_link_name, child_link); + if (!child_link) + { + printf("Error: child link [%s] of joint [%s] not found\n", child_link_name.c_str(),joint->first.c_str() ); + assert(0); +// throw ParseError("child link [" + child_link_name + "] of joint [" + joint->first + "] not found"); + } + this->getLink(parent_link_name, parent_link); + if (!parent_link) + { + assert(0); + +/* throw ParseError("parent link [" + parent_link_name + "] of joint [" + joint->first + "] not found. This is not valid according to the URDF spec. Every link you refer to from a joint needs to be explicitly defined in the robot description. To fix this problem you can either remove this joint [" + joint->first + "] from your urdf file, or add \"\" to your urdf file."); + + */} + + //set parent link for child link + child_link->setParent(parent_link); + + //set parent joint for child link + child_link->parent_joint = joint->second; + + //set child joint for parent link + parent_link->child_joints.push_back(joint->second); + + //set child link for parent link + parent_link->child_links.push_back(child_link); + + // fill in child/parent string map + parent_link_tree[child_link->name] = parent_link_name; + } + } + } + + void initRoot(const std::map &parent_link_tree) + { + this->root_link_.reset(0); + + // find the links that have no parent in the tree + for (std::map >::const_iterator l=this->links_.begin(); l!=this->links_.end(); l++) + { + std::map::const_iterator parent = parent_link_tree.find(l->first); + if (parent == parent_link_tree.end()) + { + // store root link + if (!this->root_link_) + { + getLink(l->first, this->root_link_); + } + // we already found a root link + else + { + assert(0); + // throw ParseError("Two root links found: [" + this->root_link_->name + "] and [" + l->first + "]"); + } + } + } + if (!this->root_link_) + { + assert(0); + //throw ParseError("No root link found. The robot xml is not a valid tree."); + } + } + + + /// \brief complete list of Links + std::map > links_; + /// \brief complete list of Joints + std::map > joints_; + /// \brief complete list of Materials + std::map > materials_; + + /// \brief The name of the robot model + std::string name_; + + /// \brief The root is always a link (the parent of the tree describing the robot) + my_shared_ptr root_link_; + + int m_numLinks;//includes parent + int m_numJoints; + + +}; + +} + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/pose.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/pose.h new file mode 100644 index 0000000..93183c8 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/pose.h @@ -0,0 +1,265 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: Wim Meeussen */ + +#ifndef URDF_INTERFACE_POSE_H +#define URDF_INTERFACE_POSE_H + +#include +//#include +#include +#include +#ifndef M_PI +#define M_PI 3.141592538 +#endif //M_PI + +#ifdef URDF_USE_BOOST + #include + #include +#else + #include + #include +#endif //URDF_USE_BOOST + +#include +#include + +namespace urdf{ + +class Vector3 +{ +public: + Vector3(double _x,double _y, double _z) {this->x=_x;this->y=_y;this->z=_z;}; + Vector3() {this->clear();}; + double x; + double y; + double z; + + void clear() {this->x=this->y=this->z=0.0;}; + void init(const std::string &vector_str) + { + this->clear(); + std::vector pieces; + std::vector xyz; + boost::split( pieces, vector_str, boost::is_any_of(" ")); + for (unsigned int i = 0; i < pieces.size(); ++i){ + if (pieces[i] != ""){ + try { + xyz.push_back(boost::lexical_cast(pieces[i].c_str())); + } + catch (boost::bad_lexical_cast &e) + { + assert(0); + // throw ParseError("Unable to parse component [" + pieces[i] + "] to a double (while parsing a vector value)"); + } + } + } + + + + if (xyz.size() != 3) + { + assert(0); + // throw ParseError("Parser found " + boost::lexical_cast(xyz.size()) + " elements but 3 expected while parsing vector [" + vector_str + "]"); + } + this->x = xyz[0]; + this->y = xyz[1]; + this->z = xyz[2]; + } + + Vector3 operator+(Vector3 vec) + { + return Vector3(this->x+vec.x,this->y+vec.y,this->z+vec.z); + }; +}; + +class Rotation +{ +public: + Rotation(double _x,double _y, double _z, double _w) {this->x=_x;this->y=_y;this->z=_z;this->w=_w;}; + Rotation() {this->clear();}; + void getQuaternion(double &quat_x,double &quat_y,double &quat_z, double &quat_w) const + { + quat_x = this->x; + quat_y = this->y; + quat_z = this->z; + quat_w = this->w; + }; + void getRPY(double &roll,double &pitch,double &yaw) const + { + double sqw; + double sqx; + double sqy; + double sqz; + + sqx = this->x * this->x; + sqy = this->y * this->y; + sqz = this->z * this->z; + sqw = this->w * this->w; + + roll = atan2(2 * (this->y*this->z + this->w*this->x), sqw - sqx - sqy + sqz); + double sarg = -2 * (this->x*this->z - this->w*this->y); + pitch = sarg <= -1.0 ? -0.5*M_PI : (sarg >= 1.0 ? 0.5*M_PI : asin(sarg)); + yaw = atan2(2 * (this->x*this->y + this->w*this->z), sqw + sqx - sqy - sqz); + + }; + void setFromQuaternion(double quat_x,double quat_y,double quat_z,double quat_w) + { + this->x = quat_x; + this->y = quat_y; + this->z = quat_z; + this->w = quat_w; + this->normalize(); + }; + void setFromRPY(double roll, double pitch, double yaw) + { + double phi, the, psi; + + phi = roll / 2.0; + the = pitch / 2.0; + psi = yaw / 2.0; + + this->x = sin(phi) * cos(the) * cos(psi) - cos(phi) * sin(the) * sin(psi); + this->y = cos(phi) * sin(the) * cos(psi) + sin(phi) * cos(the) * sin(psi); + this->z = cos(phi) * cos(the) * sin(psi) - sin(phi) * sin(the) * cos(psi); + this->w = cos(phi) * cos(the) * cos(psi) + sin(phi) * sin(the) * sin(psi); + + this->normalize(); + }; + + double x,y,z,w; + + void init(const std::string &rotation_str) + { + this->clear(); + Vector3 rpy; + rpy.init(rotation_str); + setFromRPY(rpy.x, rpy.y, rpy.z); + } + + void clear() { this->x=this->y=this->z=0.0;this->w=1.0; } + + void normalize() + { + double s = sqrt(this->x * this->x + + this->y * this->y + + this->z * this->z + + this->w * this->w); + if (s == 0.0) + { + this->x = 0.0; + this->y = 0.0; + this->z = 0.0; + this->w = 1.0; + } + else + { + this->x /= s; + this->y /= s; + this->z /= s; + this->w /= s; + } + }; + + // Multiplication operator (copied from gazebo) + Rotation operator*( const Rotation &qt ) const + { + Rotation c; + + c.x = this->w * qt.x + this->x * qt.w + this->y * qt.z - this->z * qt.y; + c.y = this->w * qt.y - this->x * qt.z + this->y * qt.w + this->z * qt.x; + c.z = this->w * qt.z + this->x * qt.y - this->y * qt.x + this->z * qt.w; + c.w = this->w * qt.w - this->x * qt.x - this->y * qt.y - this->z * qt.z; + + return c; + }; + /// Rotate a vector using the quaternion + Vector3 operator*(Vector3 vec) const + { + Rotation tmp; + Vector3 result; + + tmp.w = 0.0; + tmp.x = vec.x; + tmp.y = vec.y; + tmp.z = vec.z; + + tmp = (*this) * (tmp * this->GetInverse()); + + result.x = tmp.x; + result.y = tmp.y; + result.z = tmp.z; + + return result; + }; + // Get the inverse of this quaternion + Rotation GetInverse() const + { + Rotation q; + + double norm = this->w*this->w+this->x*this->x+this->y*this->y+this->z*this->z; + + if (norm > 0.0) + { + q.w = this->w / norm; + q.x = -this->x / norm; + q.y = -this->y / norm; + q.z = -this->z / norm; + } + + return q; + }; + + +}; + +class Pose +{ +public: + Pose() { this->clear(); }; + + Vector3 position; + Rotation rotation; + + void clear() + { + this->position.clear(); + this->rotation.clear(); + }; +}; + +} + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/twist.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/twist.h new file mode 100644 index 0000000..5560de3 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model/include/urdf_model/twist.h @@ -0,0 +1,68 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + +#ifndef URDF_TWIST_H +#define URDF_TWIST_H + +#include +#include +#include +#include +#include + +namespace urdf{ + + +class Twist +{ +public: + Twist() { this->clear(); }; + + Vector3 linear; + // Angular velocity represented by Euler angles + Vector3 angular; + + void clear() + { + this->linear.clear(); + this->angular.clear(); + }; +}; + +} + +#endif + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/model_state.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/model_state.h new file mode 100644 index 0000000..b132719 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/model_state.h @@ -0,0 +1,141 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + +#ifndef URDF_MODEL_STATE_H +#define URDF_MODEL_STATE_H + +#include +#include +#include +#include +#include + +#include "urdf_model/pose.h" +#include + + +namespace urdf{ + +class Time +{ +public: + Time() { this->clear(); }; + + void set(double _seconds) + { + this->sec = (int32_t)(floor(_seconds)); + this->nsec = (int32_t)(round((_seconds - this->sec) * 1e9)); + this->Correct(); + }; + + operator double () + { + return (static_cast(this->sec) + + static_cast(this->nsec)*1e-9); + }; + + int32_t sec; + int32_t nsec; + + void clear() + { + this->sec = 0; + this->nsec = 0; + }; +private: + void Correct() + { + // Make any corrections + if (this->nsec >= 1e9) + { + this->sec++; + this->nsec = (int32_t)(this->nsec - 1e9); + } + else if (this->nsec < 0) + { + this->sec--; + this->nsec = (int32_t)(this->nsec + 1e9); + } + }; +}; + + +class JointState +{ +public: + JointState() { this->clear(); }; + + /// joint name + std::string joint; + + std::vector position; + std::vector velocity; + std::vector effort; + + void clear() + { + this->joint.clear(); + this->position.clear(); + this->velocity.clear(); + this->effort.clear(); + } +}; + +class ModelState +{ +public: + ModelState() { this->clear(); }; + + /// state name must be unique + std::string name; + + Time time_stamp; + + void clear() + { + this->name.clear(); + this->time_stamp.set(0); + this->joint_states.clear(); + }; + + std::vector > joint_states; + +}; + +} + +#endif + diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/twist.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/twist.h new file mode 100644 index 0000000..05f1917 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_model_state/include/urdf_model_state/twist.h @@ -0,0 +1,42 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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 URDF_MODEL_STATE_TWIST_ +#define URDF_MODEL_STATE_TWIST_ + +#warning "Please Use #include " + +#include + +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_sensor/include/urdf_sensor/sensor.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_sensor/include/urdf_sensor/sensor.h new file mode 100644 index 0000000..3b99695 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_sensor/include/urdf_sensor/sensor.h @@ -0,0 +1,176 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + +/* example + + + + + 1.5708 + + + + + + + + + + + + + + +*/ + + + +#ifndef URDF_SENSOR_H +#define URDF_SENSOR_H + +#include +#include +#include +#include +#include +#include "urdf_model/pose.h" +#include "urdf_model/joint.h" +#include "urdf_model/link.h" + +namespace urdf{ + +class VisualSensor +{ +public: + enum {CAMERA, RAY} type; + virtual ~VisualSensor(void) + { + } +}; + +class Camera : public VisualSensor +{ +public: + Camera() { this->clear(); }; + unsigned int width, height; + /// format is optional: defaults to R8G8B8), but can be + /// (L8|R8G8B8|B8G8R8|BAYER_RGGB8|BAYER_BGGR8|BAYER_GBRG8|BAYER_GRBG8) + std::string format; + double hfov; + double near; + double far; + + void clear() + { + hfov = 0; + width = 0; + height = 0; + format.clear(); + near = 0; + far = 0; + }; +}; + +class Ray : public VisualSensor +{ +public: + Ray() { this->clear(); }; + unsigned int horizontal_samples; + double horizontal_resolution; + double horizontal_min_angle; + double horizontal_max_angle; + unsigned int vertical_samples; + double vertical_resolution; + double vertical_min_angle; + double vertical_max_angle; + + void clear() + { + // set defaults + horizontal_samples = 1; + horizontal_resolution = 1; + horizontal_min_angle = 0; + horizontal_max_angle = 0; + vertical_samples = 1; + vertical_resolution = 1; + vertical_min_angle = 0; + vertical_max_angle = 0; + }; +}; + + +class Sensor +{ +public: + Sensor() { this->clear(); }; + + /// sensor name must be unique + std::string name; + + /// update rate in Hz + double update_rate; + + /// transform from parent frame to optical center + /// with z-forward and x-right, y-down + Pose origin; + + /// sensor + boost::shared_ptr sensor; + + + /// Parent link element name. A pointer is stored in parent_link_. + std::string parent_link_name; + + boost::shared_ptr getParent() const + {return parent_link_.lock();}; + + void setParent(boost::shared_ptr parent) + { this->parent_link_ = parent; } + + void clear() + { + this->name.clear(); + this->sensor.reset(); + this->parent_link_name.clear(); + this->parent_link_.reset(); + }; + +private: + boost::weak_ptr parent_link_; + +}; +} +#endif diff --git a/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_world/include/urdf_world/world.h b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_world/include/urdf_world/world.h new file mode 100644 index 0000000..eb13fc4 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/thirdparty/urdf/urdfdom_headers/urdf_world/include/urdf_world/world.h @@ -0,0 +1,114 @@ +/********************************************************************* +* Software License Agreement (BSD License) +* +* Copyright (c) 2008, Willow Garage, Inc. +* All rights reserved. +* +* 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. +* * Neither the name of the Willow Garage nor the names of its +* contributors may be used to endorse or promote products derived +* from this software without specific prior written permission. +* +* 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 OWNER 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. +*********************************************************************/ + +/* Author: John Hsu */ + +/* encapsulates components in a world + see http://ros.org/wiki/usdf/XML/urdf_world and + for details +*/ +/* example world XML + + + + + ... + + + + + + + + + + + + + + + + + + + +*/ + +#ifndef USDF_STATE_H +#define USDF_STATE_H + +#include +#include +#include +#include +#include +#include + +#include "urdf_model/model.h" +#include "urdf_model/pose.h" +#include "urdf_model/twist.h" + +namespace urdf{ + +class Entity +{ +public: + boost::shared_ptr model; + Pose origin; + Twist twist; +}; + +class World +{ +public: + World() { this->clear(); }; + + /// world name must be unique + std::string name; + + std::vector models; + + void initXml(TiXmlElement* config); + + void clear() + { + this->name.clear(); + }; +}; +} + +#endif + diff --git a/3rdparty/rbdl/addons/urdfreader/urdfreader.cc b/3rdparty/rbdl/addons/urdfreader/urdfreader.cc new file mode 100644 index 0000000..70cc424 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/urdfreader.cc @@ -0,0 +1,315 @@ +#include + +#include "urdfreader.h" + +#include +#include +#include +#include +#include + +#ifdef RBDL_USE_ROS_URDF_LIBRARY +#include +#include + +typedef boost::shared_ptr LinkPtr; +typedef const boost::shared_ptr ConstLinkPtr; +typedef boost::shared_ptr JointPtr; +typedef boost::shared_ptr ModelPtr; + +#else +#include +#include + +typedef my_shared_ptr LinkPtr; +typedef const my_shared_ptr ConstLinkPtr; +typedef my_shared_ptr JointPtr; +typedef my_shared_ptr ModelPtr; + +#endif + +using namespace std; + +namespace RigidBodyDynamics { + +namespace Addons { + +using namespace Math; + +typedef vector URDFLinkVector; +typedef vector URDFJointVector; +typedef map URDFLinkMap; +typedef map URDFJointMap; + +bool construct_model (Model* rbdl_model, ModelPtr urdf_model, bool floating_base, bool verbose) { + LinkPtr urdf_root_link; + + URDFLinkMap link_map; + link_map = urdf_model->links_; + + URDFJointMap joint_map; + joint_map = urdf_model->joints_; + + vector joint_names; + + stack link_stack; + stack joint_index_stack; + + // add the bodies in a depth-first order of the model tree + link_stack.push (link_map[(urdf_model->getRoot()->name)]); + + // add the root body + ConstLinkPtr& root = urdf_model->getRoot (); + Vector3d root_inertial_rpy; + Vector3d root_inertial_position; + Matrix3d root_inertial_inertia; + double root_inertial_mass; + + if (root->inertial) { + root_inertial_mass = root->inertial->mass; + + root_inertial_position.set ( + root->inertial->origin.position.x, + root->inertial->origin.position.y, + root->inertial->origin.position.z); + + root_inertial_inertia(0,0) = root->inertial->ixx; + root_inertial_inertia(0,1) = root->inertial->ixy; + root_inertial_inertia(0,2) = root->inertial->ixz; + + root_inertial_inertia(1,0) = root->inertial->ixy; + root_inertial_inertia(1,1) = root->inertial->iyy; + root_inertial_inertia(1,2) = root->inertial->iyz; + + root_inertial_inertia(2,0) = root->inertial->ixz; + root_inertial_inertia(2,1) = root->inertial->iyz; + root_inertial_inertia(2,2) = root->inertial->izz; + + root->inertial->origin.rotation.getRPY (root_inertial_rpy[0], root_inertial_rpy[1], root_inertial_rpy[2]); + + Body root_link = Body (root_inertial_mass, + root_inertial_position, + root_inertial_inertia); + + Joint root_joint (JointTypeFixed); + if (floating_base) { + root_joint = JointTypeFloatingBase; + } + + SpatialTransform root_joint_frame = SpatialTransform (); + + if (verbose) { + cout << "+ Adding Root Body " << endl; + cout << " joint frame: " << root_joint_frame << endl; + if (floating_base) { + cout << " joint type : floating" << endl; + } else { + cout << " joint type : fixed" << endl; + } + cout << " body inertia: " << endl << root_link.mInertia << endl; + cout << " body mass : " << root_link.mMass << endl; + cout << " body name : " << root->name << endl; + } + + rbdl_model->AppendBody(root_joint_frame, + root_joint, + root_link, + root->name); + } + + if (link_stack.top()->child_joints.size() > 0) { + joint_index_stack.push(0); + } + + while (link_stack.size() > 0) { + LinkPtr cur_link = link_stack.top(); + unsigned int joint_idx = joint_index_stack.top(); + + if (joint_idx < cur_link->child_joints.size()) { + JointPtr cur_joint = cur_link->child_joints[joint_idx]; + + // increment joint index + joint_index_stack.pop(); + joint_index_stack.push (joint_idx + 1); + + link_stack.push (link_map[cur_joint->child_link_name]); + joint_index_stack.push(0); + + if (verbose) { + for (unsigned int i = 1; i < joint_index_stack.size() - 1; i++) { + cout << " "; + } + cout << "joint '" << cur_joint->name << "' child link '" << link_stack.top()->name << "' type = " << cur_joint->type << endl; + } + + joint_names.push_back(cur_joint->name); + } else { + link_stack.pop(); + joint_index_stack.pop(); + } + } + + unsigned int j; + for (j = 0; j < joint_names.size(); j++) { + JointPtr urdf_joint = joint_map[joint_names[j]]; + LinkPtr urdf_parent = link_map[urdf_joint->parent_link_name]; + LinkPtr urdf_child = link_map[urdf_joint->child_link_name]; + + // determine where to add the current joint and child body + unsigned int rbdl_parent_id = 0; + + if (urdf_parent->name != "base_joint" && rbdl_model->mBodies.size() != 1) + rbdl_parent_id = rbdl_model->GetBodyId (urdf_parent->name.c_str()); + + if (rbdl_parent_id == std::numeric_limits::max()) + cerr << "Error while processing joint '" << urdf_joint->name + << "': parent link '" << urdf_parent->name + << "' could not be found." << endl; + + //cout << "joint: " << urdf_joint->name << "\tparent = " << urdf_parent->name << " child = " << urdf_child->name << " parent_id = " << rbdl_parent_id << endl; + + // create the joint + Joint rbdl_joint; + if (urdf_joint->type == urdf::Joint::REVOLUTE || urdf_joint->type == urdf::Joint::CONTINUOUS) { + rbdl_joint = Joint (SpatialVector (urdf_joint->axis.x, urdf_joint->axis.y, urdf_joint->axis.z, 0., 0., 0.)); + } else if (urdf_joint->type == urdf::Joint::PRISMATIC) { + rbdl_joint = Joint (SpatialVector (0., 0., 0., urdf_joint->axis.x, urdf_joint->axis.y, urdf_joint->axis.z)); + } else if (urdf_joint->type == urdf::Joint::FIXED) { + rbdl_joint = Joint (JointTypeFixed); + } else if (urdf_joint->type == urdf::Joint::FLOATING) { + // todo: what order of DoF should be used? + rbdl_joint = Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.)); + } else if (urdf_joint->type == urdf::Joint::PLANAR) { + // todo: which two directions should be used that are perpendicular + // to the specified axis? + cerr << "Error while processing joint '" << urdf_joint->name << "': planar joints not yet supported!" << endl; + return false; + } + + // compute the joint transformation + Vector3d joint_rpy; + Vector3d joint_translation; + urdf_joint->parent_to_joint_origin_transform.rotation.getRPY (joint_rpy[0], joint_rpy[1], joint_rpy[2]); + joint_translation.set ( + urdf_joint->parent_to_joint_origin_transform.position.x, + urdf_joint->parent_to_joint_origin_transform.position.y, + urdf_joint->parent_to_joint_origin_transform.position.z + ); + SpatialTransform rbdl_joint_frame = + Xrot (joint_rpy[0], Vector3d (1., 0., 0.)) + * Xrot (joint_rpy[1], Vector3d (0., 1., 0.)) + * Xrot (joint_rpy[2], Vector3d (0., 0., 1.)) + * Xtrans (Vector3d ( + joint_translation + )); + + // assemble the body + Vector3d link_inertial_position; + Vector3d link_inertial_rpy; + Matrix3d link_inertial_inertia = Matrix3d::Zero(); + double link_inertial_mass = 0.; + + // but only if we actually have inertial data + if (urdf_child->inertial) { + link_inertial_mass = urdf_child->inertial->mass; + + link_inertial_position.set ( + urdf_child->inertial->origin.position.x, + urdf_child->inertial->origin.position.y, + urdf_child->inertial->origin.position.z + ); + urdf_child->inertial->origin.rotation.getRPY (link_inertial_rpy[0], link_inertial_rpy[1], link_inertial_rpy[2]); + + link_inertial_inertia(0,0) = urdf_child->inertial->ixx; + link_inertial_inertia(0,1) = urdf_child->inertial->ixy; + link_inertial_inertia(0,2) = urdf_child->inertial->ixz; + + link_inertial_inertia(1,0) = urdf_child->inertial->ixy; + link_inertial_inertia(1,1) = urdf_child->inertial->iyy; + link_inertial_inertia(1,2) = urdf_child->inertial->iyz; + + link_inertial_inertia(2,0) = urdf_child->inertial->ixz; + link_inertial_inertia(2,1) = urdf_child->inertial->iyz; + link_inertial_inertia(2,2) = urdf_child->inertial->izz; + + if (link_inertial_rpy != Vector3d (0., 0., 0.)) { + cerr << "Error while processing body '" << urdf_child->name << "': rotation of body frames not yet supported. Please rotate the joint frame instead." << endl; + return false; + } + } + + Body rbdl_body = Body (link_inertial_mass, link_inertial_position, link_inertial_inertia); + + if (verbose) { + cout << "+ Adding Body " << endl; + cout << " parent_id : " << rbdl_parent_id << endl; + cout << " joint frame: " << rbdl_joint_frame << endl; + cout << " joint dofs : " << rbdl_joint.mDoFCount << endl; + for (unsigned int j = 0; j < rbdl_joint.mDoFCount; j++) { + cout << " " << j << ": " << rbdl_joint.mJointAxes[j].transpose() << endl; + } + cout << " body inertia: " << endl << rbdl_body.mInertia << endl; + cout << " body mass : " << rbdl_body.mMass << endl; + cout << " body name : " << urdf_child->name << endl; + } + + if (urdf_joint->type == urdf::Joint::FLOATING) { + Matrix3d zero_matrix = Matrix3d::Zero(); + Body null_body (0., Vector3d::Zero(3), zero_matrix); + Joint joint_txtytz(JointTypeTranslationXYZ); + string trans_body_name = urdf_child->name + "_Translate"; + rbdl_model->AddBody (rbdl_parent_id, rbdl_joint_frame, joint_txtytz, null_body, trans_body_name); + + Joint joint_euler_zyx (JointTypeEulerXYZ); + rbdl_model->AppendBody (SpatialTransform(), joint_euler_zyx, rbdl_body, urdf_child->name); + } else { + rbdl_model->AddBody (rbdl_parent_id, rbdl_joint_frame, rbdl_joint, rbdl_body, urdf_child->name); + } + } + + return true; +} + +RBDL_DLLAPI bool URDFReadFromFile (const char* filename, Model* model, bool floating_base, bool verbose) { + ifstream model_file (filename); + if (!model_file) { + cerr << "Error opening file '" << filename << "'." << endl; + abort(); + } + + // reserve memory for the contents of the file + string model_xml_string; + model_file.seekg(0, std::ios::end); + model_xml_string.reserve(model_file.tellg()); + model_file.seekg(0, std::ios::beg); + model_xml_string.assign((std::istreambuf_iterator(model_file)), std::istreambuf_iterator()); + + model_file.close(); + + return URDFReadFromString (model_xml_string.c_str(), model, floating_base, verbose); +} + +RBDL_DLLAPI bool URDFReadFromString (const char* model_xml_string, Model* model, bool floating_base, bool verbose) { + assert (model); + + ModelPtr urdf_model = urdf::parseURDF (model_xml_string); + + if (!construct_model (model, urdf_model, floating_base, verbose)) { + cerr << "Error constructing model from urdf file." << endl; + return false; + } + + model->gravity.set (0., 0., -9.81); + + return true; +} + +} + +} diff --git a/3rdparty/rbdl/addons/urdfreader/urdfreader.h b/3rdparty/rbdl/addons/urdfreader/urdfreader.h new file mode 100644 index 0000000..19d9653 --- /dev/null +++ b/3rdparty/rbdl/addons/urdfreader/urdfreader.h @@ -0,0 +1,18 @@ +#ifndef RBDL_URDFREADER_H +#define RBDL_URDFREADER_H + +#include + +namespace RigidBodyDynamics { + +struct Model; + +namespace Addons { + RBDL_DLLAPI bool URDFReadFromFile (const char* filename, Model* model, bool floating_base, bool verbose = false); + RBDL_DLLAPI bool URDFReadFromString (const char* model_xml_string, Model* model, bool floating_base, bool verbose = false); +} + +} + +/* _RBDL_URDFREADER_H */ +#endif diff --git a/3rdparty/rbdl/doc/Mainpage.h b/3rdparty/rbdl/doc/Mainpage.h new file mode 100644 index 0000000..dffd789 --- /dev/null +++ b/3rdparty/rbdl/doc/Mainpage.h @@ -0,0 +1,170 @@ +/** \file Mainpage.h + * \mainpage Mainpage + * \image html rbdl_logo.png + * + * This is the documentation of RBDL, the Rigid Body Dynamics Library. The + * library contains highly efficient code for both forward and inverse + * dynamics for kinematic chains and branched models. It includes: + * + * \li Recursive Newton Euler Algorithm (RNEA) + * \li Composite Rigid Body Algorithm (CRBA) + * \li Articulated Body Algorithm (ABA). + * + * Furthermore it contains code for forward and inverse kinematics, + * computations of Jacobians, contact handling. \link + * RigidBodyDynamics::Model Models \endlink can be loaded from Lua scripts + * or URDF files. + * + * The code is developed by Martin Felis + * at the research group Optimization in Robotics and + * Biomechanics (ORB) of the Interdisciplinary Center for + * Scientific Computing (IWR) at Heidelberg University. The code + * is heavily inspired by the pseudo code of the book "Rigid Body Dynamics + * Algorithms" of Roy + * Featherstone. + * + * The code has no external dependencies but for optimal performance it is + * advised to use version 3 of the Eigen math library. More information about it can + * be found here: http://eigen.tuxfamily.org/. The Eigen3 library + * must be obtained and installed separately. + * + * \section download Download + * + * You can download the most recent stable version as zip file from + * here:
+ * https://bitbucket.org/rbdl/rbdl/get/default.zip + * + * All development takes place on Bitbucket and you can follow RBDL's + * development here:
+ * https://bitbucket.org/rbdl/rbdl + * + * \section recent_changes Recent Changes + *
    + *
  • 28 April 2016: New release 2.5.0: + *
      + *
    • Added an experimental Cython based Python wrapper of RBDL. The API is + * very close to the C++ API. For a brief glimpse of the API see + * \ref PythonExample "Python Example"
    • + *
    • Matthew Millard added CustomJoints which allow to create different joint + * types completely by user code. They are implemented as proxy joints for + * which their behaviour is specified using virtual functions.
    • + *
    • Added CalcMInvTimesTau() that evaluates multiplication of the inverse of + * the joint space inertia matrix with a vector in O(n) time.
    • + *
    • Added JointTypeFloatingBase which uses TX,TY,TZ and a spherical joint for + * the floating base joint.
    • + *
    • Loading of floating base URDF models must now be specified as a third + * parameter to URDFReadFromFile() and URDFReadFromString()
    • + *
    • Added the URDF code from Bullet3 which gets used when ROS is not found. + * Otherwise use the URDF libraries found via Catkin.
    • + *
    • Added CalcPointVelocity6D, CalcPointAcceleration6D, and CalcPointJacobian6D + * that compute both linear and angular quantities
    • + *
    • Removed Model::SetFloatingBase (body). Use a 6-DoF joint or + * JointTypeFloatingBase instead.
    • + *
    • Fixed building issues when building DLL with MSVC++.
    • + *
    + *
  • + *
  • 20 March 2016: New bugfix version 2.4.1: + *
      + *
    • critical: fixed termination criterion for + * InverseKinematics(). The termination criterion would be evaluated too + * early and thus report convergence too early. This was reported + * independently by Kevin Stein, Yun Fei, and Davide Corradi. Thanks + * for the reports!
    • + *
    • critical: fixed CompositeRigidBodyAlgorithm() when using + * spherical joints (thanks to Sébastien Barthélémy for + * reporting!)
    • + * + *
    + *
  • + *
  • 23 February 2015: New version 2.4.0: + *
      + *
    • Added sparse range-space method ForwardDynamicsContactsRangeSpaceSparse() and ComputeContactImpulsesRangeSpaceSparse()
    • + *
    • Added null-space method ForwardDynamicsContactsNullSpace() and ComputeContactImpulsesNullSpace()
    • + *
    • Renamed ForwardDynamicsContactsLagrangian() to ForwardDynamicsContactsDirect() and ComputeContactImpulsesLagrangian() to ComputeContactImpulsesDirect()
    • + *
    • Renamed ForwardDynamicsContacts() to ForwardDynamicsContactsKokkevis()
    • + *
    • Removed/Fixed CalcAngularMomentum(). The function produced wrong values. The functionality has been integrated into CalcCenterOfMass().
    • + *
    • CalcPointJacobian() does not clear the argument of the result anymore. Caller has to ensure that the matrix was set to zero before using this function.
    • + *
    • Added optional workspace parameters for ForwardDynamicsLagrangian() to optionally reduce memory allocations
    • + *
    • Added JointTypeTranslationXYZ, JointTypeEulerXYZ, and JointTypeEulerYXZ which are equivalent to the emulated multidof joints but faster.
    • + *
    • Added optional parameter to CalcCenterOfMass() to compute angular momentum.
    • + *
    • Added CalcBodySpatialJacobian()
    • + *
    • Added CalcContactJacobian()
    • + *
    • Added NonlinearEffects()
    • + *
    • Added solving of linear systems using standard Householder QR
    • + *
    • LuaModel: Added LuaModelReadFromLuaState()
    • + *
    • URDFReader: Fixed various issues and using faster joints for floating base models
    • + *
    • Various performance improvements
    • + *
    + *
  • + *
+ * + * See \subpage api_version_checking_page for a complete version history. + * + * \section Example Examples + * + * A simple example for creation of a model and computation of the forward + * dynamics using the C++ API can be found \ref SimpleExample "here". + * + * Another example that uses the \ref addon_luamodel_page "LuaModel Addon" can be found \ref + * LuaModelExample "here". + * + * An example of the Python wrapper can be found at \ref PythonExample + * "Python Example". + * + * \section ModuleOverview API reference separated by functional modules + * + * \li \subpage modeling_page + * \li \subpage joint_description + * \li \subpage kinematics_page + * \li \subpage dynamics_page + * \li \subpage contacts_page + * \li \subpage addon_luamodel_page + * + * The page \subpage api_version_checking_page contains information about + * incompatibilities of the existing versions and how to migrate. + * + * \section Licensing Licensing + * + * The library is published under the very permissive zlib free software + * license which should allow you to use the software wherever you need. + * Here is the full license text: + * \verbatim +RBDL - Rigid Body Dynamics Library +Copyright (c) 2011-2014 Martin Felis + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +\endverbatim + + * \section Acknowledgements + * + * Work on this library was funded by the Heidelberg + * Graduate School of Mathematical and Computational Methods for the + * Sciences (HGS) and the European FP7 projects ECHORD (grant number 231143) and Koroibot (grant number 611909). + * + */ diff --git a/3rdparty/rbdl/doc/api_changes.txt b/3rdparty/rbdl/doc/api_changes.txt new file mode 100644 index 0000000..c033405 --- /dev/null +++ b/3rdparty/rbdl/doc/api_changes.txt @@ -0,0 +1,179 @@ +2.5.0 -> next +- Replaced Contacts API by new Constraints API. The new API allows to + compute dynamics of models with kinematic loops but otherwise uses a very + similar API. Loop constraints can be stabilized using Baumgarte + stabilization. Special thanks to Davide Corradi for this contribution! +- New inverse kinematics algorithm and API. It now uses the IK method + described by Sugihara which is faster and more robust than the + previously implemented damped Levenberg-Marquardt method. The new API + allows to specify both point and orientation (and mixed) constraints. + Thanks to Kevin Stein for implementing it! +- Changed Quaternion multiplication behaviour for a more standard + convention: multiplying q1 (1,0,0,0) with q2 (0,1,0,0) results now in + (0,0,1,0) instead of the previous (0,0,-1,0). +- New joint type JointTypeHelical that can be used for screwing motions + (translations and simultaneous rotations), contributed by Stuart Anderson. + +2.4.1 -> 2.5.0 +- Added an experimental Cython based Python wrapper of RBDL. The API is + very close to the C++ API. For a brief glimpse of the API see the file + python/test_wrapper.py. +- Matthew Millard added CustomJoints which allow to create different joint + types completely by user code. They are implemented as proxy joints for + which their behaviour is specified using virtual functions. +- Added CalcMInvTimesTau() that evaluates multiplication of the inverse of + the joint space inertia matrix with a vector in O(n) time. +- Added JointTypeFloatingBase which uses TX,TY,TZ and a spherical joint for + the floating base joint. +- Loading of floating base URDF models must now be specified as a third + parameter to URDFReadFromFile() and URDFReadFromString() +- Added the URDF code from Bullet3 which gets used when ROS is not found. + Otherwise use the URDF libraries found via Catkin. +- Added CalcPointVelocity6D, CalcPointAcceleration6D, and CalcPointJacobian6D + that compute both linear and angular quantities +- Removed Model::SetFloatingBase (body). Use a 6-DoF joint or + JointTypeFloatingBase instead. +- Fixed building issues when building DLL with MSVC++. + +2.4.0 -> 2.4.1 (20 April 2016) +This is a bugfix release that maintains binary compatibility and only fixes +erroneous behaviour. +- critical: fixed termination criterion for InverseKinematics. The termination + criterion would be evaluated too early and thus report convergence too + early. This was reported independently by Kevin Stein, Yun Fei, and Davide + Corradi. Thanks for the reports! +- critical: fixed CompositeRigidBodyAlgorithm when using spherical joints + (thanks to Sébastien Barthélémy for reporting!) + +2.3.3 -> 2.4.0 (23 February 2015) +- Added sparse range-space method ForwardDynamicsContactsRangeSpaceSparse() + and ComputeContactImpulsesRangeSpaceSparse() +- Added null-space method ForwardDynamicsContactsNullSpace() + and ComputeContactImpulsesNullSpace() +- Renamed ForwardDynamicsContactsLagrangian() to + ForwardDynamicsContactsDirect() and + ComputeContactImpulsesLagrangian() to ComputeContactImpulsesDirect() +- Renamed ForwardDynamicsContacts() to ForwardDynamicsContactsKokkevis() +- Removed/Fixed CalcAngularMomentum(). The function produced wrong values. The + functionality has been integrated into CalcCenterOfMass(). +- CalcPointJacobian() does not clear the argument of the result anymore. + Caller has to ensure that the matrix was set to zero before using this + function. +- Added optional workspace parameters for ForwardDynamicsLagrangian() to + optionally reduce memory allocations +- Added JointTypeTranslationXYZ, JointTypeEulerXYZ, and JointTypeEulerYXZ + which are equivalent to the emulated multidof joints but faster. +- Added optional parameter to CalcCenterOfMass to compute angular momentum. +- Added CalcBodySpatialJacobian() +- Added CalcContactJacobian() +- Added NonlinearEffects() +- Added solving of linear systems using standard Householder QR +- LuaModel: Added LuaModelReadFromLuaState() +- URDFReader: Fixed various issues and using faster joints for floating + base models +- Various performance improvements + +2.3.2 -> 2.3.3 (21 October 2014) +- critical: fixed ForwardDynamicsContacts with constraints on a body + hat is attached with a fixed joint. Previous versions simply crashed. + Thanks to Yue Hu for reporting! +- rbdl_print_version() now properly prints whether URDFReader was enabled + at build time +- build system: fixed roblems especially building of the URDFreader +- build system: all CMake variables for RBDL are now prefixed with RBDL_ +- FindRBDL.cmake now can use components to search for the LuaModel or + URDFReader addon + +2.3.1 -> 2.3.2 (29 August 2014) +- critical: fixed ForwardDynamicsLagrangian which used uninitialized values for the joint space inertia matrix +- critical: fixed ForwardDynamicsContacts when using 3-dof joints +- critical: fixed CalcBodyWorldOrientation for fixed joints (thanks to Hilaro Tome!) +- critical: fixed CompositeRigidBodyDynamics when using 3-dof joints (thanks to Henning Koch!) + +2.3.0 -> 2.3.1 (13 July 2014) +- critical: fixed angular momentum computation. Version 2.3.0 produced wrong + results. Thanks to Hilario Tome and Benjamin Michaud for reporting! +- critical: fixed JointTypeEulerZYX. Previous versions produce wrong + results! +- fixed library version number for the LuaModel addon. It now uses version + 2.3 instead of the wrong 2.2. + +2.2.2 -> 2.3.0 (14 March 2014) +- disabled clearing of joint space inertia matrix in CRBA. + It is expected that the matrix is cleared by the user when neccessary. +- Added experimental joint type JointTypeEulerZYX. It does not emulate + multiple degrees of freedom using virtual bodies instead it uses a 3 DoF + motion subspace. Performance is better for the + CompositeRigidBodyAlgorithm but worse for other algorithms. +- Using Eigen3's default column-major ordering for matrices when using + Eigen3. This should have no effect for the user unless matrix elements + are accessed using the .data()[i] operator of Eigen3's matrix class. However + if .data()[i] is used the access indices have to be adjusted. +- added functions to compute kinetic and potential energy and the + computation of the center of mass and its linear velocity: + RigidBodyDynamics::Utils::CalcCenterOfMass + RigidBodyDynamics::Utils::CalcPotentialEnergy + RigidBodyDynamics::Utils::CalcKineticEnergy + RigidBodyDynamics::Utils::CalcAngularMomentum + +2.2.1 -> 2.2.2 (06 November 2013) +- adjusted default constructor for Body. It now has the identity matrix as + inertia, instead of a zero matrix. +- LuaModel: made sure that the Body value is optional and uses the default + Body constructor if not defined. + +2.2.0 -> 2.2.1 04 (November 2013) +- properly exporting LuaTables++ functions when using LuaModel addon + Fixes linking +- fixed exported library version (now at 2.2 as expected) + +2.1.0 -> 2.2.0 (28 October 2013) +- added spherical joints that do not suffer from singularities: + Use joint type JointTypeSpherical +- added Model::q_size, and Model::qdot_size that report the sizes including + all 4 Quaternion parameters when spherical joints are used. + User is advised to use Model::q_size and Model::qdot_size instead of + Model::dof_count. +- removed "constraint_" prefix from constraint impulses, forces and + accelerations from the ConstraintSets: + renaming required if values are queried +- Contact impulses: specification of a contact velocity after a collision: + added ConstraintSet::v_plus which can be set for the desired constraint + velocity after a collision. Previously it used the values stored in + ConstraintSet::constraint_acceleration. + User has to store desired exit velocities manually in CS::v_plus + +2.0.1 -> 2.1.0 (29 September 2013) +- made codebase compatible to Debian + Binary symbol export was changed. No change in user code required, + however everything needs to be recompiled and linked. +- Removed Lua 5.2 source + When building the addon LuaModel, one hast to have it installed on the + system already. +- Removed UnitTest++ sources + When building tests one has to have it installed on the system already. + +2.0.0 -> 2.0.1 (05 September 2013) +- fixed compiler errors on some older compilers + No change required when using RBDL version 2.0.0. +- fixed CMake configurations for examples + No change required when using RBDL version 2.0.0. + +1.X.Y -> 2.0.0 (18 July 2013) +- removed Model::Init(): + All initialization is now done in the default constructor in Model(). To + be compatible with the new API simply remove any calls to Model::Init(). +- removed Eigen3 sources: + Eigen3 is no more included in RBDL instead it uses the Eigen3 library + that is installed on the system. If you want to use RBDL with Eigen3 + you have to install it on your system yourself. +- inverted sign of contact forces/impulses: + ConstraintSet::constraint_force and ConstraintSet::constraint_impulse are + now the forces or impulses that are acting on the constrained body by the + constraint. + +1.0.0 -> 1.1.0 (20 February 2013) +- removed constructor Body (mass, com, length, gyration_radii) + This constructor did some erroneous calculations to compute the real + radii of gyration. It was removed as the two remaining constructors are + properly tested and are more general. diff --git a/3rdparty/rbdl/doc/example.h b/3rdparty/rbdl/doc/example.h new file mode 100644 index 0000000..33c66b5 --- /dev/null +++ b/3rdparty/rbdl/doc/example.h @@ -0,0 +1,25 @@ +/** \file example.h + * \page SimpleExample A simple example + * + * Here is a simple example how one can create a meaningless model and + * compute the forward dynamics for it: + * + * \include example.cc + * + * If the library itself is already created, one can compile this example + * with CMake. In the example folder is a CMakeLists.txt file, that can be + * used to automatically create the makefiles by using CMake. It uses the script + * FindRBDL.cmake which can be used to find the library and include + * directory of the headers. + * + * The FindRBDL.cmake script can use the environment variables RBDL_PATH, + * RBDL_INCLUDE_PATH, and RBDL_LIBRARY_PATH to find the required files. + * + * To build it manually you have to specify the following compiler and + * linking switches: + * \code + * g++ example.cc -I/src -I -lrbdl -L -o example + * \endcode + * + */ diff --git a/3rdparty/rbdl/doc/images/fig_GeometryAddon_quinticCornerSections.png b/3rdparty/rbdl/doc/images/fig_GeometryAddon_quinticCornerSections.png new file mode 100644 index 0000000000000000000000000000000000000000..c626d850800e67691a4f10dd4e9c090b51b348a2 GIT binary patch literal 80063 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

iN5?l`GL)IBB1-&g}>X>23S7^IxrKOsmB|edL zrM*Y6et;Ielj+VfA4B87ohGj#6EccWU>{}!gcP~?nsz~+R%>KoSzplIk>cx9ns`|- z>yv(O{MYV>*SPaiu+wz+AGFN9ZyV^?-Yx{ckjPWO8~a#*XV&|sUqXV>CS$o%*ms%)8yUAVNT|*r5;{HLTnf>-fni6b-+P|ueZp7?pF7R0d`9%v=*)7x11 zl7;+9oD|T&T%c|G0D^njw9x@N6^0!w@XS~l&`9I~R!%20Kq%fTzp+gbf&H|E+HV`> z?oEs59z4i&Rsxqa{CH5>S})%@m!y9Nb_qj%x3&d!(`9Xrp`9z%t zh7TArX-Om~|5D7hY1SdN3~!wI7ny2lqt&0|V8>jU_sqBsB6w#C0Yt8k@uQ-%mx_vR z)#pofwsqc)da0XL)eiVal6sD1#aB8YCc8IKr9pn<@1ch+WQd*NCNa*(jM%@eDbTNg zf%t#KbnB-9@!Bg&rmG0)ZF+^W^2RU6i$4Jmm;qM)bPGd?t}Z5q*Vwq8zWd>so^8MZ zwyYEVX%r7{_)hso3o#5AmRq&b*{Uq2$D5RU&nf9_jE^q@9Gey>3yK0;>>=fB_bF$e zfJh~>rWV$!FFS1VLMLSTd#ls{ z&C1O8fF%IOc#PG0(ng&5>4QVg$iQm*u5yM6?+*a{@#%Ldg4{$y)2!NiUksU`*hm5q z&}tQehBpd`Wq3@%RV-fWh8)~hoWj|Fomut=iCy`#Y$fk{hz1z*{na>$y(RP5LdwYT zb)}?{@cALXD#?x=&_IUZAP$ag23b^D(0&baey~^a>nS>Ae=@(q-Q#3q;?tPgj~$q# zBb^+3hpZz)D_?h-CAq(vo`Sg=;=iuE5@Nzk3VzS~xkQ^)=B}?`I<~XJ-wh`U=XmYn z*YWp}l*0Ou-gyyOXJL1wd{qN5xAscr(K49?1m)#zgE?0ZHqf zHTq}@HLd}!Ii}p&`T;-qL7l*qCEdK??d8@Q<+f2xx24e{&ni(?Yc%0Jtqgm8k*Y1;0m za)`9nU#*8@D4*g5C~lrtfDr4V*e0>W^w}3Gh}hd^b#^v*M1txa-ylR;%c}+yF6cje zUpQxekfk-a4$5C`@7R}RiC+ZOFGof4?SNXr8g$)clpcRdnSu$QJkv6u2cc_N1@0xg z?O)p@s3M*OCP875_-3DPU2>&LZG(aG+pharvmwP)_1y$0}C zOxEwu(MA~ltKpgGtMe0oviEnZ{nS=(K`DBNj1<&}r!+lg@|r{C3uP8YUpO|{TQ4~e9$CK zSJ6e-(mz`VdwcKo5^934%6iUk?*kx)SsJX(@0N5)!yzv0_4)v=UWMNuRC0E#s<}Xl zk*fSaDc?g_f)sTxs@gaJQ~F=9fgdm2_Wg*E+~Tiyv*|)83lahw8198caxz$;iJ7k& z5kvGoLqc@->B;)sABYfl=EUjgz0teNyrs{|36p?Cu|Z6cOtF!RfM1{IUXCrR$7m~! zjLy8NrD#vGOD>0U?s&9o1r$k0Trmy`E>28x`Ak0sEAEspKH(f50RCVxWBN~Y!ZlOl zfaIy81%7^>hBE4}Q$oM$W)dZpSDh*@SZqX*D@{hH-nkqSQQhTKu-{Vs;?-hzo|5Lz zKPb@1?jReowIo`PzP){-u5pRq*=opEjFFI9Zi~EswIK~j?_@1)Rq*{s#qGLrZ(xcq zmJXhB!1nONbcH+iPD}Y(I7!rjm0SeqSXV{{za9eQ!K5K{dr}?D@Z#=WZtD;KYJ7nAyuX6&ETx_w_FLGfFL-l4Q}Y z_7-1li<#L^isdz;*Qda=jp&Y#&O^Q-2MqW6jK+QOs=@ptSR{=nr9W5ODqX+#eq3%Q z6tEzuQqruW2f9WP(kX=!OeDui6;iu{2^i@%@o$VfxQ=p2(4Ywrc>Ux=G;bqFGPZ2q zu&>)fT@H0d1Hm^LR%Y@WY6FZCiDhLqasBGyyW%|H4WY=#{^`~dd*RrJZoy}_8{K+k zfn0GwLLvZw3_Sc~F7MONXH3_Q&<*#>4Z}a*R&hk9p$=$nvN!zo@TOPB0LBp$leq^! zevk+&U8sjyt(Qst+dmgq91YdYM@L=&-HFgvZ`FTut5e5PS>*tKqP=o^VB&Jy#yQ#B z4Cu#%XTi5&@&!tCbPksF?hhokMSu*Cc})wZeVks{^xrnJ5Y+9Vju3#S!Dbtt-fezh zTzpJabEn-}kb7TCWXq`g!leJMHMT1&M6YW#`2vM%@S~rwpfgK0^EtwPv2}-<1aLXT zQNd~sV<$qen5)=HRb6TolvfV2V{|D>n{mv`ph-#B9EzzY03{t@?CKq)$6uMf2LNBs zQ0C0BTRKA=TUFj8yZwg{>9U+$g`BYf1I)I9Y=iPW7Yq6%X+{;?Z#Q(CGe6+Vd6qf z%=_!p$gR$}!&yW}qZK5OIeHp7bB*ps&3OCa2=>MfnpPc*E&h(KX+F7H4GRb~L>Up~JN6WUGJ>rV9+YB99xRq_=Br`Vc|VwYWY@Nge8o zw8dA>4`MTlnp;5PR|GUhGnoLOsslBmF5Vx5oI+i`se}7TPf5M~F)?9&u&Q*ESWd}< zInRc)qk@zfuMeEo)MUCcg_M*6N&! zvpQ%8x^A9@6%HQ21Oo@@&`k<#{VIEm4bfI=^a0#fu0Ov&qUZkse~9lmWJsHj>_6c^ z9Hzouh4i)tbW#t15h^#zTH?=$5qy9JLw#=~V*da9ia~w<1a3e zDrMc16;7@FwljudzbLsKaV&EKb7XAu;2^#RYzZHiMebeI0H|*W57|52V)m1s|6J1j zH!QbheS3~4k7w4ei_;oW0OcmNrw(QpbrTJt*Wu@1$JTq!1q}N>PY|ugN$`yl0K8ec z)C8_aMF8*n1sFOJb2$_t0*|LxUMX4vOPCX{yWnMutZ9b6e@0OC=udj1bp&Oe8vdLX zHoXPo6eJT+Kth+@@exa(V#p{dTmn&d$Z$&Z7X2%dT;*QYoP~*t-j&eKJSIJ~96(*4 zKV9!@WQti5^+D79@eR(GU-*~b17rqW)CT6#rC^cddxtpJi{3%Y73hDFW6n0I<-kxojp z9+0g2Cc6{&N%JxOaXNRjfMumc9Cq5LXq79yjbSsWpxW5dZg4ZD%1=mKe|dQ`mV7e? zf|3rpW>_$i2_bDe)6l9X|8Kmn3kp}`np~Gu@!ZcH#oF}Y+mN)%NpJIF5v%pF$y?+Sou=sSsajO zxJ#zg@lwqZ@_-eduwWpHJ-$t2MzI$g|26fz9xN$k-<}wI1hW`PzA7%cw%K|N!>5{Y zQBHn5y&z65Vl|!UCNgwQ1<|#zAfx5*l*d#@wzQWzE^eg69YtPP1S@j4AY3kW5CagY zq!Kl`OrVOp`zN0_zy(XJ}K3Zpa!GE0Uj>JPXvx>Yn~oU>E#h*0&3L~fo*A@@0(5O$FN12BNJ;Z83$}Sma7b-|@h$LArkcblp9a|LzB;rf zRR~vp9e&2f8X0Xqx=d|8dW2pXDtx#e#5`M+0vNJ2OB-sdhh(wZZlfLSm+gH3w(3lD zt1Ml0K}$lc5OgYgDvvrZ;@U-9!ijx@Tg9Dg_TS;$uyQ;@B^cq!{D)sg?TJiXO0vox zBm}HwNY{B36n!5uMrBsh92jgZyP&CDV?wHfFC!<;yrn`SO7k2QZGac_lJ=dZKHbkn zdy0+9y=;dTM(a=AeVo`@C76qVKmm1EbE(dkCtN9uKqS(iZa-mKiz%O1#^>7lWU2_D*~prVO|^cuqI@pI!r;Fn1y|rg zKT;X3q%!utr{02!MJny!0=`fRb*9Ak8AOSG7W_B+z^DY^S-8B&gR|I+e==PmC>25t z7yfLzyt+8|N(<`zgm2e$>wFFbu+1)Y?)^DVoty9OEN?(YwaEwU$L#&<*YyxD?B28) z~rq?Zbm1?*RsSO)p+R>b4h{dLq%@Ha;9I5zZL(6D zzZ3_w9~@IZ(B?YFIq!;sNhTiOlUV=UBmG-}J?4@^E|qH=ZV3$r&G5((0x~EDLChaztL6@jE4?8mF2e92g8!m?7ObqC%H#l)V#gjcnWpI z1zzGh$Dd&%stWM1(QyYY5)UhcP8x+m-yWs%d3KK9#&q=X1W7Y8&(hgmsS1Jg0V9Dz zNBk0`w2m-3pK{H?rk8nCUGNIUtHM&0-jWc0xd1lv)>l0?tx)vsD;o^_V>4m|gK9Yz z=FmFA9#M)kZq0{^6q9flcO3fWF8wag=k|m5WJ}c^nX}`yA$zf_Bk2b{Yi;SP^6{GH zaX2yx7b;tUP32}ri0yO4CLe$WO3}vKL|Kv%(V?RJ(75Yg5teaGrY>8xQy5ae>5-M1 z)RT(>{x4w}2$obWv&WMJvB@>Bb<{z8%oAeaiXT6QAHbN^<@h9c(^4N)ybKbk8A2hQ zEcXeJ3LC(cfe(PereSpwJUM07T*z0JoaR0ni4fHY{|)b6`GIIvkOUE7Hito<&bG#{ z?#3?=25_CExx~h*I$S>`e9|Fak>z*#8)&b6ULei!cUEo??fzekD^^w0nc87B1%c^0 z*`e(ISUW^|Ti_p#5HTG@ zmEj2neYz(CEm;7*q6Rv0UC@h1d_4Dnq!o7aS+Lv&zBWD4%N~|D$GlM&kuO86#Wylj z(h11rjuOXpN(a~aZ+-Y49l@PV(LqhJqzwg}O#$7w9?WtF=+(zBFIVRl>QcuO+}Fv` z+Vs3^sOO;Bb; z6fGR&pVh4S>0h`)*yFqlg_Ba6B1Z$OOG_X`3}LML>PROcb_*4GG?u#jYSTFKg{5)A z7$Gd@_pczbZx1d&SCdVWG!KbyE?lkr?rV9C9ONjImf)@V{i6p;Sswm_V}HmaZLOzk zsYgix-Xs}sSF-b0-d^!JGKT&mcN*~GgW@}H$y`Ew>4vzX@>h1kysE9Gg?`u4hB!+>o}z#Jff^N43c~lqU5`Ih@DxR{IVwIF@U>FQF*+WN zL>ZD#o#BnJqnT@5->LeJ<^Re3Sx?}fOj^;rgb`v-PG|1@%5Wk~kOsKEWS63X6Qmw1Kos-~S=`%T%z@2v|VPY)Z zOl^=$iuV=UXSn1$SU+9jO4Q6##8B@_GI4%ZtMJQta14>bZ?6Z-rvz%pjy%`9B2F;H zlxL58tnT!BE{8G0_3Sup+3|y}wX~l;dAiJXDMSZsYQja0pBKDC6&B@m6M%;|bMNuB zbKT)wZaKKCjKs4DFnk(HI6FsPEApx4NOT865KIf_cBzOiRad^($_wBMT(P%}zaPDI zdqI>faGj)6l-+6xh#4Loc?0$u=#C6r$D?um{`pT>wq&n}bbOTdtLm-sExbPL5j*pl z+*}b6TglTuEaF&|i2n)0R)p!Yi;>=5*rNE{WWrDW(k zFbkRr20_2e65FhQbZS-*K#MhQAqZjTe7P^wx1M(#YVF+gi$hbctK<}~+&};HC+)v4 zSDw5*qjc<1o<+{t8V;MHPPBz*vz=8v23I46S|D4E?bFRkh?4v1r zo3YN@RP?tP!l0MhM&XN6Vy_SSTyuj#j-u`O3epcr=YEmX>DVG|&oQt6ewOm}`VnI> z(8^2eb-8Q}q(AWhIkaPPHotn3C9ug)+z(GD%AnZLEK07Oq$RGY_HUW)bFMAMKeC6h zGyk?M0eKEdnci>N?+x4SiF82|Oz7XX1#F%dj(s%$)W7+$%-<9ls??>sHg~(SofF`` zzgHy?u(Vf|nqg5ydDUgy(rptqJ*3Ky%p58g8|O%GNG;nSuJ)}%VXs}K9eVZXxOdy?VIKKV+i|QXNLcYKAo3QDY zAIfsgZaDn6X2{+EYG6Ul@#lkiz|nHX-kqyYC_G*lk$pJ*tDt-kVgOfL>|o~xYiCnaHHv}g8lgB#zFMO?f&Di`@Ig2RbNZ^_6h^6?Lj7hw8!T5 zews;|Td4Mv6CJ|J?KZX7+l^ZIs7v-}*2^HD1*mSxf@;xWKqsVphVAfQMM{7SO4Tac zIg<4tt`R6fKYpD>)#bomF)y8j-t#fSR_&4wxQE6bE)!Y%vvRILrh*!*ip3L9?C{Fn~ zGKJiw<_Z0CBtgX-miM4?+)U=`Y)7=1s-jtCSQ6k88E=fDsvywf5FvtZGMr0xX|t-o zs?*)xfhwU{sSEtb6ZnIJ!ZrRNIzND#PcC<*Y#1KrDM}^5_k`k*OVFP8amG)m2CuVo zy{!%X_Fu_;uvdlD%b?o(a2-| z={f-zED&EB7g=9SVCs2`F3_<6f8H0xZ0+?XTT~c2fzr+rTEXn-h>n`wXX0SohtGg% z6(f7kgCjd}xFJGnM*veSf@Y-f*?x40TMAhRpi{~9`4T#b2C!ZHx@QKsBTgnNuvU}C z!G0Rv4I^u=7%wmHc_1cU;Klwym=vgxmJN}p9QOs7*%T4+wI4vrmrUqH2;=@ z_kVJShWNyzee~h3YYtYxB_mU7+ep2sFS_{`apUAdb#l8_ zQM{LuZjVX!k6cb-`T*K`88Upm+pD_EkJ9p*=uTljde!K zW=`~Pk7d-W!NUGOTwx?CCb#tE_rqhWryyv(Nsf=;egp(l8|04}^h$F8|EB zWrg{#`ArD6X;hqhhLvT5v1QY*M0J4$N=iM8 zs&_ca$e$^)?{yVOeED}$*7Gg4COu24o-XyBHV01%fnSIVb*kvq=>+XdTq&0PXvRTV zub#)e<;M9^OpZt)pLK6$sQ_xNM zfCG!$yCnn9sP%q*MkH=B-={E4>Dm4)4Z*M5&p}4G9qZms2QA6)ZexhK!F1kcjeM|7 z^ekO%rxPZ6R~+WKLUtl5wAsu^;md{JuS3 zdqtN#A|#v~%lZMlor?_Wd7hmP9=A_x?y3=T-fwz=NdIf;o$Z>bS=vN7UX~-z8ell$ z8tQKu>sdN+D_50tazbFpV#|>I(>LB@Ftjo(-d#qR zV~-rpsB9K8cQs*i%=M)!d9BNKi&1{I$Kyrz1A|zay;Yj)&02%LfJmi7IJRNQEzALC zI|1MD^LoB@^v-j!snTklMRS2<8)kj1qd)vHmZF`m*bI1V*M>j7d)s z2?5WVi+P6?Vvm#pGyIXy5}J`aJSJUrgt3}+<&mXlG01plF1k0PmNEW9G3e(`?I5eL zi+$|oXX$ZH6ZtahL&u%R*El8)|QoZ6(GqAG!du||{?o;s05X@vAwWaT4@okox zY3{rT%TRWX@x+Z*X5hAsiRrY3h7qQC2JwgCT$K<5EPG)Lq@ECv^WkUFH$R!@to?b9 zIH~@XZ>BUez_|Rt3tUikG}QGjzL0ZPrh@yD$>$FrM=&@_h-A$vkcfEfv^2I`aHI?F zDhy+&8}X-=n{m5cz|mWEnBJ^s+x8m?`#1IkU#cf#MY|pNpg!!7K3Z)GJM2ZQgg|Vex}M`eFrgXrC#+Bwlf{Km^KL3dLPC(t~?k&}vTa(FZfGj1pWz{6X0oFx#=*56)1Bx3P1WMn~OVVSPd@>lh(JVXUJnv6Dott3s;W!#<-eXjS&0T z*|p@?N0fwePM#{=GA>fPdt2b}*-r)n6?f`tBhng>q(Kj+Eq3=^LEqs0JD`SPwQX2Pxxz9E# zPe%(WP!^o3GBZ!RjMDlp2-clRdvZs`xy|G}X-X-SSv)Ds{y_?ND^{=DG*k7Ob5?9p zNcYBLC7;SumsNnFT1dE;j6O`&jS%hV1|-JgT=UbQcakI+eND(m??+m#_y9h+VQ5s; z8HVwb1T8cC3VjmIn6C5N((Sf0V~(y^TRz$iMs{ue(nkQD;7t8w1{Bl3g*<&?$o?JU zy@BL6be|FWj-7?84|kg3gEXeIcOCQ}wHTIPKp!NaL4 z9wAY4)B91(IWBWhti^-?xP^@+>~vv~KJo>g&67T!{kK~&o>=tV*7y;$z9`fi7MAs@ z(L5qruD2_(*6Bo8VYE2hi?}7s85rBoQN<1f$r%MrFi4GPmj1Fb0=QFYvf(h8yb5!x z3zI+FoMcd;pPk~;VGTRHy2eXTA3U6mOtSktiP?s|E9Wag6{QxR_tfwuNk1v=xth^+ z6&(0i_kB=rt)Qq4W&7ouKz%X~CnEp+gNAT;3 zZU&n}G|BqMOhw${9$`STil3R8;jh5}2Pbt>&hCsjBSvzkg#!h54(ol<@MJejxfBM% z1DSWd+;8`}zNBBJ)T+(_7vn`pm-C8`w!HkJ%FbCDo`UFpN+q&x547Ow2tnMrptkC`Q1dV_~kSEqNePIy?n(nd8MjarP01x5#CjsbN4??r3 zAMerc3h7?T+eU*w*B!ws8<5Q5+6?_NH9vHsC|E?o$wJxFy%YP{B2zwp9LYmgyId?~ zK>o>nPE4z|%P`z?Hag8+Ctb`w*?b1)Oez^KWRd#GpV~8;x68aIgZ1uao z{~Z42nmWy!;js8A+}Ze`btT2amR3`NcoXf-(Gtjw#PsKQQtz`TrO4wQQ_KltZ$Yrc z+mTZf2xd)N$3#4=KrFV}Ao};pmfqXIgJ*uxne947C!a95oKMkb%_mc_8N^lB%P^!8 zH5#+z5U?9n2l4!SaS=NQKr&VZCxPM5BM&+qLa8TLw*l}1_yMVB(ecsocO{|jjwWjl z?I}BVcP+T5YB5OBb+C@T7N;0Q@dIXjxmtLScyB>{ofsQE;$?s(;O#;F_ev{K?bKUXKkXy2s@3kM5pp@DK?)?YszMs z-W2Y}QjkXe_Wte`LXPkQf)(G0`2>CWhzwC%qPpk;lcP7?9qCei0pu1MTr3{m?Fo?9U`u6vGMpYF3!su+Qx*+DUJL zNVK99+LxO!UW_zg{`kpBlDmY!b}tgs3KC+8Jo?i;a}t@pAbfQ1BU=?mQeMPB++DX% z?0yOgH-9LtN9f}Kz3zD{g0_j2cHKkZk9;F%1|FUV)br9yqC$8iXlL)1P_ynBD{QYT zwH)#z?lVQ6>#BN(Oc^hfW%H9J!h*5h9A@|iEPu`&Il}PoP`kE~X7ZQHun=p>dS)U# z&gMJ%Y>ygif7XUB{L*9I?z|C5&6r(qxb&TyxRyWES9cm`Kxd(8aZMaEnb~|2vjT$b zZ()AOF6>ls?7xQcQnR8(5`1HD2@C_Y|8QBg&wax@FMAKX4(PT+oXacwlHC;#_AD8ww)AV(&l|!WJ{gmE91!ti{h3yi@XQl%a+>>~1hFhXl#Fw@Af&Xy z4L@+QuqmR{Hk&8Al4$z>u5~P9&?({U2ZcY zJJM0C0wjurC(|UaU*J#hdzjm_8PeQ(KIeQ5L70$GxRQtVY^W&BGtaHio##Kc6{(VM% z?{xt(PV~j$VQwQq-SSoWI92Xh4f3@x?fMXhgCEOvtCU-|Z`ldN_%!OkQTnz~1*Nvc zIV!15aWE^kUJJH20)vD=y5cIg8aYX*PzR-`&}oAup|KBqg<_eT5q)f=6*@V!ezm?~ zc9-CCC-?pzLYNIZm39211;Oq=c<1LblnEcA39h&DL7kBhQ6+|Fkk%2|Dds+L8w6 z%lfN-rj4-5oi9-_-`hfsj7(2WP&q>-06Xky<&h*ziV9@fni(0F{081I?%nQ&v$=&t z_F5R2oMzUKm^|+f(0s#|2@{RIhU1_}kp^W`#}e#?p?)23nb$Nj8p#smweEUb!9vqW z3bl^!|0hlDT?zJvAWu$5He;-i<0;l`u|f*h@t;b*YT$fEi{jwkkV#&R)1sas_U=>P zITP({6gDFLtarrD52tLA=N#yO8^gMZD7FF?lptE)*z`UT3o!wF`acODX?f)!A=Y|^ zm$8er*K}?|8tSN2@NYWKSIzcv?r-zxtVGL1yWIrM4j)t*)Z%6FjjEF@WxHKb$e+sY z;x)#A_L)CIXclxMeG@XrO!b;)ReY|k&;x#0m||Oqpik{Zb&2iMtHL(6YQRDnsHphB8cz;jJ8SD4%VI3q?tAc2B>ad^M!kV^ zf4|mgT?QHa@tH+D%sJ?}_+MZZA9C_{*41jb{|JC&uYUtb z7|;v+r7SDOfQ5VRKX5A+NE->@H21sK*dcJ3|BAc4T0o4u*=KMN0jhdzPF*HEj^XmP*hW6o>QqPbPgSyU_m9BMbhTWd|F!dJgP$(%&B zJ<4Srpuj#Q9;574V2yq(x&t{uM^ZZ>(|d+(*pM`$sdegi&& zp;lwVWOAdAY{Mg?q2P?bVl_7FgVevlMiAt?5G%DwBiZpeywS!2VCn5u@}+W~RaV>O z+lV=2@h>efped9|AQSq_im0d41=FYUuT#$06gV_PmLq&doP}Pt1t|XeBj#_3>S37! zOKwHOu6A{gQiQhiofz2py&4x;iUcWPN@p^Edr-UT=4l{Q=oZITp3dDAMB2u|%k~GW zljpO#>wvu{U5nU3{67y=c|0qHc96|jm>(D9p6T05Ngsz~J#AS@-5ySqXE{#W`cJ4Y zA5FmVZVoN#kQud-SzaXvADf$FC4`}Vt(%aVPUzJezagkF8wfui(nxNlSwaKnAx*}k z*l;u2gFb*w>lcyQwg!2)>#Fjuhbe0?W}S@-IXAy7ZW! z2poq6BT-Dk78P0wE^y+)&$Gb$JCg*6e4S1-R_f@=pTb;`{rn5=-cO+pQVSJ+D}5)= z4cvAD5MZD!EgUudg2!Zh(Slv}qYs`VJ~MZU>HLFt!EQs(b?g4uO6@dlMTRDkzLpGA zKv3>FSRiY;5TQ#(=ThPjpoqzmPByYBmn5MmpvccGz-^P^ZWY@z2cWJFc0!{8+@6=O zBuGGWrC&mm_K=}fi=M%iQMA)s*S4#=#SD0Zl;tl z)L#Nfh_gt-GKhHE?BpVzN|NGpPmr)FHs6^wRB*IKUoC-lJ89=ys#1f#clT?o`GqG^Ewl+t4)3Q}l}YJg?3Of7Uve*n z^+WG%k^By0@Qc*Gr8s$pKOlx}0bN8MgVYZ4u-;7XLd37Hymi0O`vpcadqJ!RNg?IG z7Y>%1FffIhIPtvEsgOt#dhoyPh6L?}Vqz<($$=F004Y{lA(LEvMvn*~lERzt+IQFs z(qaL?|LM8j*S+M0HvbTsj5gt~ON$f*Z{TT-P741Jmx36m9wmU|->lUhs%84{$f9k) z9_~-iZ@)e-TW_FMN+J9QMKisV6qJ9W^DY5#$T<5iE$;F6KluY#0kn}8eLw4`oL%l; z0sUXXK_4RKJ#%2vze(d`&TB*rlFJ0Akl+5uh3Wn{VFMX?$X~p|fM?;yOjVPu?ZAGw z&Gu4$+kqVugf@|X;qQ+ohLT5)b1Oh@ATn&&B@+oXr4ND?K*6ObzC9rYQKrGH{ZkUm z79P}ZjB|yylcDV1I(QaA@(7`7_@p#<`vpOn7MKd&<&+@obn&LIQB7c2|?n~xj5UszVjzJXR6Fa1N~ zKxPxz+b<%+k3r4-S}i_-4Bh>axa3bHrAv1IYPhIi;N(tVkx}AN`d5AuEge+`j6sdH z#XOB8<|6yCQ*NUB;1q+XC>aLow1pUlmeHUWro&L2NL28M(inV)IA(rRpd{+;M7Wa2 zj-hsR4d$MDKY}%H6Tw0nYN;!Gcg?a|^USQ*(yjx$yVb+1IrY!ciH?}DlzRrxur}{N z|Ck-2%3P=2-DiUw zr2>83OcqqCfQ!vU!9lQWjhbfra)X;mCO*_FTsa$uS+t&wF89r2k7R_M!g+eqTEA=f zlZaG^JxE%lKW9f-g%>gwC#XV>GaDp?cMaX({W3tKY^Gpwp?DPopk%B6uj>qzN4B2w z#x|CNEZ=xHWyu8o!ZAW?ufp%Q#aRk)ncrI^ZJe5GIq(7FO9geX-iiwGR{dMKR+DV} zZ{Y?l6VwpqNjAPQtS49CeKuOGlq);FaO_LBVl$tW*ZW&6kl1MOvr2w!sK5W{j0M)& z%g|XV^}B$%)fE>d#%{RLU*`R|U(kvR@m3Q}$&YrScPrK8#aW@*Y!z0i;_aXp>Sh9}NOs z0tdMU@jP7u2VVmTj~`T{u^=*I>BRSs!v-94Y|o9Q14T}|B2ZbhP?l}AKYDEMPP$rl z#V72F16(F0&t~5X`Qb`!v+7fiqb_=rutGcTKJ2T;=$$kBLdJ`S(*>?N%kQVUw*J@H zfDKw;Cbt%8d;7lx9Hid?i(GO!+`RN+vtEA;9F*Y$>!ocqcBuT1z`_`M5@;}GrCXJ~ z@ZrT2{S$Bmr;r)1Xa2vO5~VcdKSv6?qAB|=&8wf$sX|6mFiSgXR4jRBshRDhy=zR4 z=`S1VHg;*AO`De%)A6NlC@-}gP@dro{OVY+_vH9(k4ZEBFT3^tF7gglnc#R+onVe~ z6Ju_U6ZRtPEBUzO`Qb;%lRA9ucD5U%lXWv=>BZ@C&v+NM{vAcGaHz)NwTj09Wmk`? z)IvzZvni>Yfsq%iBzbvAt*FJ<(w^yx24(pWxz9;w?};w{LZoT9dtBOOxZUN;IP{Ik z+oSR?TL7;^um>XHy?OoK6xyoDz50}_qwr61#Dh!!!j$j0rF5#B7kpfrK9W%7Bev+q zZ67aqz(i<|Owj9mm*B^Iqy6qD&I&oI+_xc3cC_9*f$lCEqwqhQLtp_MT*g-D?y+XxO;N9PPdk?IMYCu>n2g+pj9=0lcu1QV#|0Z)a!`ppB~ z6L4GRWG!~5CTK@ufGb(3pvp0vOYqycr`v&>O)lQP>xZ+w{j*3>X4EZb9kR%pg9Wi% zN2<8TaB&#_MMmeR1aaTA8Rv?V`tbYMFg51zt2yxqBvxy%}-=E2sBn?tUgMH}^N@ z?Xm;v@5P(c@8H}V?!<3zl=|zrQwHHgvRD(XSRX%e;C|b9>OFm=H&Im{n~N^i`dkyq z_MB=xa6Ej0iOi)?w8WTrMd@!v$KM#};BUln>br=0jM6+`%;2^TxAn^)$o-5M5Btns zuS6jw|OXt`-vGNQJ3t!EBLqS zAU<2ETcP;6*`>eujl#P{M8yPC_n3exQ2E#W6R}AB^E1H}%8Zt$uT!gc;@wY7FnQH$r`XeyHh7`s1cgR67xPGA32@ z4=vKXGWbp>vGnT?H54~QC=Y)752u1JTo?9Sg7O(F{<)6eZO+%40z%LV?v(CKt8QWa z{%)$X=y(b=CL+4t%pcNV`>U6=L@GH=HbPTA&|Lbtw6{j?Qjo-zKRe_=cw+E8(rw#G zz~;19msf*)TqW`>4iQYIFB83oiUB73S_L}e7+%lpU7MMRo5H+&sO-YXtY3^n04L)p zvHVfugN*s8XmW~t8<72Vr%l{wfHICGscYMeNa|Tb>9W`r}}-$xU~XSjzr=SFZn=)cm2wG0`7gUS6P6-UIF?dzhAVu z+>IXmeX?5^3{5$ZBu*$hN$Ndfjs_1GEug(v`=evy7^FHw}p}NBxc-6h1@j#K@0v^$ShfS*yj8l5cq{Sk~-!XRDfgb4YD`NSc zt zN@>O5fu4Arl;06~#sphAC!okJ4ZgHq3_HsdQ}j302b!Sn^t<(La)uV8x0~#Wt?w&1 z{3%gzk-)5zJ1IEVu$A;_yXL;%%3HO`XTm^_!%lIu7mlIFiSTuqA_1(bB02oH#%_8A zet!iNmSSyHBr(TvAP#N+i30M^(b34f41B(%^nuX&e-;1s8Dx@|lS$SSUJoC*u%g}K z&8olfH-te+8eNv$OxYlHvFAk zwrpdpMxd1vX|+-9Ci71;lRyqg7q#W)fC$1$4|$x>+D~SSmgF!#6>WR*-7hp&biXhx zQo{ITkjy(XK9?u+WG%oMP+%c8N7O2#OXDgM%9VVFc`V|@btW^uHRd!kgby; zCkkkg#u&LAUi~3f%va7c{*m~>6rCYjO};MN_5H-o*At?TZ5e+Xs@(|ul|F@*7Yx_R zJeAd*F4N5AQc4bJ{#Z$JAcXp0q;{6&#Ra2@`+6OT1yb})plC@JzlK*nd+hK9<*acIkt zps1}LCjCvQ1gLklLh6qJjjo`UX0uULSSVOmAO0OV9cpbXDmX%!l)guYBkZE5Hot10 zvKnwJvolQSjn^rxx(uhSpeP6zW+v1~Q7KSI-Zxim(?aoRbAu1kma5I#lc$0b5#&Mc zMEJ}geYF%9ykCi35gOM-%v8BVfsu>CuMv?g`szv(_|S%UG>}brv}|-p*>qRPFz%s23>c!MuF|Nr^%s!EkD4@jVG{6^ zNi*kzk!##5f#q(cvIvLb@I)m+6q(OicU^S1pJCD=Eutu6!1H$MPk6LQWjsP?(K`rq z;Viur7}9=L|6Skg6n9IwBRa!p^sRXRWw(@;IChB1*Fy}4AD9! z4WLQ*8Hs%`f%^(Bh67)>fZin_?_Bs7$IkA{9>%)&46H}PMfW6aEmgQOfBI-WJ~Zhl zEra?c9uMRnLO-+6aend37TFK=hWG@q-}%3yZs?g=-;FdxvM8QY4l8|9``eMvP!e$;wu{gPYdTu~WO}I_ zAYf?6Q|xjmB(Ze`GmCo2(<*ADbA#Q32I`?DQt=uu&n{R?!MG*MmX{jfT7S=iQctk<`)@S? zE2XZ!(uJw1%2E}g?}b~MurQ*i9u-li9C03}Da(=k3Kk%|^Cnp<4j#n^o=TtyWCn1v z$)8hIkD7cQ{q1beg)w(RCUyUvzi_;RP=GO&$rc;Of1u_!oHqO`qc5Xb$Nm^IgYn$i z9q!-RK|>M{7|PiVdG~b^S1&kcys%#TM+cq79r4Hll&q19NW`QW>0K1odl^3p5|z6q z0tCyiW+}tj3U^*)=yv7?Tf?gS)spKiECR+#5ft!7dg6y;#4>jm`Nx4W1;frFuEHBua z7bNqG;l0Y+i=A58UKH+M8JRgQknSEB`d%;oM>59($q1mwb^phK6ZX*B(i=PSbt)dW zlB5#wUc%D9`&`sslSf*yAQldyPEWUC7I)WJ;qsTv@^Z^{ojmWAi=6Jn6UDFhNJuI-Tv#SUHAvrIQq&Gm?f24r_+W-IQ zfjZ*G)qICL@;Fs(_x8}Tq^l4=n?C)MjB_9bmuZ|NSYjW!0PB|~0q6v`S~}Fc$_*pD zU(qK;c{#HsML=3U13wg0P|DO&6D6r8unfTvQQ2t3#_J2h|a0U4rFvscW-i&8wtS~yj7Jt2eLm96)MLjgY zzO=3b*?+6a>wke^sQ(kggzM1oDn34D+u*b?&A}I;L7>9ZwplyWzA^#?FaO;GMMg$% zNgiMXP&OB7g(U2O{hIj3ke?*p39=UYdo}?ar1|&)5hDE;<;Dw6dnw$<$S{cfuUrC# z9l!G7-%R{o~$5Ua4RQaUfyIz-QAfSw{$VAC%Q z*qi>mVrw`+i$tnin`T{yQgmwwKsXuS%xnC&?ca0(G!yB+u}Bx#11mXW572)TRA3m~ z|Mjq7d2$x*_A6JaE8xxSu?ayzH>J;E!b*;g zj&;KxFA@Vt15hmBjgBJ9%#}!w{Vsj>6?F~_;+20+ilGHA3wQ; zme6-DlaptFGoBfv?nz3j+8pEKQiats6K}pG5T6UaNxyWibK?SM@;z-F?bi7IwuZJp zwdW{cdWDINp~)fD_D9fqerv!JqD${!C%R4Y`wx6KmFtilh=^dC(`fgwdiT<d1b=^ZgM%bbmMf#SlcSbHWjIJ3+a2jTm;+tj22J+1KYjT| zowipC4-RxYT6f9~lL8t*0;;IAg7z<&13VoFx}WCf=;Z0-0v)B0%+BfMqJC&9i`+}A z$gbxGPvX^DteL;dJ7h9r`AO$wa1cY}_*f}4U(FMXC?Srqn@^(gk6|by5M>qVcxBaV zodz+m(Livh7^ok2o;yCwIA3LFwtJ4djjIE1DG2OmW*(Jmd|TdYw^9hx;K>z0Mhkia zP5K4`K1Lkqn!bh{Yy0H-W3(mk+tCVHP5kLhB9?2XA+(dg{>(}}4&>|LgvcOCMl0pu zU!q^mrF3q7B#iSfwlk$b2a7-g6T<&{LS(5f{&^yt{dqJl8;*J=)si`oP=oE`Z!A(J zLHJd#*}^;<(Ce3l^VJ|1!arKYY6cUkhyQNj*nDqXzcr}k4@@_6Y^Dnj0WX|y1YM%m zCp4F(t_rlx^801{#EGBfY(Lo9ap6a4rcLcb)yb z@ZFTY3m2cg*jFZXP#o6HeZZy&+R&*%Ddzo$x?n;CKd#-DO0gVgp8zO@rt!%yC{Y>_ zz#78%l1qevH2}NnLdC+h>uNtXDpOc={cXYw<2Y)CybcmkJq{SR)sh1HvQ}VR3w1vF zutc$iNu=l3h2R4Y0~|z$nKH;>jc{O``(0AnzvermtaeuR_$J9T;Q8fdRM|s~w~pQY zOLze`DX_L+#Ug}%tql^Mf!NK*(}x=@f*l~fKB24USr}2fMGcI45-Ngu8O4T)$8I^A zos)W7F)WjHjhsT3~cXj2+Z6s5w?M^AYf>3C5YzKA9eP{n%*(aD4`7;J!JJdYwx6eJJIE4Ozz-RUnWQ)M0GO+`_>Wor!X=@ zND7QBcxs~n+$Uv+;0j;}3inwGk{4>iZirUttDKSlP&ZJUri8~XSw z)>dr}n#5#CUbkgsstZ9P!6fH_XTiumM-%%aNtvK;si<%`I}$(HCx zW8|25h(=FYc9dsP*0o2sUUKouWAr+z*TgfN{H2CjI}HH~`+slW-U`kHINr2YTC# z#5fn)L%HtI&v1Fy?Om8q=lAqN%2JtwP(E*;X~iAPB&2tV^Q<#FMlu4A9R`BVnk_R~ zp|b{gX7tPmEgmVDQO}7Kf7r?jHb$oP{vf`n1wUl2rsunLamiE{sz9oF4Gd4#DuN?} zZ+73gp!Erk2K<(Eq`)cg6=*Tdv{Hov-A5VuuF`(60%e~7;kWw(=D>$U`^p$>Av$tA z%K_A#B7x$i`(*+coU2dzu{l~j-jdL~A zNXUQzT~4bUVZnRZ%mL^&wRs!I$nlA%lwOGT^mL%y^wC9V&T)cx&a%-}$ndSVPS3n^ zI;GA;)+nLtd6f%J-q3uvm&bhx@4keA%*$Lwg)Epab8$m&Dl2|Db3WV-@Oh>fI`}No z#D0e!$&K=hlvl~S{rnH@C%w-TB@RkiT-$c3AGTS__YyUt_eqNtRxy9JWg)Vh^MTzW zN{TsZTZ|#@R5E!W2*-Q*;m==;-KKa~4VPKlFS#uzVsiYL;;&lq)<-nAg}wX7ji~NE zJ&9^&4`Y(90(9G0=M8K#?p|tYDV{F2sJ2lQtAntLan=cTlkj^;s=KZ${@Nl;ty`{~ zXcl3ZU8f9$tk^H=y8Ax3ns@MLYtE#m=zL0*Mk8v` zeFyAq{xe6lF*M}Jr~5$Kh!iQo93G)M(k|L>eW&uxp4D$(wxR=i-7>$kNrhiXKVI!D zt_NJNp7kL2X`j`|m1{q;u;8{4`a>8R&JjqDj?%4(>HL4t&eKKA7U!)pXYpK_LFO&*$Q=a@7t+YIs*U+f1o~;5Qp* zlFUrsq~-9U>8!MeLdt%mU+u(LB{FXY*nb85qyvP{5_!(=2lt*i))tgj)TBD3r90-^V^jTy&&Z${+V^0ULWB!#NNi<>Gc zw4s6~O`r;93G*+haAg=x2GPqM*b`GYYg52^$Ny|Qw+?kO%3Vo4 znAeWX9wxY)mokk@=b0`2AgH>2nH9d@(ZiNlE@m7yf%Pm+%eF+e^_(zD6SG;!orE(obMz~6+&C#UORrPYs}8_-_IzoY&*ge%D> z*vFF#zC^$Q+|MOBT71^UpqcAk8k1M-7Sp@S(Mx+f-dMg=%XQwQJUOQH`US9%b@EXs zs9D~nwpZ~??sBhWuSrvFIc^1vri1eRQW#bDfO9}GRAUd~K)oCwp_sb*g#`Lwyt%XQ zmT$-!OG=yCVUK#9?vxF9e;=&_`N*o$c}k_R3I&6cIMwR*JN3)^w}8L5xq#9H z^ydHPK!$^eyDS@udL=!h|u5yXIm+E@|+3*M@^~RiD_3aCz z@{?`I0Y-5^R284&KK?|Br_3w~Ygk@8QZtX6c=S(l_@xXyeJ-%Vz6GxKGSzT{^)6{n{`^-e_Q>{TtDPCGW z_(?g|;risMA;N9~$KWr`TIH3IN!XlAPl?2{avJ{RK^ybbC+bKqehUNMnY}&h#(roa z$qQ?XQhM06N~nUdS%J++X1AH~IJI|HH;cA(yl`p+4qlMpblUA6shs^N|5^S!{}-y* zfzt{ZsVRA2o)3^N3?%7(Dw;m&zKdX?UhAbm>Gjn!Aq#768Kv3vVg;0a7xLOo;f`cpo6nQquty^1eqIA$t>Si*ZsW{QM2C=$Qx)`nXWI%SILVMJM6IMxzlI{ z_enwmG&)M%yt~B*>y)vE%9=Vp9aLIzl|;*Wlxi{$AyMik07tQp^gqKwz^5d2p_(^v z<9fF|a`Aqak}XN`Q+ElgeAH)a-_(vgPa;i#3&svfGIU_Dt-pUbF078C0Dl4}a@ zoKgybiXyOw(tR?W?3;nQ0wTFO(l@~_mhIp!ZZ}}WKY6hyPHiVW zb9eU_zMJ?G>5VY~zadrZ=rMGkBy~2OqmrJU$Rn3_A@}ADPoa#si25OE)OWZKd9>w7C;CSE8BqL-PN5N@J9-|}WGw=c7Smu*_sHr}=`_ zy{g{(tjFauz(Fel`FZSWo0;F&aGli=-fmh;&La7l_yF41$)0w8+T)_L>XWLs6c8Zs zcT1AN1Lo)!Ey2|zmE*(Ymqy2tGXA!n+A!Q6z}Gv^VGj=JNy-v(1hf$f3`8lA4>2h< zS26QSdfX1+myrWg&`&0E0OpW}8iLm4 zH_>==m(5emyHDk7^*-T&;KoZ$ZfRznT~0I=`%LGz3fY+xZf8ZX@k{OR<|jp5>~ zuzeD_fYqgvky?EaNwr66l z;2vx~hd0BZg-!_6@8@yLhzlp;;X>$PDX@dIXWDy5hWHvE$#y}k=cW5u;eDsxU3N`c za{101XrU$zT4BF+{iU`sDWapgPqSss7n31%nR7)}ubOMHY-I2tw#yu78~(yRdE*Kq zrC9UE*g$+!-;N?bSL!&&ck??6^hIt6;sfkqc)7n)6sKUqHFf2a?o1HCS5BsBYhF~d zWEoI^vgdYvBWKZ^nr3gL_~I+nxinxSl+43eCFjrVOt9~;tO^lCfkv6iv3FaG%qN6* zxncqrpvq+`?*Yi(e5MNIOU#rA&6IS8PiX}M=PGT`vW;FY2O?GwD3lhPFaWISW{DaS zF5-82=iaFjBF5AB8W@mJO|-fTum^PBaHc|lN-+X9_Yj#UWpocC&-EzrD=wuOC6Y7_ zD&v*&=jGd6dsA1HS_5|L48rl$3pT~GBuU(4gGMs`XC*FT8gGT#ulAQ0z9ur4yu+bN zil5ci-zw_fvHjRiNr3_q8`q!+#23NxMo(Y|gKdtPo^pG?m=ikKPz-&-sB*6>tQOl- zv@#T!P@@SpFP+cQ_0JG`PgWzgYmL%O&6{;D6PI-JM_K9g{!((NE@{W@QG2MZSxa9m zj3<}#!0j_K1bmT*FvUj_Th1J&(leqVZu4R({kC~)#v!^SUN?V>A#a=dH}tZK#%so% zQkO{z?>_iR^&c|c3ZF^v1~uobF&Ui8DW(Ixm^x@rRdMbmg&JqyKeP>v+m-wK?c}9i z1GSH8wQZ{0$OG(`#N9$bEI15(pe;ndbMBe+NGT?%-d1ODma(Zhw1lC=3D|brU~mX| zd@Xg(rk^1*R3LhcfW%lHs@g73*~2JC6^XxiZE~+v_%f&_WFP!c24dfTBuqM_yp}#- z=N+9drsG!|NS$jVjkEei^5!!t1pGy6(1!aF>FGPgz%POnJLeulUy^v<&1gZDwM{~= z`>efBFG__Q8S9jNfI2kg4O})(=Pg^bQs0G$y!2MMTuOSoWnD?ftHTxtUJl0>&uQK=6Lfy;=~iZRjmzuaj$= zUlSd2-2CcOPjy@5A@E_r!f_#^Xo$O@0CpI{)i{vfDPR^SK`qrs{*R0x*+SD?cpaq> z{=VYSRqe{S?udD~8h4xI)LO=(p;UDB)Qv(aL0&_2@p#rFQ@T8eSD&vVM?)&-e8X$D z`Pzmbh@b=@cpW{^`~q5~bODDe2^X*YsB20+Z8x4IU;D_4&y{YU%_#$AXRq$mPguVH zc4*WB36~Ohk++p-e3sR5C54chSL_gvuzkkgXhnRT#8FS?fRc|gQ;G{!rQ^N@8~|>j z@U7_0iWi#w-r-@R`K&27H+hv-_SUe_CyuD|lRoh;x3d-54Hr@VdBZ|)8$-p$ zz31;2yrY?7g5OEoha4wmzvSs`WQsByL%sNkXS6?Rjz$Sn9wp`>tM(|%Z(A1wtF+=9 zy<$IiUPz#ifP zXWSIKPEB%TYihn4u=kYYsa>>`oOxZWP^0LyQCb8TtZ1CIj5C1|!DEwfifXcR4cx$PsVC=5;$7hhY_SjBNQk8U4j-e7fs03N5D|j+T z0LCD4GgdSS_hZ{vEx2rEKB6i_heR)^rTD1I3dB2|4V^HcF0iDB~EIY7kUfal6?k}L#85L=%3v6Wk;W_+{ z4TdAb+0hc-#T`w1pt!f@Xsg48Mp9S(7meH~MVI=v&}kz5Wt)NOg^o~#-Pj?X-1aqj z#+OOh&=W9uznN1Hf#_~(H~k`Wl=AE^IEVb+!CY2e!GPdo+cLZN#nfbTeKvimiivNV z7^9(+u^R21sCmldAlGl;%1+L32aqhj@308yY|L->kxX@23x&fe_Y z)NDpxxv}k#?|W*^-`xEDm#&8xWGEGxWD5wW*hMK^P~Km#52d=^6KM|Z zTO5uuGc_uuRblt(jWYjU$5(rmQwC@*ElWqLGw%r@94E8LIF9U_OAkdWK6K4ny|WO)7a+q zt#@|B?1>>$semLdqzh%#K;T6WSTp)T54fDGl7a?Gp{Mbknw3&tq39OgUqwhv z8!CeiQ%yyy8;2=h1L9qtfLnhZhyWMw5S`06G zK***`J(ku?E$q~N+8gs>?ArwVL~JMIvsU&YM%aMc||N=ib5!e(7cVLf<8)M_s^4 z**YrDeL`Y2GNdXf<$+vQ-|W2`TgT(CpFC7OhQ~HkYxNJ-NLA#FvjVpuhXDCg6!7+A z(z2~Xx}Tt<`Uxxye-!nwk(*zaK+;^i0~N2n`=Ve>_byfwQY&WFfZwn35+tKG?>{kd ze(+wg)Nxiq3{sOWx`SPAzEHG;3>4`EHRPH)c_1zC!!SaU))uMZtaaQOkMVsP z27e5-tm59p;)cdpR+vZf*pAef9w|e<2L51BoMFG;clD0qmMd$trsQc7NY^l1BH5vF zj504s)}RLuvLAk@oK%KT6uFU}XxjktGeb>UdZ5@aX_Df%n49zC$GMm4M(O|o_H5cB z)oB?`W>WUNyLy+Dvk+;fSYEq{jq(L=|2us;{qsAeQmt+~H|@{+7h^a5ewp^55s zo_avu%m$UqD{83Z{ah>Kfqd=fl6c(LP=p%3FV|2;%{Rzu1nWBZ7Y;Hyl+Pa#y9^GR zqxQgpt`kM$!$JsMauwtN`z=$qFfhLZ^rn8td;P2sD4!NXqFRf3u%^XgeXQ5rGPuC$ z*>0_lo0Rv%^tQG9zH3WBy<|dF74#qNX_ALH&;9!1l7;rMmRTG5{R#U9OVJH7z*b(w z1QGJVMP0*NQiItZQ9G-XKgL>ge**F64TN@GhBS$_DJh-$rcD1l+`ymi*y+0Vuxz(< zHTi#P`|5`%pXgmd6i`tCQ9w#ULZq98MUn3A5Rj6VTv}=AkXTrnB^0C;mQZp5X-UbY zms&am?z_n6`@Q!Mxbl-aOq@A$<~h$i^Ul=Ap`8THXE8D_mq!}h>!<7aGj@O{Jy(T| z#y#Sx3gW(PS+dBFa1eym;~&!`d$xLcmxG&byXA96p!7~S@^F9!o=Vs$TrYp%IQaT; z<8%1t_T6p)k+5l2a4L!-y-#J0|7Juj$}6zUR&_X)Aui3ZnJpy-bEoKEVdGyXWV^8Os~9bG-4l;NP3Uu6-_5Fe?0qb@utmb}w5 z;n7c?rMC9N^RqF!9+6J#$iuK77BE(pRSfdxI`UvYPoO3rV9+=*CW^M2slRp|y950V z4M?xv-$OD)%5=RJEhc-sh_R6df3hnAJvw}E5{6?ti4Yx~%W%+~;v~|k?E-x{t$a2U z+3+<^WP&snWxjmDA8zj1t83K(H|YyMX?}4kHq{t>bdNA~gcEwA#u;q|F4SPg0wV#u zh0jJ`47z2Pl3PZ2n!R znXF&7_*jGZbJ_`Y_v+E=Wg9^N4zuBeszVwxhHP@(`f{~t?9)YmOPlJr;EfGTUvPxi zPl64WO?QM~Xa4ugvFCXb3^Bu+QP)v{6i~Dkj>UmP^YHwq-ecUHLbQa06mTlcY&}w=y zy)6YWb-y$`R~13^Vz9d-_zEWmOm9J_-I){E3iw1niI4OLPU<8J#UjjKH_}ls=t<01 zj$|kgM%H^YGDCGysmi0)wudx&l{psgge8*U=6Vh6YT&Us#LM}q4O(pzgc>LAiH!8M z<2pCt4IuETGt-6I>yNJ-rF+X$NAo@{WLZVZ9T05?;my@gic_1h;j2o%2Gw+Jub5 zAs%MarVn(2-J}Y;*%b2Pl(E`*ov3GI?`3arhrna(81rAucA2&p*rW`JJ_D-jNK3<3 z2Vk5tI*IKgKULaBa$+vCZRLcp$s#k3(vFCT!aQT^r7x%iLgh9o;mz z-fp$_JR7E>>U)XFp#r=KI|?xFlf*&Aiso(_*m@?@@Spe1gmV)kYs|Fw)jk3p`{+;* zcUe$X1J~JnfVXu^ag*?y_QM9&RGA7xmk~0H`_Wb}Ox3W2^d;=Nh887V3DSNVbd(_& z;$1yH6^%+=xgM@}S;@6PDuS<#y!c@^inj~lLnk*z;u3AC`ts_on_~y18PWW}mS~3e zY(xEh$6@k?Y_$m~&*xmA_bevIt>B<84{oAM`c4pcsBnq(R9ln`dW``_2*yuy@HbpK z)0=Nfq9zgG7aVc|y^{>5zwDAV{dSFb6Mj-7$?R}W<2Ju@YZ9hONwJMo4MNmpBSu7yK9@V{7TDTi|h3V@icYM-vHIhP=I?m^Z*GH0s}q3Saxg#)b4Wd|_8PN@k((c6CGW-(%5PiD!9 zZd_}{tUvPuK7@H`sRu6uW--C0&$WvRCNJ_Rfi&5EueWVvgcd+hXLHJ5uFOGC$ThFW zIA!LRNjt3Lk^$hk@_Y9#BNx!n86aX9zqfWg?4_YvSx_(u?*ce+eJ9Y+>TUMGf6cfm z(S0ULmy1ArM-=5!lc+v0VfhQ8e*7wpLYYT#Z6jpT50DaH{2&*e>F2B%J(2*WEFfU^D=c!&Ejl(IoGO;Z^cWpVI0ew-g7e&~uCAc>pTay2BKZ|6Q)9>$uJ` zr=--0`LYna`OO2O5*}y0@^( zmPEZ5?{Bm#YL>`ewxN0t*M;(twnAUR=fPn!e~NV0afO-NTYS?Rsbc%by5WLfS{MFe zH6(tSRXlp?-$UJ;9i}|kU#j0DuogLpDYj%qhMYi}O7XSSrGDTI!|%cus$qR{OIl5z zCY;S%D`)X<0;w4g6`s(^^e=muJS!jaDsO%K5CEga3tmX&_)Nix5*{deSB3GsLbT^@ zlttM#)3-9?7^(u_q1EZ(qN=7f_aIgI^Vf5)wvX0akIIGy|t-(b*eH0K?H@4WpTnVTkxy1 zliVQ1Zo69yu+%uU%e84k*c!`zF}mVf$A9uS&lp6xwxFg8=Vx>mX)p$!R@9#4{%sZ0 z>UrW@FYs#ps46{#^ZAb|BF8)VK|`tTCU2^H{ekmydlok!%(@-E24SaM5pPLz(}U@- znsvX=$+vjB&cU;xW?U_56uR? zms9S_isQ-6asb$2mmj zv-M!~IMVZBhro?rnDv9B>)bb>Jd@4ffSyj4OXsGNB`G4u?Y>#vy3HQq*i=2ucpYI05mu_@9B?!j`9j-K zso|0c9DvfPn&G+#>BJv<$ScS7uwxt!`4?Rq$NJe-wuJfHx9sfP?j!+f5qchDkw><% zg7T5i^@bM1gdzuDl3d7sW6+c2{%np5?1FN^Wr<@RlQrg2z8qnw@#VW&5NxDvV7#yV z{G@C){qyJ15=P_GW4CZY8vX*EpP}C;##V~F;3;LvO4+5^cvO7TtFMJwqK{>ZpW=wZ zlnp*rdkMkbYzVJomt<^QK!!4zGuP3U9BAiASQrWo?w9>i<#lVCRfPYS*E~0r+hq5Q z)irP{HS%ou^+BA#d$+<*X5qw6x=fkuHSy{kJfU#Q5t3PTv%q6-w6D+)Q8cMBV)=MuH*D=TpU|xmOh*(gG7k=R?t;a6rNrSb;;@)+A4Dbyq z-LkXD$FV&6iU*MZ&V~`|hF6~cafgd_jopmyVK*;(&5L!_NW2A>6FmBkslC#%Woedk zu#2MqDv0e+uA!AQN_oWF(Xc$>T|g@|-Zf~Z^^?p|(P#MV^2izaC)V8G7PQZCl}WY|vX?hqn18cI^rPF~R2(R}0m zg?$@Nu;*U-6?rF9ycGGek)EG&pj?-0#GkMe>7(G1x?q7kW)RLetFCdc=A_o|Y0o)v zJ>$169#!=w{I&MRRv_9c6>{4E8>fH?NFd#oti8 zX%KX|>lcD>GG8vbYAhPaUw=$^$Kjghgr>&UT_f!X002ce{FJ>c6(C^%!0JI1S20AUS^#@Wxfvbu0{26~~>nr|x^lnYqvORhPl&U3X6^BdsiqJrJ3aLW%>x z6HcvYp1G=b%C@(4KA`l3%-FCSkE#JK>9C8xaC`*AS6$V!NGjs@`;R(=wSK{Gwu}tL z+`qUcCk`AI&Pb78YD25=K(}<#xHy^esrt~CGTw5cUmsWQo44ft5hwpCGqgDpRcf5n zhFK@GS-XM~fJ4u@rmy?NuF1^?^{*T8tqDJM!xMrP9o|2smT5=5kAA+)0F%5S@6Vv@p2q>5|(du@*`(*~?cI*GCLm!X}tgY7}zFOo2 z@x%80WL*1tx6w(;wQnoo_32abJCdVjT-`RsC?q!6K{smP|5K)P3M21a(U0b(|;& zBx7rT&T(^BxRCwTjcBVUx@!OO6ad!*4Y}U(eDD&{P7Z>R!)|bK6m?a;pPIH3F3zk3z+UTQc2^Mlj*98N)9J%r z>t(fYyghcs{?nnBrjD8bXE6 z{TS1GSA{HaF_G@>RWUuq+QPuOa2}=4hgNZ8ms}W-Y(c1O*e{DEW=M?jRk&Dp&~{JL ztaAo6V_u+XiZUz!r4%~Pk;$U`~l>41a$L`nc} zt2>(B{+~vjeV4DNX`d~3jw96saUmS%`2w_L6TTAi8yy(MBEhdWuzOiGfg*fK(df%l zrhzo_Cg82%ia={WZ6)dMAUKr73e1Z%#Fox$G>v;7=fI%Rskj%uLtux5oac|L@##47Gp%iYxx6Mj^b)#cJ+sC%Cp zYe+jP%Z`e!GD$87urn`dZRRt%uXRK2;swSLq?7Zk=hAY}Qn0=oICKVmd>ZL@6$bq9J`oht{8C{`D^`>M zt8!?a0t~Ngg-p|A1ZsSXChMwP^9mt5S%o<0gnl8Yx!F1J$0+~W>KM#dK?vq6snT}k zJUw_L1WYtNhVi&=Pn`lOw`iZ;b*?c0znJwrNDz0!=HNhxkd~8R(E7-RrYGsJL^vn5 zb>0jFqKV3b5Ods!vK76w0ux;)(5Vy*f(?3_nLyH-6P$PtXUoz9q8r_6a9sFGA&MY9 zzefC-o~Q338( z&UqeC36o)gc_C3??eD=u+o6g1$8~0f^V#zyIUx}wLP{GD>G?O0MfY=~fE*}GIpb<@ zckuW`7RNHsq4Z7Ns)?WPr@R7Y;G#d2^k=Z+%J;PFQBQzCP)a|!#OK#=#|eAN=qu%) z$_kaUh=Sz-{_J`em?>4I!l>*AV2?DR{i|qIK?)>1w4;T;PscxM;No69&06kIFjXc5 zHI}ILt$HJGc2p);72Wc%Yl_Q{}I^nL!+@!IdvxN-1wLp88P! zg((j%Gdg1Q_9?-y50Ezej!K=pBCOaNh&%lhIH8Tlu?s}ua2Y_-lP012%*R5tD${S4 zeXM#=jinhwb%U`28xsR0Wbml6AZyX`>VTS zFf~QX4;R0LkW&@`^Bkpz0(%#uk?WOn)-il7h&cV$Bg)USFYi&zLKHZHw`b>tnmBBk zqFuv}QR!l11k%;_Wd9KbhJZl3>JRjzK%zvJSRl%+8y#j9?^DL2Dg={?lLGiNz=?(M z`ZY~~IZ5F*7YB1~%Y(E+nlTjI5i=c(__6|Bx>cXXfz`A>cB+6Fus-EBFC7!vo_wN0 zXa2PKDWDL;?)`L*xF>@UH4&?k!$@jXb!lAl_8zZ`=4ORk`3tEBf#Z8_XNQq&wi!l! znWN8Xo(miG&2m(S#ARdBEWuji9*@aAcgF(*zb{dcds?L*eMx`O_EnDp)ANxLa^(l# zNpyWD{6#ai5Me{9&vX-sRGQ)BXZEJ1H-$5t$F2N2$uT`KO#j%itiS;2bmNhZR6czk zC8HnKd)89bz6ZS%{`+M&ZwjQoF`W&cQBMCh)}FCi=*TJ!65a9^tM!{MJ+BRs%%mVc zn%ktd_ErU_fY984G2JEN=`a0@iwDOS11>Q9s6c7%C0^KfLG|5L%jHk;LD}3dN~1g@ z{zb2eT_cvlw`!R$#Ni8W;gUDvc!-5OxS#K*3v0wZNo~qQ8L9X_H#|x53pnrJat4Vq z%gg=Sqyq!eRkjlC4N+b%8mRg@?opAaco+53CVF(-yW_KDaQ(W6&>tc~TrtM4=j_+7 zFH;qOO&Z+edrw!Jn_=7s>y<8`%x27b5W|}*b74CyLf^cU6a7_<;NSANoTg`h{^WCO zCm?jWTnE@yLk#@W|JPEuomNcJmvNe(E~|M<(7 zwq4$E1Sj7y4%R*t`Lsy$TyO6+FY;&nAt3v9P(8m_F>H{8n4d#Q!_`lLuQNa^Y9#M~ zWZl0lyms#A&KZyI^4DaliS^N)C(YtWf_-9s5xnfDw5TV=lluxTO=LLE;V+&d zTB$(`Nt&L8KMZRdK4>4xEFNrCtCpk^FNu64*GnAA8Ck5_nO9N2wjmcU?YFhE3mbH^ z%;i|jkjd;z9>drv_WBp@WX9%iYVH&pJwracn0*Wv7|W({Q`e)03Ol7*gcrOVUD>S} z6kHG2XKTvc$#~+W=e1mMw_rjhDl-!61GbX0RMo@Ea2bJ`xUoQlW<}(}4i_#D`<+Eh z@?kKE|F~!3q6UfRr&?LASd*i+xdIF3iTi zvE*jE8(UM{mwdZV#quUwea)@o&={HSd!OuBj_X<(+?ww6{mGi%Ry4`3^>Yv_UPJi# zzbZWPTpVYsB=+T?gFcEUT&@e(Q&Q6tMeE3~yPgyp^u4lUKK{_^pV>sN4DaGBJQe5h zu-=hn#k~&0x%>&_dRJq7WOlGJA3FbzxbYjPYmRe6Od4XGxa{#{>w<6TH^0i{6wT(w zNY+fQ%E(zp!C%@=juR|WB+t0+_P_yEDD+odLW5cg@eWS~^Y2q{QPyEtPp5KEh zbbEVqWRA}mIu=1Zg2>SUtBY$pZk%)Eh|;}dr(eCxvvy7`Vi(t33cM?aXZjEo`x4>) z?2syfo%&}VmDp{|U!;k?1@COxnzp2)?p;Z-y5uzb<;|8%Z z9Mw`et=r0Qh+^uFv8)2ei62F-k7>@+I;0*43$$L`09#w+92z@P$?%xNmMpz3#u<8z zXM`=p1f#9!RR}Dxfk8H?nfX2S!1!A}w6^b8-ntV4lV+IlVOYoRNj#)Psvbr#ia1~| zc=QvRufGs8Y6g3yN@mu6nCI3_CrYL+Ntn3yUGK%bl9qAw+(74Nq;kwq)URlZBcI&O z@p6-A3s45oq_Ku5WAI=1>GYXs!yP$+vdQK$uU~6n2k5QI+%(#RF(jf=DiGbm?i4Gf_TNoH-hN?FhdF8~}j-yMx){Y(Hm*Psg+#<WnznF&PVQ+iOtoq}qEt5e-r;Z+$%B%8T8gbMc;F4coq!lkL?Bd(n ztOh%MbVGe>N}E_P*2@-zfS#fc_vWQ~AhMZ#aD6Lybi!x++q{nCMrC#7%Qt8FyE@?> zaM4JmcM+lqXTgh3FV5$iUT|h6C~{&nSI{J(6~`g1CN5~!n6fxJE=pX?ab(}ycd4V& zkZ50%=;3%s_>**HDy23uq`A!GYLfQGz)a3)^aNq1fNalFKE1oXhc->wRyzBfr}hG2 zHU%~exX{gV5;K>z*d@<5q|_QUl0%l3Pj=1|EpC;aB?{4?db49?gz!h>)=ttP7!C(V z@d^~=duwmumba^0ra&%SnC~$e8RPvHW+m&Pn%9};VsAC%&K2Hl#q}^Be_t58R&$3uREjs#<3^XlFj%;6z+591<+d)MtjvRnRKEz*VGdp)?ZyDNsg{|YyKD*Q; zkcAB}hg{y^TnT9QOe&WYf;P%d*yn6_=4o`Ze$%x|DfS?zbi9L^P;^w?eo%+u!-+eRT#El7F63F~U%G&x%CPBGnuyT=DxlC?sjq(#z zp4up**WSY?e3dIb8f%qKDnxYV%>@mZGD8*~c-VJD(TFS5Ywftjr2UoL7b#^X#AP53 z!zcf|wci$%+G53t`f9#Nw@l-9SOhX2T3sP5K?^@7QE|`fDs~4)noVcBduj596xiBX z{=Jy}s;NBlNyx$a8HVHrXC(z96s1=U4?e(#rIqM_QC$)0EKZc1RY7Q6yJLtqyp7G( z_hvYJg#A1|0@*vSU036fD}1splzlg5fz`F_%$1V4e0TNQRHl8GV<<0;fv3HPQm!i* z;Yz9=JIMcW+X2$qMVArGxhwDf7C65?`KyNf-F4{l%|q66N^4Y_>Q@=Bcl~HD5ZA5Emvi{N?RGTI3Jd^?WOkIlk@4V z8#Uadcm8TmYw}%Rw`DbsUIp2a5_Ws#gm;9Ls`KOC0aLg~1(+R(nAN7d3N-)&*d~~g zc%cBn^uCVCgRueBwrE9-`E?|ikya;FP9wM6PRpdN^@%-V#Nqvf*q^5{#q?o)A(=Sf zgyk^FJ8k?$zvdUJo1$}^YB$RUw-tqBkL~>sj2&{D$w&R~%j7{5tIJ1?UxQlvlHrMB z^TLMu+HcyE3BOG&3+2%?Q2m4l4pl&ClN)-n4~R;=i8R*Fy-k$oRZ7lT@U^0{xtV1mD5o#2>JaN$arM))!Ox673Rfej9gwnb`b-cDu9i;;AEPlRNXv zsLoj8K4RjyD_-LgVc!FE{#_sFMgA`!F3lK#5~io;`44gf z@{kquNM0GK8QVimN*CbMvBHAoKri!UFk$Cvy74}k{ksF-a!753mdMD+{)~b+S^l@&CGVfwdBFF}^e0@Rf^G>^M{leX=<>okn!n zn0Lr4+w0I%=F=U5fW4%Rcl8Z%DjWZSkfoTZVL(qw3kwiwDPeW0jQ`ndMKG|bGr*on zMj_h9g|T+T%JE!Sa|a&c?M~Amyt4BYr9%V}cso#-`m^B!<(5tB-gDnZwTeWGMZcCp zmop)l)~3)!jqQMC0~H?L#zl%&wugI2k2LdZ6C;sAWHRUqpK+th6Cs zgS6pC=cQA;wDO{>8aFB1}jTrl658m=Pm2Z!0?0MxEK3nTm;Dk^D z*B1Y2VK4m%FA(?&xCg~leC~JP=-WEjhZBV zX8Hc=&?A8=>(C>FXJbWZ8!<$**s60EV4k}NRCEDRI@YHP*>QwJ#H&|s#|tu^^6~FA z?T=?i`xq_-HZq6q88ry~iC3YgB=O))e_54L>b>ypUAT;zFXiC7___Bjj)kXpP5s}z zdBNQOm3u7AlLV(T!9Luv{xC<^Q@x!a&!7>EKx% ziZ6Gen(4NMb@}VOK{UGRC0)pj>@4&F->1JG$d&inPTl_VHQ~NflxyYBGRk0 zNbeB(7k>ZyoqOTraM<1K&dfVA@60pL#_DRTl9N0j0RRBx>T1gR001x@0KgL>BETMb z#@SPg{e$PMq@@G^R40>O+2UjWXSY|=*8%{7xc~sjI{@GUdkFFm0PqtA05)v^0GSK` zfWa%PSx*jog1}BgRT*%5_nX&Rl8QY-?4@Q31^`$)?>;zhe)%b24-)#SYpD<}5#6I8 z7SPhC8@X_)Luh=Qk;y8k7%hYIcSN`Z)BtXD4&7n!H+-Ctj zt@=ByrM7$?jMh9S4G>$wzAy*CpH+pqeE!i2M4cweQsBIw$bZkpJ z!U{1m1?p?SDS)_#G7_cV-6&iQhYiSg+33NwuaqaX_#*gD(Cww{!R%Usc#BhHu#HAr z;&I@GExp$h)P=7cxuV3gFJ=*rFZT{%3$w(Mw?R*gVlFo<*F>d%2tx zOnDZRgc(^_A+Z;U`R}I;c5ujtd*a_DjhhF{aeSd)da*4wn`yK(In~YkpB?48 z`o!y#E&OpBaJg$+$2JZUl-B_|)vcIYf+dZo>i@Gqva{Tws12uVF>NlwS6DtLWk|Dr zky!%wHaeGo?M~*0h?1TFZR$Y6FrB|a#QRivJa<-UfppPQgmV1vIYBnmUToJ-l=<<0 zpG*E;VT72vD|Y_D(SY2u&*c6;qNqvl3t!8olOqO?JPMIwfU0iL=T^m*Oz4xfS*g2g z5(GlcLeWxr_K;apDDT~UHlp#2CPpWne*gm&g+>0~wHWI`tgvz9$=@Bj)+IBz=XI%v zIY_><-gNxkLipm86E?Pzr}|;NiBAUM(PM%)_MbwyL;))DR3CAUcj43OSXrhj)LRlf}oJWHUkF>FiR5A=UVc&)m~ z#Mo#%{GT?KowDg6y+$^s|2rv;@Gw90$hwoJm;UaaKYNRu#c6P`LHF+e9{hUnAU$VP zLw2F*_$~shda;t(%ZKSm2R&FM`^(tsu%Byvr|!1nPq4Jq4e4C(DNBRtlGV=_dCsdG z*A|PNh=PZAPPlSF_K=XCo_@hhS2qvz5&sQ=)9NfwvNPpRrg(kVDh(E4iT?$)vwO;2 zl)aCOi<1~D)-tD3p%QaO%~imR7lP2)ZigP}NHL-`JIlYXf-P_5+8+L=T*w#UMS=wC zVt-%X(6FQR;XRa7ZU30ItbjM>Z?vMx@{&n&`%yMR2j>>& z^gq3NR0RW2v^G#!P6_NAn1YJe3&qm37=HPltm5Ing9JPo2tMd{irOQEw0;L4s-Xs) zTBtKb1>qV>UVUe*Q_96(V!}m;B1BL(Rc=j?Y{|l=J#=>KZUsl<0|YO*0Sow9I?0DnY4EzPzJ4TZ zmm9?SU$Bs+Tl#+~)Gsq;3&;HZEEKk(LEN>&XGa3zTgo7ZCx<@Z+To`6_-Mo~0O$q`bU5 zcCEmN)QNScR`!7CIWkS$qa%bll+B)HB}|Xt zCtD*$pi7%|!ksiP1hGz^^JrgT36WzQW!t#sC=@b1)7jZ+@dulg47-P&$6wn2sE`4QE$y!mA+6!Fxk^^FA?cd*CeoKR4fB$sT;;1aPjt zCJvPq_fJY@N&2VNV%f=F(5Pvk|gFTH)a-h#f{ifNa#H42ENKsT09#nG_H!2^E!@BIfEG2&~pUdhhFrLG*a zvgu7S{qhRp`}g;Gmpl+vaG#GkpX@3AB_ee^XsUzFLe)0BN2=8{KagS%{@GlPxN%>Tmh4evBP=ssY|9FB*PaEu=Hm)DW?Is0 zc2h`2gb1gc{>GfTw=D|)mo-@zntqfHlt5jkVh^7AS5)Dpnw*QJJ%}K|j4-rgdO~M+ z56IF4OdiOpa))sHOlSI6d_h0Ya9W>)sQ%OLAf0dtC#T=w2P#nfvD5Oq1+WgBW&XDh z&pZDNh}XeuOUNdgwBznb3g6v=|5(^S+)lOVu_Zf~no?8%PGfHu@Wd(p9{JhKq~Eid zh6Ewh5yVqjM?OPMaW(#1fSk=aL}WD6&s(cvzJRo1RoTrQl1*#>-gA>AWnUQEagnD^9ZW2xGF#K`bJ4DG%e> zQ=BNL`0H&x4s`Bt-R^Tzu2V)8EWdEHDLIG*D|Gcihd?sWYpy?CmJ=A{l~D6If3Pt?-%IK}ej2)!DoZ!b1

JJor(l^|X)b|Ui%NB@1M8EIwZDZ}6gs~quI@X~MSa!AEm^&M zPeEsF;f)UxeY-odP1dY3gx!~&or(M@G${^_m?8aXK`%sBcm=|lxgtJ-OJ}Qt`ZNg7 zF~Is;{m*5ax)J?RAK!|YW*lPVr{iLc7ZVfzYT$BaOq~)eak+a-la1ldHxrJ?lS+|h zPJKi?%A2v5rOpu7l9f!p@N)mEAtu@3So-2k&V%;iu}SG=o1|CkScsv;cPU4&BNyfk`GNbP<%Er#c!(b$>wlB%j0jm zd9n26A-t!SFd@Cqk;+S9T?#U;z7QA2Luf8$+IQ-5@Tfrc@CXFIAEep8adJ=XVjk*z7%VgZX*#$} zF`p`5nQRxV-=SDy)l9frYr(L-%PcUbw|gKe^pICt3=i$u?+cgx%IW$h|9!qTXLF7T?rr@E3kD z9~_K&?pxIpZC?W|x0}65-1u9GXVn_NrvwZnR@jnS#cH}%jc^OTY6UVqX?U!c`C%u! z|EY46sFv&>aXaf@Xkql`n`R`ALbSBLxo}yA>dF=ypweN4!(U)(`G9A=P=4Ta{^HD5 zd)LAJtnI#3IMHJK^XmEYO-vA5?2ar){Z13>nPr0Xdh|}`0V(_+LI_g-FqGvwAJ%z_ z+QH)&-8l$69CUKOwv5V7-RlPV+PIC}F1PQMgzU63k$TCsM32lTeBaWsPx%#HePo{` zlSgVEpWB@^|2ANU*FUyP*f9RY1hS%}dARr5-Euc5ClEktFf{dkB^{@#t0pb`IYy>e zex;*H(^d~&cChGS5`5BODW}bS$~l-~Xqdjo&=CF33sNSIm;FrBZD>N(75IS(nzI?07SBZ6(BU6Q|_x0(t9sob5AfOuk0w zUxl#HhlM$YR`^Xhd_84Feo5E~jhGxcTD5xq(CGH)@zD7-w=h%Y=j?>!^JGkz45RsW z4pQIb2%2tt@+z^~a*HuMSSDR`fMbt!#H43ub&sS%^EJtSK!xWLEq~ZglE?GX0Q1@$ z1StHi{Nmc~6MXYMPXs9K+ELoa4-;+gX2swY5_;`2)tuRpU*$2?)JQa2xY%)u{&T&I z-tm-DN7aiiB_edH`G03m5!}4y*ZA|U2zAyaY89ES16ulsX$^W6+OA?!_@_3V@gB;N zToOjLy&56iGD-WpXHz+P?5WZD@3Ba9YC_e_JSaFMzUGkkXd7;=20%;mW}Z7`29k-u}wg_Kb4cppQB;y-t6~`(O|<6mBM8X>W4qDz>8bPG zfQ;1z6!XxtB_96gVCs5xLRst60F)g5E8BAlBctOK%>6|~R&#*ea7w&A(+KkSZiEf8 z4&!LT;<)D{(K9h^j69RZ!U|UL*>8<)(HPOC)C`Vup$e*hz@h0d83pZE?y9Pxj62gv zszH+gLHw~34IA#uc=)~L-CsSYsI}mFoz!Hf8sCvnR8jDm{iToCK z-=cMO%tCF(*a?0G*Lq5}TmW!ZUAvRHQIJ4eg2Db48NAef;P<3A{R_fcxMVjvqqh0J z+d^{=3qv2}?9NS9F4}jxc-X* z!)5P}kP5j*o?uy<1MjHNSPrbt& zM|?M(dbQ1dGJE^*x6enui*AXG13Co!Cv)H{eGB_Za_dLxdkRXRjC~%|*t+zv^0prSTF}_O7Me zk%!==TR6*XkMCZge>}DeS3k?>`;MSVw+;_#v);=22wuL3vpe~jXw|;QzbrvxuI$fc z`lmLBW(XSck%i$$&PW3Z_#a7iGx7rS``H=rD`FE|iS*yfbw;k|(Q;XZE^51PxvV^J zKj2HWURlS~i&nhY&@d4{zIJa%oXJgyyNfUz85mA0#YA+bE#mvV+zI- zhhWZi+J1X|yvyqL?n8H51`S;3Od+F-E92)gYo1PW*?RfJ9l}BJ%-RN<=qLp?H z;f)f)>eqJGkCyE}A(3C`XGe{@t4fhaMmF(%btd1PN6?{`?ne$4P_&IZ;k5w&v-4_9 z{T#T^UN%_uM;GSAsJki%RIi)X)4s>XR!$9m?QBcx^tj&s2FEDRq|EoKRl%jrn%oOmzl-kLdZ>gN6C<20?imU{KI*&MhL$2^!UO+0-8;x~?m=degx&`)zv*!uwT6CX_3^Av+S*D1br?6`c}12D5jHGT=c#^r8wSkt7fjJ<>}=_0BpZUDbL z$YOE?Jkr}tkc{x}yF~g|I9$%CbvV4UiWUf^GYMM_n-V-VsIcAga<`p%CmrtGeL9yY z@Ca!uyK-G^fZaHA$upwNbxsKvqKmxDmXT};J5f5_^r)5U2Sbt-fmiq4YY&9$8`)SC zov4j&&w=#_R%kZmuI;~Glhgh|>l+`sl$R9-%8*Ny)u@);=E0I)`J5}CNA;|z?w4`v z-HK0aQGYi~f@K>vcCbEKcHr0Kj(QCD^Cg|gI2yo6k!~oSn8$iV-HY3kr7`a^swpr- zewn&R0Uzh|xc+{10LE@uk?0kq6-52s_pXWM{;8QjB7fGh z#EklZeR9vhm{s!k)&^TBgcRP}uUcJU%2}VpM%%8FYzzG+J!NL`ZoGlwQ{34$R~JY{ z-_URzeWFAOcZkJL!eU(2L-VJ9CMR)qO-Htx_l*kb8%xbshNc3Xq5(`)!lVVgv+-efRiS=0|tvo9j?M ziBjJ)n0wve)bD^pO7jSWY$xrh<4BQ-&&cQ>= zy?G<`frw9?n%03|x?K@XD@VF~a!QD`51ZVMx66)gZWsNP(@+q9vFu3=XSfbYHGUnV ztP9y=Z(`&YoC5W1Np)>)jqQ4sTTyzhIPk@N@cPh0vX&LHCW!{2byRjdI4oWH-m%mO zgw{S2ljG(sTi>hga9JlcX=3*5?CTV@bpXEqo8XAhpZxWLP6UCw7UY3@b%7de$(e}L zLticVT`@Xy(gwRE_YnyNro|_8vz+p*){%a-w;xzOTLkBFUzo?6FVDuUw9Eceu%;Vr zrGL_^7+($Pr#~!N5Lsuek7A|zQVc`dQYgKRnM)mZdeSM0uBCMA{1TO2%I1yw4p1u@ zv*EtBhg%vF1j1@-kEWmBlmAqbvl*ZZwTrCCMRXqKBE;`2Y6Y~;X*XRR4oV;PaReQY zs#+6UMD3I=;LMf4B4|@&v4HBNMfmv|AE=MA3dC0Scc zvvk{WPp=iS|JZv@(ErMsjlTVqE{wUh%AQPl@2oJfO;yexZa?EZ)rfu0-LtTz+0`|) zM@viLvBE+b3;46~|7NktUmZrWs zcGg*gSl!(Sf+F1sZxV_^u&vP_rIoCeQB}4u-ZZPe@!IXTMQRZ+ScIpi=Ph5Fpnxgd zrbD;=I|r09^P`uj5$Q2iGg2bS0Z$`UaR4EEArc)Umt0hmLzi;9Hp@B^vs5(geu|r+ zJY;O>xBN69saAun+jV9WE;t|XH?!+dog~xUpTFMwiajmt!3N%x*_E)p>cj|*7<0-X zAn-B0%1;$#kf9|A)L=)|oow$%<%z4n4jPG7YlXFGD=WZYxi244e?~+boM?a!9m2xo zvFK2Y2orEYF&4+39K|G65WKU2{D0ZUo3Rsolcv@wppkVuzkHP21m5rdq^`JvYj5`i} zxvO+N#l6NiJ+E>mY!cmA5Mr=kV_KEBD7>Ik8LG5znL1}Yi=%M*blaN_6oE(Ji-*s5 zEeMo8=#D>!uBfaAXrv2T8d_Sm8X2;6`3ukpSMT`ewY9b7Y;JDy`ybwB#2DCUad0A5 z4t7d6?KB(xD6Zpe&Q!Vu`Hte%2pTEMN1663VP9>xWz%fx9%H8}-EV%y_Sde4^h}lSQVt6Xdn3={4lV(HgQ0p{g{<+nCue2`ZZ7u} z`<@5NL8NOZ0C}>6@;!N1to#Yo6NPbIjrW#+U#BvIC8d=!{ zzb$Ob5udcoiSJ#V%1-Ll5Nh?kzrdC3tR~G`9zR@j?OW!fss2~oFzmx_O;VC&IKfEZ z)9Mq9l&1b;a*NMukLc-XX^&@ry|T3QxR*3(N7XM~hi%8mL|0Lc+O9E9#zSJujr&p!?JwhmOdgZp2$3_8_KDoQf9+GW7Fa^%ehkn}DFc?ER;1 z96Y*|L=1TXQp|}_k~d$<&J^&IAwKI5%$1|~`FNDy&lAlt^w&3L!E>yqZI;RxpS*}7 z^Y9vHq>X9V8Z4C+o}H2DCp-V3vY9`p>3L*7vDayzUIt5F+~1}GeFWr?cfr@1&^ZM# z*nn-)IwAbxOaVzIgKmr5s33Tn32S4Zfn#BQ`wzC>f-5nOr7+L9+#|PG<$T`EVIzE{ zeLH@gW6_~=_hgn)QT}C2WK2advabV|LJGnYHvnhZZ^p?9einKzUEEP8DzsOFtPN1J z1OGEtHk>d1P41zhBY)O2OvVxV{ZoFzy6nxQwvN?p%}kGBG6>Y6z-P zGGwDkc?9>w?&dR^T+b=-3=u|#J5@2FoxcUmA>{^sS|hx)tRt_Vvwt%Zn;t?$_Og!T z{rfAoUfk6<=ZBRkM$JX;(4VnBM>~0M(bUpXOEQy_tjiQ(MrB%?T4J85$<;nLJw5$r z@e>&_Sr_*NYAddOc#h~@9mahF_^Pt1 ziiVx-6(6pqVhH1QSXmZIRA2L~7%8`s0(`}l^}p#3QB+|a|;3~-RFkyND; zr8w3H@Upl;=<-dx?bQqW3%yh0aen%`ITBy_gKnDh&|LYPR%q8tHLJdchdI?y80#ZY ziT%lT1ff;Zxb_XjYu)h@mDS*8)pP|4O{4Ju=h|sO{`s(M*i90Y(~XeL=y>1y5Y~{AIm^=!O$2t3QgI>5ueTE;|vc(Ks>aBUMTw>_N|1 zAR=oF4WH3qSj=I3ih8!8jU8_lU&)~@_VPpM7cM-?G!3Hh@mNOx41 z2g*KD6W4rrDxpOA4x4q##8*E(2$pO*3A&Gn8&|ftYQU{own(d~*?w1F&r*EWooss( zM|U0`|KGq9_1~>XL)}i@Ym^!y(@BAmJbbz-`W4ge1aL)oGU8K$tF$mqioOA950^%f zu#!th+uLhm`1FVom2mtOpEdS z1aBN1KlhL!2RSHHqF?IC^9YM<&={=p*gVjsU2Mf4Gq8-@(5*z~$0i<}2t1XQTq}&9 z8l~+kjrrZKtU(P1r6N>CSUMMh@gg|e>QC{7>1f(>pjw?TajSZmCtn!kdftn?gxuns zk^VHv)K&%L52z@F{g1WrE0uo`Oop#~4NFFjph;-5!?qp_c=W#x0;zt7m;JuV_LZ5FQi4>j` znG~}WP>K~p1Xup6ZQ%LBp_(+Zct#q`PL#37>3Fw>wb9sN)Jp8eDD5i%^ZQM6h*-7s znGIF{A}|2=4CgWR^mdv%sJbi90f)r!{MagvXae;m6#@E0@p_=>NBhsk@CqZ9Jem~k zp(aa0xxz1GA2sRYHx*p%uU#R=HY*nj(7q7JrSeGwu%U^^%s5k<1EHee_@C1ASVBpT z_ei~3kt<0`5XoL|)+}p#`9!I}f(@&c zx61%(Wzu_6fMJIjN>k#BEPbQX2Cd=YOMnorRrD~AB8!xv3M*BLHEx6#yk0W9m^}`& z3N!4wz4xCZm15~4Io^X$e!DQUQ}Qv#_7Os#*q5PC5F>cu<)E{8uoj=)S0zwaj=}^x zPsfAzGaDjBuIz%rH|IMyRO~6TD#2cMQ4_wDw7BP20 zopZ-($?;bAkkOiuX?Kb+Xl7ONM{#jsEl1vyT8LchFPbj(0-t4?m2h1q{h4 z+kL;`Yh+s%7piLD*$Jvc<05TShHG)h_W%Lo#bcHv7TejHcjH+y;GRWrFI^pYJ!74N zHZPcd-?U=12p>bfZ*Q=t`UscfWhFPa_n&;wPDn63tAKIxOWXZYD|F=2L?i0>tx3sw zHR2@3f~8c@T-UU}r|s|Euuo35<4)i^-&X>l3}rCl`w`{NT*EA0071eBf13 z`WE~T#_HjjOcku+k*;MrpWuy^vD=m+pri6j)6%YD56}eq~&TAyPaW0RfeOZ z_XA%jcr*CMfU9ud02Pv?U91TfieVATg&sGDYaHX=1JdaN`OTAAgUspt=Dk;IgB$K1 z_$g@Mcs$(#{uN<)&y~y4oH2AG2JVH*3T^+7V8;0EZh~5DD(q$?cw%1ZC9*HJbbm>3 zNsc^H2-hu={r<+GY*B`?emrT{JgkYCjG@Y;1AAw^zL-8mNC42fr&res?}|DjJr1;I zb4QRX(ZL2kV=aGa#UnK?92k_%MsMKRk~yM<@|*!Siv4QS>(xnWZN#crrtHW5%5UK{ z@kSL-3yAp1ZyhV9(GQnCBTg)1yMu5Ff4=>XC%14XAeU)|_83)EJ}NswWp)9o_3K<} zAK!)?l^93x`EhwU&cKQyQ|k0KO7&tJU(l*a)HwA^qQPykm!KD}u z^vO9|$XZ*Jh%%5h1v@p;Bn-7uX~Q4drnT+Zo<0vR|1W_9u?gH+Qx6-KAQjk!_mZn- z%VF@6{L}e^XjU0&*^Yo0ki>Aj*F(`b487!vPwhcOZ4?%e#IDeLULJWD)aqQOzvQ3& z$4gtGy>m;lx()G|Pi5d-!nUZ5XW_WxIyi&)je!{j;UTzs2)FV7lmIr zQyOu~yF%{=*2P(<(hg^i&kO)_L1)4U|60TFRf4R6vz0Njz7VAnA%cP=qxUM2N{lG7!X6buexNjq*~V5SxZUv zKWPqPbAPQcj(3;shv`{Dv_6^QTioZOj)Tmwq;Q0$jPVG8Y@aC-W z-;=$9ulks=$bpwEz{Qu!ddl~29BpO3b4UPX^6!px*&uuW}@uZ;#Xs_9(}EzV_Z-vJ^7TivI^E@rXz>{SH#VI6>LnM+njx33|G=4F4Ov$M4VaWqNT-<3z91T8 z4Ru-q&wbkdI>z}AJhnO@$pVW(8UV?#&2-2jPy<3>=+X6uQbwhaZVFHCA!rh44aAn+ z7KYgwdy~sp*W$L9^a7kO>0x0@Y!Bq0j%|W$ZU3rc(Z8{+ ztt~x6Ka@O{dw3N!vvRQdUS!Kc;ZwhZdG_okzf6lx+s?tZ0x1kesiv&7kFzvp@?ihB zJMH`@-EmT+7mM{yh3S<@wt@tt3b$&YLB8eL_I?$C1$cRJrWgVfW3o6v&hdTIh*`)z+%ZIc79Abkc5Slt=^aDueyz&^lbKom z7zs(e9n0reU$v#6v1bdJp0#o%Mw;pp}`=g*F?%$)e6k-C$JtV`NBGr2j}( zRtLWlH~?jf0V^7zMH!O+#;i%|mjT>tfKyqXVE@lTLe>->9(cBxM_Bq72I(AW7XDAcwk%Fi6>fMmL+?IzR`m{R_m9Jy!Dx#YbkKgr+3AoMG}HXHTwuI? zavKV-))5B!a{@md=h-w8W0Vm?{?1|Ir)5Ky%;6=4m$xTuo62dp~!;Q&jQ$> zii4u&LYXj8qyZ%!ub}{*HbL?F>K~0=GOMZr3_x^HzvY;bSNBWq{6~d080~R zduKcKFRL8z1~?&w_VGBw5hiU^T$IG_`}Y#o`W&|{)_*Tg?JkIQntssc_@Iw@RGi#M zlA*(O)J@MA;aDq3^b%^v|9kSIXUOTN_;i8iD$TTQq*v5aY$o?<=~z-cXJ5#mnsA|F zkP;v#(@V@OFo0vsr?uHfBZxn!9IXT^=sAT9u`e3$qY+Ie0_(-0Om_v6{X`Q#2~ft+#g~HpeN3`Nk}kcG zQNEtR3rdS9zg9tD8=iO)7)jmCkv`mUA1tW&BUr+?M^rgIVX^PH((C@dsSW7V)GH$< z7_x!6k^DafutnGK6aCY5jF(P-2)k?)L$NoWV0~wOqa(5j+|h)mwEqyxNX8vOi=oCN zY}W=tGoMiNbPdRSa)_1Xml#ZU{2h#svd36JZyX;~(>Qre6lqfns~l_?#`a>k_ESw{0J;>y z=H`jYr2MK8wMV<31+awpv=YLAqA@%WVg3c3Cg8$Vgj~6`PxZ&IIFiDmBG66kH{O-7 z!Gm`(S?nMdY#neGPXuc=Q#R5NQm@3+V;Ox_*ivZqel3dgDbb<3WXr}czpe0DDe$^e zsJMGKJg?@Xa2!`NI|k%MyYKlPKjy~>n+{DN0lP`7*^d^g?+5l8X{%W8-4~@PSK0+O zEBjLh506{K#W)!Haa!$Raf}2&)c7oHQ)Kx&7K znudP`OvWa2l~;Z4dh@^+fUaeWQ!ldAsjPqg_33QtI8_MW86#u_8{j8EE>pXND&-xF z^=+)6Gqj}Mefn_whs&5rdw5veqUuQHhkkb3K)#9duQ-*Oh-((x49~$Av<#@slEtR8 zg+<4=OFB@MGP#v_ils}?q50!hapgz4SINT=ch;8^!A=Y27`5|$fDC(Ja8B!XodSiqDe9gA4v5mlk>CMKC$)V-2*@u+QLc2 z;R~!1c$3V+DTY^ydqUNO@kB2;1Vl`l3|LvbLSvv>SIe#$dofzXzOSP;&_)8kgXw|o&s8_m;1;4>H zIoG`=TF4^SCF)NV`x2I!?KYi>+&OQVx~#umJau~0Qgs}db=(}z-H4&aNr5?0#R5~@ z-CWAzXJ)5akfsKDhO}NVhX)TZc7s+S@p3sog)LBy@i2uL3GU00Nv(jZRQ#Z-;spsu0vs?yR9NR%*;FV{2Q866t7uKCh%+BG;7jk5(A(mdxjwFapJ+=LQ?JfA!my&Jx(8dYZOJ zKc!|q8qLh2-0zq=CMJ|ChGj1I)IE63kU{~$!nb;chINb@B_p6HyqDs|XqtjUkVrn)>M3a%ty7=@o2hswDG8&P$g2jZg}h^4n~X@3EIuh*b8pLgm(ZKtzM{rIfZ} zQu(VsSLS(NN|$YAJ_lV7Br}RWPbKd&`|Fg*$TWAZu}C#BdWU|?`uv%9YN=E9&AP+v zA90$~vb~-eJ6c-i>Wz8qsf+e=jwt5tB}1K=Mf86J(PmKmBpaJhP&ORFuhcBkKY__G zYNV^mv!-Sm>9^IsZID90)aRZ4y}&BY4AwNL4m?1yE>Fi}Q890n7IlgS^esdY1(-tx>~6QBr2YKJDI{?-dnE5O>CQtwfA*7lt?{G5 z`NBuxI?!@NR|J%$LA9YfZjCcyD_e_2viX-@xt;{64^?k}(bi+v9gn=BAbm&CSDx(}*HKU(V1+jV2@5}8=7%&MP*F0w(dAglkWVhE-^Mvr& zo6QfkgZq;N^Qw(}H<_V;r^4e+rRUp*+*byFY8V)7aFlnPA+8WLNVXV>uxc$F>%W3C zNWZ7wo_>otzG{itZI55hZXt2p4-BonO zLNA@q5;gkea)X)WV9(SrOU0l-t!_+-8^mI_`FIN$*uhCHM8r>+_N^J1((4WywaC== zyfjt=b*QwVW^2>?TEM2d@Zl$+l0=acoEm8#v`b{)vYSM?G+Mrw&T10!NqPpLa@j)v zQn&f|7B{EgA^fU2Kfe=iZk2@8vr{=%zUwJpCMOdf5;wO~l8BUM|DW?!Qc%fpf;fh# zt5F$hwzNkk_v9ySMn_(-{^48PdYSa=p)`R+X9hZQ%Z50zU4egtO@@XuP!JlZ{4A2a zPa0ci+2zg3e&AIm(G%!oMlGbifm#Efz4rs147l7&PQK|eH(D6gL9CS>*k z=+S<*rD46yDk+DhOznREhHyH_sniu*{}hb&xnG(s7R^IcoFB|9>9LBAnWNpHq_s>` zQT|=phWAQ8P-byGFe;gkNgV#DQp{TZSJ(;xlBxR7zZexrnG!pPci#K9!_VS%4X^U! z2%zV0-Ui#;o^1wc6uzW>MEWh}UG*EVT7wv)-RN)q_o6mHm?HwOPT^fF;t}-lotm_+ zASe||Tx4qkpzTK2zl%K9>}ilr_ZV=Z(VBMu^=X)1!m0N2cT`bJmaOGBnsF`AAtmVc z#j18zNTp;pDsu5puzxOg{#RaJ4z^656$Lcy_C`e!2;2J7rBq@ouA7sp2+m0MDK?GN zCpgJ=vQ1%+A3vUS5@n2JF9p`aTyu9)ye)Me!`BNVBXc>w^>%!cs;icW@I1%l)x${g zdA`Be5c>Yf_Ierg*3M1f%9kY_?`qttUnJ?Q&2o*vMfH@euv4-yTGQBkSWN|+<9qCj zSrPb3OHVvnVy`Y2pZmMZUwL795PdM%YIC>YWme&z#Wq<$eiB7WXJ_HFm+VC!p4H){ z@FEN(454Ar>t`v~G$1jk3)Uc#0$=#`h-zWB>3jluf9k)LKdx3t-I$vim#CA7vtsMm z(zR5foCD4g@mFeAt zuc>He&k~-l9w5&!_qUQ2a~zbQK+78Ybq{lh3Y0yC$U@TmUD48G+V#?~911cO3PE~l zxD-*brUn-&MU;a%tzX;J_;`JO0f*V-xI}ALQ!*}^$l~6WaO%tNCMKr+DpsPXT2iuj zFfVrB4yF)h!Z7IO!S?XqxVgBPiu-N;>9PBEb5`a`kUpD-hd z=k-XjpANOtsx-0Pd`n(SflGeV?*7u)S0_-?lcK-t1mr`KUC!p8lwQ_lc>yi;R)b%= zy}`?Qj%Q|$#SKLbvq_kXy)=m=9Bl&wP4)DG9QE|-AEvs5Retx_!p`~+DbNa!_YZ|L zTjhwlW*LgdHfyzWIz;(Vj`yR6VHY${ms#pdVb~@!$ySoAYc`+9tOC{p`i*^Y<~gkj zzjd1&H{}&&8kZ3A51VAH+N2Hrgci!r=0xrCcUb2s8P1|wiMQUT55iXfddqNwznS?n zyITPSI&*5m_@pn~u^>s#Uru;)yA~q*;7851@+yy^Jg$_va*p9Rp@Clzw(#dB%aS}x zxcBn1%-K>VOop!3i5F>>~!~MqLLzyPsCR`4{m8=cj+J!{Jgt4fp0;etx|w7A+ai z&PK{$V&n65h_33e*3I6_43m3nW0Iv&_w1twsZG)xZH@`8B{Z>V3+qYsx)YO=V%RBq zRwB7uzq~(O_B4uTz(%%_Rv0r%m}ALR@G?Vf==n*3$qx(+-Wt~bo+EJmKuuW%sCR-| zlX477PBbwz{ERyRa5FfOr20b6O!`x-QXm3ovYloA%FvJw=bdfek(Kx?&8t>_feOo( zc|UVkSKZXd7oN{fb}Rbs(Jk=rM~vy0b!mSQ=e;yAHy=#>{#{Hq>#`C;gMM=}G1qj# zC}g@iO^j_96wcJsH)s*S zm;y53wmUH-KbOQ(VuLFau{|=QO&LrOKTc&8U?@+Id3`@B5zz%Y{1>fw^*2!w({V3! zU~Wt(b#t@+dcovX0fK}$(z6U4Vc3|*PP7o>;Ii?K*!JJIzL@aLszUZkeQCC|{!)CPk^`lM*nFE= zeZiX?eODM-TkG(B2EKkZt_pR7JQk@w=`uK3X1iJghA?wsM?lly=Lozo2X{6^9Q%y$I@ z1m+o-YGu~n66UmOs4x$^XFaK^_4r-5Nb5OWLyQSG)YJ1T9Jn4HcJ@^smB!Kvu1woJ zP7>UE--8*;U?$I#fvFk~m|g`wJZbGU#1{H~`_}JV(r(QD>MZyC4qy^FSf$p?A5;~T zZ|d!3iSdiTzJOk8LQ`G!L4+~tOYJIH;qlr=wD(^NYP`rkCxaG9f9VCUd`L>tsehct%LR%oVt)KK zUXZ|1*q{H8r1K7E^L^Vm5t|aTYPGTV-lMb$9X28MrZq~1+MC)`wf1PK6?=~wv8h!h z_J~nxv{r5EeZIfV)|tW$7QNZCk{((>F2d1lMl4`< zx!QWk+04<7mniA0$>)w^W~W$2z-5t<3mJSfW-p(^P4dQ~hyyCf3Lks?IU3B+G6I@} zec@e^P9Ov{OoIj;T*~a$*HM&f)xWS(_XNNq>wiTS;wxENDLgJdbfkYhxs5MBU2#i(q=~&z^x`;^mkqt@x7d$Vd}A#(h`^NSJ>qQQ#RR#uTrmx}IctXRajHz58Q($N zPJaH=-PYFSTCS9q)!rXJM$hW&Hx^*yBpkpTnmZPXf9E-ON*-gou|{%9n*iII z<}=`t6eD;~k{%B`8X3iw>6W6%5$*@ch^-i;IzC|;Jw3{NWq(WNyBP8be!1?v9c_}jsl6EF zASEMDzK;jdX~;X=fZ`R$#$8qxFhIgYVSX#`6cYLKzxA?#TEBjq*%|{nQ809$coUxr z@1gEshwzFU?k>joPYuL}4H67G!~2A0RpD}{FIvbd>(jIu+w6SXMM3NWN&X(E8ZW{< zgPm~iCUwOmg-o7`@TOZJ3_QnpP8aqKKXI&_`l>OIA_4^rS^jzX!T%O%WQ3uA%*~a~ zMioDXNz2H{Jk&8UkvD>k>0qD07{t1rUu}#j0Gg+~lhX^Tux#Gn!mBWMbbVvJsQ(&l z9uDeuU-+G4ZVuC(a(rD54h0-~FZSo>*jIdu6|N3?_=Nc$P=EehMnT@^oc&%ZOpRK<)pf!%l0m57|9kx{^euJo}z$-btb0h!Oxgvoj3z}1d2@;L76}&5Dx57wixnW~1T+kzwpc0#?78j{>o)7d#6u}%BUE0T|{`&g( zhE17YHSoBtOE>h&_dbzep)j%#kgjIryL27B_b)~q9^Y4W`1)4kDR;`#)|(F38G=fn z_Vzw2>L@Pa4u`YWBE21nQfznd=HXv75vDaM(}o%#M}hA#PHEAeTFYvn$7m<7wp3{pf4XLvtADtGhO^t z=7-Kr7*y@gK^h!tcWcpAdG9q0+4aljJ|<}}YZ%UG6|1p@N$ z4rI^mxz?VCQ22hgRxyz3W*N^uo{NOnGaJ~^R(Hr{nNyYfp4Gp+QeK#Tqh?B1Aw=1; zyQ1vnWQ3CbYA$I2{{=ezdbZrwQ8_Vqz06}?#keCTjfcucsos6tuDy|<;@CmCNO~o^ zwH%%x*-nehatl}dI=0a&FU{t?SVsevp(pAp1ga33QSIWYcJYjWRZL&pv1_hl#JgYZ z{(wUI2=N@gL62b({P{jEk(H<%?;pNx@K($A2mN=I!km>t-_ZH0@95H_cPMbwVK_W1 zVNs)sPeCAVZ1S+Jf}s3tjGT*7L732Cp;Cuu@7Z?#!o+Z`t${3Kd`2P+6R=CJT?*`d5_2Rq1sk-i1^{6fR%z+Fa}1;6BAsd(f4X(!1~ z^g~gV#XZI~#9p~<<38#z>Czp69?<~OgZgMsP>B;s8|(Rl1DlTDEWq-XRwK1f2w+s4 zlJ9>n;%%)Fq!j`(DW61RypRRZ~(qx{S{MZt@^BGytuTT?qnEP^O81GD-nEW0Gz;MGj?w_Y>q-P%1gJhpJDt2Lj(h+DY~xo zgS>XsF*>8@hFx{%g@vV#!oui5>8LkPs8!)F{0~&QSpz-a#pHjF;pBYtjPQL4cPMGJ zM!zIuF&ur>`F-x95|3Zt{ZN0c=$?TySmA-P2XQrFiMH(7T?YE#yGXWZmzO1_H80}$ zKM%4=(fcZ{Qz8QS+{GH7y2RB{_6Cz5ga<4%(;8a!Fk~W5h60$^=K_laO?uDD%JLqz zw_Ur@4{h+ohT+=ufeJ?dvF2!;ax^ZqBWE}UX|QM3y7hWX_6`cI#+4u?wa}ivm}WJ6 zIIwOLCC>4v#3cVzK8ZZPgh-YsZcQtmeLk$Pgu!+`@A=yBvym1jAXM)nBFOve4Ft6` zzc+iqd5GG#G<;h^^J?kYHmx?r(v|y&*t;h>9)Bn75UjW_XCEkc1y>l1f4L{-RhW7Q zs8Y8*eDFh(E8}{Qxk$rTu*@U|{)!5tHi;k*DOgxtq)^;9V8`15vl$?>A`{IFB*6PG z{1n?u%FDr4#@@y-hGzO^6NJRy7r$Z%Ks`1o(K<-vula=ql#w%_{93(rmITbY>|178 z8!Z%rbz8vp6Kh!Cbo+qIV>tr0W)D3g8np`oW{ki#){#9-64zFdZ$z0Cy;y}7ICA~_ zibF4KXB+9dkq4|SnRw7Rx>S5(A~a9gOFy~o@Yi59YECcx(=7P&=uuhYb6<~mYY zz6wCn{GJn>WcRthQsqbd&AUdp2x#~XhV@k4KX?C)-)YcpY`8fQj5AP<muw~<+Or)%BBi1QX`0EKiIHro>g%LDA^bo8v(tFc*)ZQ!p6@#%PQF5q z0<^aTQ^BoT2pNiSO;X$}Ff~oH;*$7@xh>tHMHbn7`Vd#*Bs)D^+D~2;v_>kNCAB;8 z9+qFpNij|VUsmV$LiHRDR8=(ZcLZc@4M_ybr2zrfoRm;?xLsBdB0IITcV|b9eeC0d zBQL913=}4B;8j`DLE}Tu@OhD^<17yI8`F-tK}LAgq2B-;j+VV%+6>QerHdf@$InjV zC7vvL3}=PRfKEUwdD%}qHDa^88ap3kmE$znW(>LKxBZ-Yeqo?(4{KT!cc4OP!2>kGIo?*n^|G zrnuuv^al4|imlA--e*M&y}^#IB=ckH#QyyZmdaOqo4ao9C!2gj8)e1ZDIy3gDmx*9 zW#UIf?&o5WExnp2OGcrj=y}wiMGaiig|wgd3;9(rG)s6QzOZd2VTU4AU+gJsUv|!l zO`;a7&VF|_*RKai3+lHY5;=;C8*6D^wb(Pb<4@!LCOGr8SiMfsK06qK`GRwNP+pe|+mZWV5?|$4B?%aG>5TIE&n2 zyu&un&chcD6_cU!b<;8UJ0o_{Kej?YEhp!Dyv zPu4OVk8Tnv`1;VQcA8ZCbl56T=U|g&Zkt|+W;Lb1SuwI@YyxvJEB_`3eW&Hlh&cPx zv%gjZ)nX~eEwu&fi5ZcRD90~)QG9Jm5hekDJg5;akcALONfMJjIfDp0>EVdOJ)M4FFyGaZ#@|L27adRq z^ST)@ET8>ptGT`EB0Z8t{5^grJpb|&QI}Ln2Zb!b^Y*-GxXeR_h}8;Y{dH(jY;5AF zl++7o*N6Xti3<{(vRJ&5Sdmhos2UOwf*#o-+6!k+0CW}dZ$IYW zAC$DK3pSshJ{>C^vNL|PaVriDb}1<}-ccAB5DBCost0MJQfjEDMbM9wV*uM(2HxT;cfNEq)l4=nBF2)#z01L{(`yrsXh)Ux3JL(FRpuMX+InA%Z(P zf)u%m6Dj$}mONwisKY+x*>k>s0}qc}ccNigS)adbc;YmXLOX!f&7v)BYe4IdSQpQ9 zmsu(F;YZ#}R#kVF18KobU*QA4PzBPkUGTp&lCb`_v|1|thH1VJiUcz4ulyXxWgjC< zVVp9v`F6vpLT&_`wlNvwv4i`FhKjnnl%GMBbv#J`_Umn`%jxvi{Jhqz%tx~K(0$m6 zH|6oBlKHB$$N-muY#Q2xEP!ZtZ&Wi*6-Jj86R6pdz(#RJPgl*KMOpdkrSZwck00J_ ztZ%8TIVLT_uwfe8)h#O=fp+uNb)V2=uV1m3#}rdE%TxRQ>VA+IzoEyQaJ>0h+VHO> z*)4t9Ng%nq^Z~ztGaVa#<>QS!vIO9{Czj{G#SgHLpDi@0zc^dGSft|_mxaRy?Zi20 zVjhRYh0NeLmFYd?FiNn1I>Yx$jbL01Z5u$bDgcY>a%`^Np4^BZDDdn<LnoL>APh;SBG7JZYAQf4L`>Ig=DxgW zoRd(#xIX$BabOmOx(#fsNYL5>`+SXMnUQd)+|VF3#=KalUVQeL=Lbnmd?2c_$nzt+wD@hW9q z9;wNh9jnM}Cr+*#DB;dmO(Dw)DE5}8xQCjq!QEoRT4c&|fA+H#5{f0W?p13EX=8`j^wdv0o1>>3p+i|LWY@&!kL|c1 zzJ+TYSLOJ;$vUvq=KlUF0=M2BM@uj;Cp3ix;jt7zt!*_aP1RN^TUb(toiO))DXyoH z;p8+xdt89F&YtFNt?9;NJsI$y{2bgz20FJ?JIQcdn12DVF2|c}f0-3~{wnqT;m~w}J?-+c+q8nVp@Y#hHrs)@FxSiyB++<-cIx z^KYEvO0h6;0HR$<)irelLy~kh4XqstGbu*ds9lnv8OtTsl z(jcUQyeJpv`N7tjHky zX8pg4G<9@eXCl$Qln0ML{jU8eaO^rC@?oH%9;mU6BjK~87gWgND}fMp{x&wO8~Jq= zuvy0ObLg@?&LRJ@&=PRC#`PTVtMLRISV{H${x6apGXChl8Cckr|8-)tMmJ|%z{^O- z+4N2u!iJ9@wWD|P6Ea424c~aW$dxqvh#!0wO=)p^uBkcvrG|rfiz!3}CJmIrllMvH zJ(QHtCimX164#{swZ=q3kARfM965B}7tR^3+3-aCvY^^Oa^CYEpNI(K`CU;+v+|$= zd+x*7`r~iw{p_EVs4oPd`xwrKRFEUS3|{~%_8CnKgsBg3$D)Mk%Q#NrEsF^D!47tH z#z2I;w6b!UTW%`l{;%A~*P9uRAQm)w3+-gwVHx3gJu!jBg08Wurx}fnV_MizOj*v3 zKw9FP+b)S0p!;R$tQ^!Uz&{EME1*Y@an4B2Ull-XbEeG6KQQp7Bu^--=53*RhytFa zJp~;%&HW=a!5?S!-Q$uZYK79qz=$kI7mQ|>ahW|(su?@lx6O))qrM=}^{iBR{aBMS zfhRZJEye$R?>AkRYGnXkcJys>mO3tpP}RK>Jr+Bh*0^nQI@Q#hiMiAIyKzDJZ9?Sr z{(OV;%%5oqe?@;4->0Nx-Xv3Q-|V>CzE9M^_Kq)B>zyu&)|1AYVsfoN6-WC42GluI ztlB{&GVrISiu3d5!_qYtcb6WugV63dn3Iymp9{GGb?&Dw^L2AKeNIBOmE`y8KTL16tABuZ2l}+N@a>CWhipi z;r8Jy(SbXT4)+t;W3OP??fIZcx9xS$Tgv5{zZ;Y_ek+}1E=wPV3@Z0`E~{oYx587N zJu8yB{Pi=P=QBD5w!w^fF1B?fqoQwZZT*z}>>=T7o^GX=+#DkvLA7X3|6KoCfD6qS zRo1uRnZVzxQNTCUqAag_uu<$oPvXu! z*fJGe99sKjBFaNk5!hVsAW6&Pw_+6wgz2p9U=u<=> zyC`EbVY7XAst_QvJed`dX=*F#e^5)}BZA(-&+ER&lPDa#Q|g8%BMr=}$)-6aN9t_} zD0D2(|HK%#dTuwatiL*-c!xjCZ(nC@SdL6~lTgb2{k^D&J41523RGONzvK53MR!vv zNs)x7OikFeA~Q2p{x>wY<~fK2;Z+lT4NigH`&B+osxj6yk*frF@6`~NvGliYgh3xj z!-_=W4@=Ym;if{JMb+`?Q`;%5msk2TA&_#E*N0%LbYj7H+}vtqU-mvLcw7sq981Td zxX#Qc@EDip^w=n|y{vS5tE#HxJTF|IZj2rolE*LQKpNrzxiF#zJB!BTCJ>mt>QPI^ zUowxYa~vN#+dbF=t!NbMCO?X#vJTq@^eX3-#>0sfgQHnngDEV8GlfjhP=LG{e0AMP z)hrfL$3^lUSZ^hhIw0=>qhgBDW|c`KY=R7z2cmaYP)Hi>25x(-Ki#Huw)>-w(Z3WB ze4X!Dte2y7)+kT{OMEXZE!~bh*_~M-&@neph>k#VPHr8jL4HG{cb+0id*d)kp{K_K zbJsINHSbzg3SbjWHN1ZwhH!-BInf2ur!O;sCC@>gfH`=M%Yc*P!0{6t7q8Id;;V;S z!I*`f)wbItI8s3H+@2Te4a7dtK_KAv_FUVZt>X&X*|NzmCyRc{4?IVlxZrr1BERhb z-~0WW1QEel>7G)=6QY{i#mB zf5EX&;wW9oUm8#a7i!gj-+*!ZjZ{M6b}@!LLh`-Kd_R=%HI zfu+#^XK3^yMVlE+xey&iz!d|tA^yA!*dF8+6yOMf5F_*sm&yZos)CwVkG#a)CEo=d z{wg?j9j$(yESC&GPVF({ZJe|Qf+jF@2}vX)VI@ZIClx^zI7m}>BFdoQ%jcrE{V7`C znp$#SKLOtKkR6T7(h~6#>my?j2nEgOd7;T@22ct)8Cj97O}hvv*bsXK=e+kZClZ2#CgsU1s|Fp?N^&_h85J%+BL={=+Y_W4B0W* z7CctUg@i{uK(VUd3HrSNFrtP*_g$#`rB=|>az!{E97=PoXL~?y6z5HNR@Po_I#IP6~q~R%3 zS>S^AuMmAp1Nc-KdQU#+0?Ld!5U6N0KjeMGk8;68^qi5xVnvDlM~*7wP=k$4`@{hN z)yjM;%xbQ$Ai(fiM@)|G!JLl%B!wBJVr1d4HYiA!1#GF`49p0M+UweLZrFV7#&dPN zgb&~l;|$U;LQHBvCvh%BUFu+1l)^18|JU75B?SfH08^L}NrWat=KKd_-UPb87hdC80^F{!L^jG&O5g(HTs`t zOOEUJ;f;W$owYpcS`62Kag+brx?GN4eKkZ+h>!VqXz2{+{JKkg7*CKp(7$g%bd7gj z6TL{5O?nL_348k=X8f=Ei-)fWEbulz7VD{N3JRJLUDGWH29^Lb1twE1#n(-FJrW*w z8MgEgbU>1-O+Uco4^mU>ibo!y;WOV`@XSJ+Q17W9itwicYl$U7zMzpTKcm>EDE5s zF)*6lFc;4;ukJ>Nyip7%A}7&HwK49A0+1|`vDF=r`+D*XA(4(^BHR0FPZ5;vyfva) z@Jk10_KC2Wd(bX(b6-_Ib)clAtbkm_F1*Cz?U0WvSow>4MKlYWeVq&2;ue}@GHLWv zEB8v(sDwBGG7;Tcl+vZUsVytpy#&x$mX6{XyNDAb%s`Ea27oaaU&&9S>P>TsG-}_^ zy9GBhHPg!KtOlOfyer6M%^n6Kg&{+5IGizN&<(yZ34Pz2`Er@!L+{y~K<_{_UfL!CV@{C!`=P}|Y~wN+3%*%ab$?!1j^KwY zD@`|MQOBZ2wZV?Y+VQ1e-EfR6<= znJHCr!5rMTGyd?4&aX(Ux(G1rJKQ@E#@UXwu%maJMs#=a7|M1iw#=MF=Bcem75E_A zFSWHW6qJZs9H0Bd;hpXoGA|W1d2n4tc z9|19*8Gp?&^57Ll&Ey}=WX!_bQ))d&3b{03puo#Q&_+7@h5H0oaPRTRhX0=jdpjU( zgAE5q6~IsmU}Mgbf1H&$E9th}H~?&2R+RR9R*&ClG;UmZ`KWP2-_s?RB9CVp;J=tC9D@iz3XR*#{mM=>B+1niq{g1l|x@_mK(pg%OHnx;3jxT?E^qRH=~aH_wA3{eDB3v zOBgIg7xrrt1{r?GdO=j(QTlX|v^1nuueX^fo`({qhqn-*Gg16!eahOq#KA9;9DQB` z20r*|8fZXPHfS|luBj~WO)x=rLPA6kv+iG|J->0@&ksd(vr&{7pwv%(Rv4X60+D-$ zpc;0l>zw__L)ueqj0g^UN-igu$vf5}7=qFYzu(*=cXoez_MI*^0s6iIKNn~(B;5<+dZo}eBRJrv~3J*764Fm?j5Jj5w$zZPO zrK`sVJVuNR|6Bv56?Drj#;E+y{>DYSBo0Rx)wNr=@2C7(sHQy~1!n8(+esG!MjqI` zxw5eP&Gd1Y&ZjtF9c|S(78_`L_mn$0g7BSkNlb4=*4C)0xbH-MK-O<2PCx*LW1?Ui zDk?NF;4wKE42Z5c0>Hc~NB_|weBEJP@BU7=ugb+Q>OHsh_yq(Yah$JqQxp1r7y$*%kdr3BL3YPC@S65xnF&v=1)d zwtrn6N7y*7bhfCqUsp%6!KTJLpvW2n9&$Og!(r3Yk3_bmoF)OL-=7xZkhhtc3Fu2- z$Q(F8njXy9iJGt9*AnDy_L$flwOIc#DKgfAh3XMc5olYj?BnUbO&5lGmpXy7Kf0L$ zXTi81#?lKuDQ1V9v%9BuXJ~EA9l_)3PtMnw<->8r$lHOhoFxCIbo7sLNHVend*Fpi zqzi9YM&}IQCyDmDpQMMq9}8WXkPic*w8^~ui05Cmz?H9|+7@f9M-X*js0n;z&J0_X zKkm;`1Wc4#OwCTzjEDC1rAbXql?y!K=DXMrK6XVrMnD`>OaRu?$(&20;r1DyL- zi9VNPs&A}XqswCK!?RIZ2vfv~7-A_&?1!i+t2k^`9fLt)hsR{Pnl~SJFi9v+^1pK1C3&T7n?sG z6x^?NSkoZgG7jVYrK#4oM8hG>5Z_H3_6C*-1EjSd-P={SP%2wJQ#Ya@mS~no{Cy50 zovU-X3D6^_Pe0^%01Zu2S~?$%s&1_(0_bhC2sl*|kEL0wC!dxV<$+B3*81KLUVdwkx8`FXw$xOL{E*Wq+08=CJ^5=&)=*v8yTz942sVFZWIdh(6{s6i{J3Z~7oTCR! zv!{ST5VV>80VD&P+e01S#Mn>i1nsxi^Q74IRNui;;obI;%?9t(&ZW6sPB#UBCka9T z?uMIn4_9Ijba?c5K-7<`e@c>LzsU_$1s&i)MjBo5CT{T`(hNS%;Kb2g6qpm zUIcd@vd#UK`d34H9*MRf2TUAI(SxIy3XBAF&F1qmSlZsvV9U~g6F-2gSs%8X?&kj{ zGBts@D?hc^s`X7LX&6RbNS&0NU%(jP|7g!t@q8Sjj$AFk5Z7pQK``1y0jG72vRhwX z)2U&V{23vnp=2(8L(VmP4vv31Z~v{}XTCx7^{g{ld*TIS?8sMu|NUz+*{oLHd`^T5 z!o9#LUQ#9cs^JCRplUI;P`%jEqCdw_Xq1g6N#sQk#Q%|9es4-hcxX$O`q0yBv}fiI zK7DR0*0n+mr7%Tar_X<>>`f+4vQYB>TA7y9K1RNsJs8dxSAcN~1vPChz^^drg)C#I+S(TK7m*Svwch2G4OGdwB+V$wEovd8xmEmA+W zlLWA%9<3BZqa-h-Uibb;LuFv@kp~ekhLL9fllylg?XuG|iuR*zoK4tC);R3_v^d+o zjr6qYMI8d9jH72bdW>EubKiw@>%=2~?SwZFHMfW5Ka#%BYu+ZyqtW_IF!P^;hUMwk zgMj(5Fjp z7Z*=}NE$NrzhT~JuJUYGi;IiP9*6npZlnW2>{eci{dwbgHNa% zaQ=*&8{k6K9DZH_+)37RU$VqV(`Ilpc;mc|z=)>=b%IQ-Hxr6$t*9zkR8w1t`$+kl zzznz-BZLaRWV13xB~PCFUWQ=f2>!M`97ZliYM1(vibUaZn} z0hSGyeUM=pH2TR{h6E(}xaM{;0bj7?yLWVw^!s#FDQH;Oq{b#n;X_vV9OnVO6~1&E zt}GOlM~&^C;LZ6t$DxOjK*}G`BJHkhG=^*;1&VPShj>FYt{so4B3h>&I=tLLpf6GQOA>c5xyNR(heK`{( zE}(DV|IUmgcx@f16rD}f#6W#n`2k) zV4z(AerQ9^Y#^J=kC7e|chA}5ADV}A#%t8lnHLG!(4ij?(cBn|~Cr0f`S zRD4IBOof-%yD`~_ClM-AtXGOcAdXx=4b;i-S~lI$U|MCu*+*yZ%`Y@9Dces#YY7R< z#9rnL<8DZk!`s;o2Ca#=#B+^Ii_qF7I|?DR@C`dblQ)*qS*+L}j{|EO3b_C}1)jv+ z&=Y1L?v;FcDqjA$jjs7=w*T^sNuqM4MzN|Fk+izEE1qV!uUTI{oYyl3^$^s7R}c00 zOnU*++0@^#&3#-=Idp|MQN$@>OiE=*O|!YC@%4zXcUo@^^{z+})#qH&;b zCN>&08}pYC@^avtZ)PY@=oQ>f>%S@H>Ag2WSLBBBo|z?VsaZ02L@$>8NnwpPbJ;{V z);n5HqN#96;LCfr$Pg)}#gL(6mx6`{FF)=Sx&Pb%uBZOL9#q?$zp;{670us>2GS@J zUEJL5QL;`?sTCh~Cf%_GZ1f@2C9${VE7;7rSa_U@R2=Hzz|QBa5N)WMFl};x!kz}BkMs-RKTxY_Krx#tDClPGq@-|PHtP#5cwVQY^wn4c z;lie3_$M@ily&UW0o7H}ZJ@hl9_Mm$`H*6-xy;dgs0gtIsUYzy73?@Qdm(u9{s>WR z^q!%44=}HwjsanJYl^-S;b@&2e$WP{jQ?^3__78Fgf75>ZIF_fB5(u^*v7Kyhin4| zUR{_|^(&7g$B|ElrvDTK502G%YHtyTZLdR&sgy~+mo7Y~&B0Djr@BoJC;_i4u9|Q! zE`54x6uH!68CrJNI&o(Ck->~DoHY=>F;%>fTk1?3rm(uKVDxq|C9>N`^Ax7z+9tSUx>{4>8XLhDSu*!+g!xICSEXG z1b`Z~l>c=^b6IBf0dizsvs0Ls=r(RJbWcirm$Y+L{K(?$1aVg*>^6uK9vJju={INk-H!mj}0|IU? zUC*9^Lv4P*+`x(S$;So_3{^$%WiFq^dD~>BK15(myG7f~VXySDrUoYi>0>`b-N#(mC8IPv~rOE@?woh^@~@`Z}gPrfR?I$-fjNYp21!RZ?=uhI;dR5G?T> zksF+d2c(R@;7%wc?5sl12%5M4)oob%mjbP=2I#qy&?8M;27tE!<~41xZ~@TG*f7I<`u0sXJcKC~l=5VM>#_&F%n_}{Fx7=ZoW0d`w0tJr9>U)fhu z2Lg}_lJAhWtNDNd9|lAI3QR^4zxt;Ow`P-OdMS0lY+3)XHAbNi<%D@2IX^ir8AH?9LNVKD#~caeVX@2>zLuVevdSp|A*sHv+* zh3Ozz+xemw;F9nay^^*4 z6P(sW7JsKxUkV)?RW7FVxDIRp>pcug+}XwLB88u4aIOd~R1TBdaZjJ3hCbr|n2$!s z0-9{M=;9mmyKl+bBCM6Sw_+p#0@t8f=?~89d5-;O?9wpKZa)=i{9 zA^#}PueVO0cc=!$RP$P;hC1Iov%PAyG1Vsi6Ysj~{3NC~>1A9VN4I=|Wk<@HGtH(f z(7C+D{_bRB&Ok;MzAGj$!t*9@w57DAvPs-DPJeu6i;J0ko8sTT-_w2lKN=Q$Pv-10 zSsNsIz^`+h{%m5Ba7;@1h4~QJGt`+2F7ybOWEop)vd`uma>!PWgj1j?V`5A%o z5@dMDfvm#(%_9XTS;ps~cVN-*QtuYu2dT3BAP1|wkDBOt_rV8I2LP7&?5sLa>}5K` z$RXbVORiES`!XqQwbvmdpiwlu)_1v;Qz3K@cCjSb6s5Phqs!e`M}dk5L|!Rc_N`#d z=h?UaR5keb*pZ%9^v@rxu&&(Bo;Rf7#jR}xG9UO{x7CUAya*JzN;yPrF4qx@I$mU- zw`*IqCMvNuk*0%}3;-*V%FamT`8Ai}seH=E;GE20f8W$slvh zoxNb&#DkaV$1MwFjQ&p)xS#(e!X?O_P0o8>Mqac_Zy(BgQ!=E>!4czkTz*A3-re3M zK!63?sL6d-pTRo!dtVa|HE zk(W1V%OH2a7y8IDT#jD{w}3=@d>U2|<8e&8XTBEwvjgpyf$lIwSfbIEbjiNEGgbS? zVy~x;hB0}e>~Vuq81E2*sMVV%wpvS%XIplEonLB=tol+mT2yvrA8|7Ll&3-6HR_## zd+tUG_p^LQiAp7|i}D1rGPEm5cLY&xzROLLGx0jS-MTppEVkX=8dy79>s8(hmNRrZ zy?+`QU=*;&7@}@-<(Fs5Ga#)5EIDq2Hv&Y5L0+Jy-T@^Fl;~~jN9~O<9^Is^Z6?W` zUKUfv6`6#>p*+^3TaSk$mK`kgEeb)mcZjsol#32OEcE-5GQ6e?O_IYtvt*EL1Ir~Z zn+YXFyAXZL=k){JNaYx1A7X3Ea^N@^A9cqUPfO`@_X!qRN|eP{#;MQtpszi}1ym38 zaULUc*KOSc^m;)xtcS9KCa3$M!cY%^&8a;aC{4-EK&Y`QwHY(G%PmwLaT1rpt(iH! z(j!V-fv3P@XA3~X-mc!r(xKMw4BVU6>dV%MdKpsL!_vS#VlCLK*L8yYy`wfFN4Yk@ zy*~5qtE+QBJWowcIF;4*dRb_i&Dc-f^t$O7xwkylkbzO9+~SWNyYkwJuJZRAeSx!$ z8qW`Z?;(eq@0^Pmp6ZPJ8QRc{``vaAN&is$6$edkXVXMZ5%H70;H~UgBkMb_EhJKf zNu&vaqg{i_^mBXyTgTCW6BKgwk0sG7iN^%cYXUMKR+7NLkf*T;jM@3mZ3L@G_MW8vM7AC?2s)kXkE~|>SXYKDm0Bj;Eb=*)>nYfbib?0PJweGT;f7b_<|T7T>^ zs9Z^ugw!BJcW(c9gdkJSF)wg_*|{o~)L~9hwr_Se`lg+FZW2lD89m=-1sB!KtJ-@QjBELZC8EkfR%C??NS4;m#a{Z#-;J9 zSFM5t8Ev;^1nDPVu4pI%t01Zxl#6MTY9HkIcya<9e&XtM=Eis+jZms6Ymizg7Ih%W z=e@@cpC~r(o@gT2vKBxm;jeTVd_N<|gCvg=C>WLBZAbLOq%ej#KmU$b{j{PT7~xXK z3x2$@>KtJ4e4M(Zq)HwB-s#S@WfynJxEk6B|1+a#l#tlSMxdn5D#-ivb7|?*l%b74 zbZbz^Z}u2c$gaUh?qk3WSpSme#KEJNu(ibL8DiML3t%Wl$?tD~ag^z)Vv(frth2I# zTn^S{s4ek@KyK^giOxQaRtd2#e&>#VtlY>^kA-p11?|3w>2kwk#gTzS2u^OKJOJvD zugn&gE8GV@GV#`QY`#5=jj!0KTx2@SqQR}fB zeOZaolIv8hbLn7FUVNPQlKbiJk!Gn|2B@)F{?V#;2ttQBQ>elgD{wt_E&CC zz!6Ql%1ztV?U`tbKPDIV$wUJ+JII#z%|s)-6-4V4ygQ#@&Fy7x`Low)L{f=aV*KHs z(+hlVLFIuCL-fK@kb3Lw!+6qyuOaL$uDLyuE$zn5J4+1&v~=uU^^Gf@Ti*!-7-{F- z1_Cs;Y{Cv~Uv=J@87RK;@43T;&LlN(LnJrhGnNg$mcdRbo*uGQ+OF?^P5KR7C7bc? z3(PR$#6EI&`ocvfD)jCtNHMI!63S{B>?k#yDZbiVIjx9M(EgT2w9qIN;dDRQ&U@j7-xBLHxe7}NXYPxnScW&#kWTM znO~mCfi}mZn{7so79>2j{Bro$|9mypKO|0CMAZY4&u5iTTn$T3 zDJ#{k+D_a_dq$w?8J-neJXFDm?Umf$EX-%)u)T&Jt+XDmY|$x7KW4VuV?@&ocmu-R0vURZnUk=Rax4s%1$(p};#0Ir zeP1H!$R~8>Clh=-TT{MzBbcb$9KxomtugNodk~ZtlE1u!z4cG)LN##^^}~WfWkEh` zk2uAfKY3H)C*$G)c>4{ai6`);KNhIzjbFsu<}^j76{ z&EbZ_g@lAyh_P+PvhhRU*#=aEnup%`vCx}FqhEWX9%FnsY2S)Ybp(g5htTjZM?3~r<`Zns z{*@h6MH=Go$Zmp3|7F_j%b*t6IzAW6MV-t+KlL$euZrAW$uoyQ?HtB0)k5 z>HW5tg^5jvm_!~OTMMF68vYTVrPA^$MUdp6*9@a1#f^`usM-uBYE6 z@{9DoGf`o|JLOq&SJ=K&q8C69t@{-_6gq)EdNkzH(}a51^(&30`o}HB5u~BgwEqk* z6NwOPjJ%K!Zz$bkxW8G50$Wo~28EEQ75t<%{5#fp7C|TAT#a?A51W?}Ir;fDOWZp^ zESbt;D0{U_P0%*1kRctV{H@jc9|H-WH~3rcA|0wj@8nkusV3LOwO>s!c!rx%?xI&sR#+^h22R=SN zEeAXxLKCYMGp_xJpsDn`awz&mwr5+%H!~g)hHfprHd2yosgOdNIR<1*b&l*9R{uo& zy1XY>HEj}2wx1YrqrBk8TC-(O)t3KOJAa1>lx;3?l6N{$q~?68n;|DVwILpwxltiQ zc#%;~5vSvCL)cHH4{2|_oEg#*?3ub!u}ada>T|E+*1fqNjU|bmf!n@eO5M2g0cor? zc}lc%Ee8%3*0$4Y8W)@pfo9Kk5hC4;Fl{kAt@f5}QTf-eU;mk#yW=>K8U7(jbmJ$M z_0$A^ha^MzfXY?Y`0sEk3M{41#Js+Eor5%0N!tSMDKZz$zqhm;Nv#+jZP`=5~<%qa7jP3=6#YDXjd{A z83SyX93w@bg^&wcD+^eF2em7aaE<35MP~)DF&SECs4BxthNMp*@0s@qer{ay1!KUr z^3U8sq{2@d8B(==-S2iQ+hb}uOAiy6^aIyqC$#bcr#`g)siZy<16EPSv1Pyz=Zk9P z7seY#n=1)|n|I$nbX561J%FX0`cHb(f5zs=gyQ%B3>|sRU*zWCAvzHIKwmT4B<4A;m z;6~Dj?Lih}X)xj_R^mAR;C%gv22yS=z7z>>n*U{CmmqA8vVI7WP~i9H#8RkK-3KTo`QuaAm$|+AktteU&wJZMhW^)8cB(dz7Ymu^{`F&vuw0tPb}NtMs2L0EKSnL~ zWFr5jq+Y_Gk(Q$D92v}y?ccYQLTV!_&bO;@xR-alL#`EMPO7%haZ|zcVWL4Gl5XcC z$r-oxTk#bbvV(5*;GG?My37 zu07USfOBr^h?*LhpJyI3LKncc?9EWIogHT-FeIRP!Q|)_jLj^9q9-zz)A1V@)}6SX zAf7NfI*QG#MXB&t7vqf-L&=7RCh`rgK5JIWrB#PV8@7cX8jY zrHdo`=zgD-$l5@#`0Q<7Nmf?Fa`7CGJxgF&DLXVM`li^e3`vRg*}gX3x8r%AdXrf{0vHB5s7sYby6fxY74@%)nVFe=Gr>7m zEi2&?+e-M0UWOvQ%BoTsT%01aLVENY1y1^9_r(*+MoG+A7kW3enFv8TNa4NuRi@tH zaa-Dv4%CP963d1&ke;Tx0|W=p2ks@T#vtYBPMY>x)SwokPz%M}c0c2j%p6JoU`Qgc z!NIvBwy?E8d9)u^f6kON-Z+G99p%8-D$s^VYv{`|f6`5urCNq<3aCP|Be4AewEe5STE{NPG42ie=AfmGI;4JK{}$%*)|E zlZSBVc|hHVp;&3G^2;gctqLPBV4)$Qnpb&p~%Ydamd z6&d2ldGG|xeFTK5Bq~Hxs-w6_7<@twamfh_wVv@TYZA>xotN2}#lag~BevPI`nw~F zzJLfXgAt36sJW#DIf#Y15H1E6^gW2&;{;J%tq!y-gkYaazUqxAkT#Q@6gx0ZkFz_k zw={TfB>l5J(F|pTMOA}k`@TO^#7y(P!)v+1<4`(bgrZF$z=lkAZO)HWP;spHR|FVl z*Mf6?+q&q)4s-QpW;>{Bk*YJB8&04%?%*%xlWKWdfpmh9X9 zRVevH_j++CRR(lLQ2ulq%j_;Ak#gr{IHFJGsoP%9(Z|0yTvPBM}}(oTsID$GkzX*ogeiIyu)Ne;_2xt@aNOM ziml{Lv~YH#us9#rIq=QcoXd`Hx*;`dPusT0=m5iX&nZ>SviI)-V#UYOVfCJLsRFx7 zs&*cI5Zt{8 z&Y%GKOkrN*WtlS>*wia#bgSjfL41rXhQi!yik z^5MC}cd4~x92XXi55||@$OJ47f!Xr9o8?z{)Qr`meEYpmBLw!WU_gETJ%Ew~tCP82 zI%YL`WJb0z zjsZc0+&Q8iYz|Z9qC0e2Z{>rk;Fl#*V=`JP{qu85WE44ZqCp~N1ognoTGnFsb*ayT zyqUm>Ud==4OX%R8U&i)`3MTV%oDRF$WI#J*uZ1|^?7UoS7D^9&ZJZ52cxEq7G_P)U zt+8jt`5U(XOhlps(yqUO?z1aWO&cE|-)9YRvDthsL4{3Oncp-dmCQYUKMfpqCrc(= z`iw5W2f^6W78a)N KV3dz_)ocC)+eu_RX56f7YYml|lc#!P8yMmU|13 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

uQW$GRi`{hO<(c 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%0D$+NB^E6qH$2pk)!BphQ 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 C3v$L~H1i0gT{+kB2 zY+fk5%jC(SSDQ(#@6GeY(PBX4`6N9&U+>u`x2*u8N; zfT;ajTs$1Ky!WNw{f!n%U1=W#rUa-jm?h_*1Wv+PM0ZT=_t)68p#B4xi@LoBV`uW zd+K&ZvVbKLx>>k!pVf5CR#XYV%Rt`_jb5&mHnj&dK*~Y!&?H;bpC2-xD4{oqbrkZx z+!n;=DZ9R{ZLV6pqr*GP1wa<>(H5GWp{JpvX<=H4Q0?~jib$*$Wi!@3F(EtY;W!w}lS7%$nKyU?^lk+5x8}Jv zPm~Kf_RcvqSGktQGhFhYR?wLCz@dv|qEH^VETia$jOu&1tJ*hCCZD0X$lsuY1;DUl zwETU^NeIFDJ}vf#rW=S?5-<0IN2ej}&dduo85Ah5M4GxUdFd zcfk8~ApMG4Sv_R$OdHrLT*< zuVKi@beP-qcVYqcYqNE*PTz2F|JScD0DBeKs5M)MeDRKIB7XJI203=^_bK(8W;vmr z{0A_Lo%H=ujpl0i0BcDUUhD6LHrJM}E5j%0@g~Tb?H1aB>`#ZU9p?yr|Az?fnA01w zyNR3tsU4$|FL#Tp<8~JxAm#Ng#6HlA-scxeGj5YFS}OU7su}7TJ=q$bChA~Xf2Z5< zfs7{p$C$l83{i;A>2i{{j4gWBm(0a|8Gkc+xjndlqZepFn_I-qIK7%%Dq9n zf)?6Z@Y%Zp6~ZwEjK`&B(V z4^q=s8j_UkH>%z)+9YyMCOM<_n-yc=7I@jIIu#b*Q2A43fZoLbUNyvVki~FCT;$kR zT93XQBquoI@%ZAmlZSv-*8#dCEBEzsr{$mUlP>YI22uPTxc7=r4kchitJhS%5AZPE z0)dS2W~_W)0SIqud+Ib-g!1%`=-hX8=@rTwK$~H9vE#FO4+sNT3O;kD{Uo?>oE%jLBJK!SFa|@( z=uSr3UEtLg^3T!xChPY>2dV^zn@)_AR)WLBmD~3{e-2$Ib|1uOy{$eBCc{&4u-OJR zBqlC?Poj|!0#xY?^r{zWtqky$LU+u!JN8PX-4PdsKp<+@EiYg9zj8g`gkNL;_<$VK)ic!6MQZ68xab<4 zIF3A_Z+uku*a=D8DP%0PXO|ppySg6~LDu)|U3Dn$te1{{uxadiekV literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_ElbowForearm.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_ElbowForearm.png new file mode 100644 index 0000000000000000000000000000000000000000..f2f2dd8d1fc8dd2b2c90ce87c4ed32d78063102b GIT binary patch literal 59041 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 T&E!%#fG>MnXI#a>fQ$bFt*ce- literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_HipKneeAnkle.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_HipKneeAnkle.png new file mode 100644 index 0000000000000000000000000000000000000000..430e39916b6fc60a2ecdbb77e8d95be8380d4ddd GIT binary patch literal 263104 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

&(YAxQ{p~G2C_*B=7Krc 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 Np@Av7;vz2oe*m!(0nz{f literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Lumbar.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Lumbar.png new file mode 100644 index 0000000000000000000000000000000000000000..efbab57b27e3b609f4c104c68c55a9d50bdba00c GIT binary patch literal 56979 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 bJ-|!Z|Hh@PdA&n$5#qeD1-kSM_U8Wp|FfB~ literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Shoulder3Dof.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Shoulder3Dof.png new file mode 100644 index 0000000000000000000000000000000000000000..49cfaa619f23fc3e2d87b9c630a5c1a3c0394a48 GIT binary patch literal 189496 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

HM>a9B`!yy{uIlZUh+4hgVN1zFD?;kZbW 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

l{q_*>@}V+uPx(PV)+{rA@@J+hbUwK4R&-Wh$WO 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�ecrEh| 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 XVTk4*^;Yvl;OC0=RZQ_k+k5{HW9l5B literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Wrist3Dof.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_Gymnast_Wrist3Dof.png new file mode 100644 index 0000000000000000000000000000000000000000..43710de1cb20ad87438037558793329c081ee858 GIT binary patch literal 203518 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%0DbVnwdrc#N$7_zzCKBpP7~E4534eToj7kXD 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�dpFc?U!GG|ayMi8TdG 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

IcI(&KOEWxuD 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%0A_MMoYCJgs*HoudP;U%=l(vIRjkbGk)x#J`s! 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%0AOB8* zmOfjU;>#>fvlTCwETP1cz*EfV}{DK>nJnKpnz`ejeFQ z&?lVK-*Ncxvs|0ogR62wi1$lXqlAoObglfY_T^gJ#mB`c9u1tnb@s?cyD|7J7{pY+ z)iO^VdfL|d%-;OqxFvF#oKK7#vWZ0a-jOp%R#m~Doh2Pu9jp-qu zwPn#j99O@WG9#Cb54n015iO`D#MT>a$1xof7*hVgVXIS{_B+HZa{sEF$^dqp>@A(f z!M4D}pz2*Ao_Tv}3PsMZU$b;Etp8{jSB@iGUT~E#a(uYzh7d%~-@FWq`eI!@4 zu}OJ8S7dR_HlPCZV~z1(*g3^+ryQsj>An5|ttJs&szo-8pow(I2Oc`>fc2f%ZF=fN zFrr%oq@_a6G#uZ`kt z-?!&@&PI14Tz%B4j*#SWl6Sy<8+uiuX?v+MGZHN$SNgk)sBVl^!{swQZ~C)y8Wo1& zeOGJvz1kIf1(HH~y$RyyYfk1 zEWPV${Pw!rN958cLGr)`xTdAF=7p>10bUJuyN++@N?T1oq&9a;=ozzT@f!oe>hKW< z2CvfxoDk1F_678sT8FQ41e{iQanI_=0&GKvEXv*-tb8VFpS4<`x_y~Wi)(5A`o zA%>j1`-a{nDH52w9+7v@B-Iqv1K|p%wqO|BwtFSzAN)6&6hR|yFX`= z7*NJe7eCgdru{{PNk&cyU8xO^*}CXw`hnBgaG;qEIqR+q{afnapR!Gvx1XaLzfmRg z&@7*{d0MnZ?JG5x?&$Q*){hs(kT9U`u5s;hQ8$N>I2GUB#fa&{73C48mxS@-wz5tv zs%@*EqR0Mvw`{$Mb9Z+9L~NNlB*W4&4Kg2Ha$b}k_=NBX05-A`HPB2hAh02?4cuC#g5n}E(%huMTJ&V@J~AKP3(64teHgLeEjKO0p~P7 zL`0TavLs(w-;_{}Y}eSH>p~SXiJ9w!SMuLMh~Tsi_+~VKqdpO-j*ATvOXLu%N$lzE z>ydLq$NYl&yB!oxNIP7ir^tck zqw`E?(q}*L-^fOl1}6-B%mcX4QS;R|`Fk%09&YdM zer!(HXndmVR?*ORkTsUIt?=o`i1)&W$f>S0>3~W&{P&dOS~`|b&3~AGis=d}QrrgD z35lA~CTH#@Xmw-6PcAb@^fXh$-DN>FvzBw zR1fbPYc-HapYPkxB94j$U1L@XxyH}R@2wh@w(M_-J^fO1H#c93B;#AroZhgekvUQ`;}t9Jp4)&~gb7sMa}3YX_z> zt~dJl5**u!uYqrR@}lQ+{d%1ixQg`O*m?dK-p~ul1cN8_ieOi)|6O`rHK;SjWkUKv zo{TkF3#|e29czLEXALaQ_@90&682`?B4&dU#feAX4JseLY?(qY1!rzVfyvo)uh>lH zDu8a_16MRSCRxF2&N^7`R|;TJ9@)&avDyCLfcg}7hrMH0-eiL$!zdNa+2@AyhJw5t z-WUOw0STz+fAaj|L+AR&MC0qjA-vM!qPLX}eB3Eyw?1UB9nR(q`H?&%3yaMns$^o( zSy6?#{^IbzgUPsm1AFpB2R;Fb#O2f~O))$D4B<0E1V3G1aRmR&x%xuEqbY@vlJ(2j zRNt!lt@a@+Y&F=F5k2gAm*I=3Heg@wUigFp{98gU5cgj}5yl7jdyhzot`Z*5OBe;4 z%3fnxy&QPfiH@+Vp8d1bD##c<7NguKL`V3zx%UOY@KV2oj5X??$yn+8W~>70#|&u- z8qpza;y(>YVtG6pi3VJ4m_OGNR8o<>jUr?Wz+W5hX| Q#6Z7P6g3pe<;;Wr4|EB#=>Px# literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcCosPhiCurve.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcCosPhiCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..b40f6c0726f6feacd0a7de536b3b218082bd581d GIT binary patch literal 30956 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 literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcLengthCurve.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fcLengthCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..82ee9c80748ced7a27988d7de4d65e1f367315c3 GIT binary patch literal 20917 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 z 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 Np7vv{Dh>PB{|EGWChY(K literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fpeCurve.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fpeCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..fbe8f67777c34ccc6c31f1173e730ad3a6f74e17 GIT binary patch literal 25651 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%0DA3nC{0^S9oH$O_`lBucuJA5J# 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 z zFLHD3@9Q>rpVg4Cu?%o`yFTK+>H~-la>qj6MRg(H=W^C@;L%e z?jE@U=Kmn>A4KHcZ>*cr4bq`)wSHI0^!h-Vbik>BI}z|BSv6n`L1j|SwWb2DRr?#; zQJrs7+iCtpNi}2pfsIn#H(E-l*!a>c>-K ztFfaF?(nWay&btQl7erQ@BFuN&a2K-LX9$UP0KTt-!nmZAl`%3W%T_(J$Mc9KM-Zu z7e+NMkh(sUI{&S0!RMU;SB)_zJhB6x+&yvvs1+eAV|~oSz|{?_U^?D6q#SNx`NkTw zVNw2yiI8F&shzmZVR?>3IbMF_u7+C^65nCQ28qF7>SW1#*twsjLc*?~=2r27(C&;d z)LJif;R;1ebkx*WNqSrD6d-sE%rL~`fByWydHB8~ksh!9{16syW<;!uTDYgN!88Gq z`xJFQ1iVN5xbXl$s&$tCJQJSpEAksdw~vjTgFnMqHT6l!=*5TXjDQ8X3%oNvWSkiU zu7F6p3{ueIpmpMVBrp%}k_BdBE%tE()N2uc;9dER&-*vJkCy=Vd99XZFo9PI7X2^D zZ!GZV8EZw4>nAoDsSF(5w8G&DyRzRSL3dJP2UC1cEN*mD(g&`|8F+H{$R!~4&|#GY z52{}_#Lj#?R?RN@7?Ph01 z%nZof^bY6nU7Z??dw^@7T<;iTNjy0CVzjQ!UtLsYH6HGv@UyJfn!gb5Bt@FbAjL?2y4bHQRTp>@6|4B79 zNc&y1?H`1VzI1w`PR}snT5s02j-*{87GHFpClUJ?Y{h+#)#}vtpDX7xU4nN-MQr(? zPkpHT6H=Bb8QwP<7zT|W2L}=Ib8=Ms<@k|04THKr?0vyLT7!8ZiQXg2;mO^jNkKXw z=0%s*Sg`Ti0g1jFlLy&1`f7Jc8$UkEqCT!XbGP;J18;DsI=xo&yXxm3`K&VyX&e6M zwGG}Abv_!I$+axYfZ(lqUMEcI4tRIqy^t=O6^{mjCwC7I4-bzj5J|pFHblU?!dUBn wPwqJePwpNb9v&WH1ly+%>V!Z07*qoM6N<$f&@m)>Hq)$ literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fvInvCurve.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_MuscleFunctionFactory_fvInvCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..67430914a7e237ed7f02ebf92bd98050ddb5183c GIT binary patch literal 25558 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 >EJ@pUu< zq_gRUpo6W|Na)+4wLfugT(K+09J5L|$KEe|3zj1wv>EE?0LPLFfcbs|Ia~(d?RDnM zeJup8NbJ9V|MH{@f}v2TCTHvl#e~k~V3`3zcPk;bD$X-kA-1NxxY*(B^P|cz07sBx zptx1A?+g1FmNkc~UVIHgM_qltfg$1&UOnSa((}C#2VB~|f5*+a^Y60Gf<~-#g7@rCMSJ&Ko+1tvL9J~*W`!EjZ93=FMP6GJ=Wjfe~B{0@d_rK zd~TC%&S05u%I!a*9RnFwGHd8uqd@U9h>CAB+-j3taa58IZZBVAXwDwtdkQK8d>0Wd z1hVXhbBHJty7x}a7i_ig_xP1sa!{~g-}$o`8aOgJ2>|8~J8`bC4~O2O5c3X*ALc+zHdvoMHXP(cY`P}@UT*+vEyp^k~a@48s>)Y6Z6W~uO{~Vq>gL54PqWy)ae@#Mz&E~-TfMJ zv==5zP?zegu6|*bOE04TFe&s}H+{Xz1N9U<8u)aLO=lZ%))dA1t#?wvF9FMcAZ(o) zxb$LR7CZFQ+j)NzFEQ$v^gZx26K6FRT8Nk*ZgTVV*bJsRYu<49uI zyGYHLO%7nY>s(F2RGW9m^)h0(A}V9&JF6|lF~A|u&FM4tNu}N*u9B%=ZX-N_0!2b) z>YLxsJw%h26zUe_)52sfMM*X^G`t0COp|~yIyOfu$P#QS%sv&=-05#5PkUb;8 z<+#v|_4RerNE~rF@C^HUnyV|?CNG;{w!bj7Vp&7WoS%(=2E#xQha7%_I(4B4Qaq

$blXqqgU&!FzicgmeBdz|u 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

1H## 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 z/jajF52HQ7wa(gLukAY*eh(}Cjdi>4zPCsx=t5?uRsiAa`SXz1w~T7ACVNMQBU z6M(Y1{QAE$Ig1-4CoV4yfWICHIVO$mWyI^jb0v)aa9P(+cKy1zPW{^`Qwxiru!cOO zSymm(G)pT@$iP~ABjefA=Eb8b1_Plqt@w7)J8!>p4u1Vg3Bvbc2cV}IIuAiGvqJp* z{MoMM&x90unPh0E%NiR4JixJ2hL8iAiw}t2G8#ThFHCRJ1+y6|utk2HuaxQgWUpUP zSoo-0YSpUXyUkhovO;WXYO1GN$nH{=fuW&~hV;Revby>ebz`8>scUe7;MjvxSJ^m8 zr!YL_kf8Ia8ju{COEa_HX%1K)`|7I@&TE>PkQvD+pY#}|Xyf+&p2Zo#}uC4Wb z=0JHd7ZV@&?0fL~7^Hw==U9t@L=FdHQmRg9*e%pT;q=G&;)-k-UM8h8hYP4%6f zox$^4=RF_~QEn8Y`6ZmDVN%o-g8$v+>arfaFz|^3?t>ORXaI>@nx2lUHE=~;^DFNY zFlHXcSZVG1^PI^VGQTkhu+7%OzlDW`yYcP@aI&a0@TBd}1P;trz~DY0y9oQmu3j>q zhVy^wOymnpJHYq_;U7PKtN_;12W70o1ZnprZ*OC5-c*LOy2V$sQ1qFH)rm=dneQgh z6P$9;Ed?GxSY*W(CIKrP3e7uYyI)srm?7@iSa8>%>~AXGCfo<#n8R-5h=8XINENNS zfrDq!-4wK@!DUKA8QUV&$42Ej{t>2j4jSU;IdNZwip}y}S36vXLhYh&||)p=-MBgw^XhuwK7U)oZ&*qmk_{sm1}l>MZsrpZ03 zvBOL#P3{rN;g9|O{r*^TW-|uL*sJ)W(31!*YQ6S?5Ct|$Ti6{Cq5N%Y$_>hxEb$o$ zoDtj_2|bhU!MMuhaYAc@D5JRDN!+pm6{HkIhMwAhb+e1rD)TdGLWTbMZd!`IN7HJ0 z&+L-D?Z8R`k43Y}-GJGUHz=O1QATL;ef~Tl@O8B?BCcdH3*wTHpLP!vJbdH>U_O89 zX>4p<17hh$6x5(ef;fcb1Yd#Ci#=YqLnO`2n*ZUWX%Q#`WvF-rTCZe^y=F-{lo9)efSN6anZ@*~|Q zpiuC$^D3ijrBk3}n(8|q7B~xSc8#{)u{BOL6V?W`Q_sZ>Qs322ib2;U$-cz3u(Wc7 zP_8d{R?{rW{UQmTf-FKfdC3BM;zS`q^rrOpk4OvDeFMlgQmU2VJO$*fT@*XnUY+<^ zxcsbMQ-_h!QrI{eE(3q)mzY3U0_lgy%tdhGAHc)l_p|+a@OJTP^;4u{&~CwDa?;U$ z*G<_=xANzJB<3bnaqx&f zdt8y5EES2jiJ%Ivj5jkVHiAh1&LCK7(c3huLwStL4?_rxc{rw)fScF29}8>s8@3p26>3&la6z`Co!lZNrZd;4b~LtJ1{U#)wD%U(aEENH5I<{w(21|jLq&CNagU%re|}N*ey0g_ z*1ar`SFain@hCP&Z8R&IX-V%_lKPLAgin8wVEmI~J?O#0OHpk){nqwT)ho%RM!vNwO#wXU ztWPqgMIQBqN?6e+__s*+q$X%D+{P`r8Xx6=MO)AEV{8)PqcUijI%CN5i443mss!5) z-M&$RWCIqoUXz1G4ap4k{x&~n*^%E!DF1HZ>hiP)IIAza?PAk+@a|06p}V$P>M;%T z#^tB%&&(q(154qw4eBW=&RRLnH;DM|CXBLWLr7YGj1VkBcW2orv09eaCpVbSO2KIZ zosHiaoO&uUes9l|nZS;f{5%(^20|TVlFF-VPmWI(=lgqtIMpf685Ou!mF&SPwwv%i zMa^iB`e@p0VuzRHl-uv9{HS@h2m2XZoa({AK2dYN4@n7^%cG|8^`c*!4#MuVylK5$ z#+p?2@YrY3x@F4B!bM-pN|c=A9UqGpE>Ar=B({*fmv4UY%FUr6Njl)0oo}YA?YRBT z;`&5j@I6C!Txw|K8c^>&xHU$G%Yjq<*d_!WBhGotw89!*Uk8$0JxmO33Y zf~Ci+m^27(KN?yt-e>=F+4&h-v3M|t#Q!oblm|e0F7IucQ5HhiT9-^TF>6dL?WG)MMLk= zJ98FR^GF)XzW~qt6L1Kz+b0(4*nb-lM z3qNq)i+P~=C3t3!lAVVmb+8^(Ax-2?`MOIT_{2QojtTstq115K_}z}Z!8 zrT+3<{p-%FqCX){H>8QHix;%|zF$C26d+TB{9)XNz6jCYLr2cd!c;S{!svew!l;SG z_d00YLzM1J#qqsUjrlaFSN5szX|pT5?C!O4{QtuO990Pz2u*6@`e^FSw=MWyLVwrn z)rngVu;Ak8Bm(%VRP_>`21AM4w&mH&mU<3K$zQm?JozjH$~IgQ;I%{Ok`)u8FwxQTj%kMmF+X7&a`qaeHKNO$QqNL42Q0Ih?2>$IQ`6cb z0Q~Z<*a+kU?V>64c}^`z)*q9y-zhOC#u10ItzeFPp^_FOpcez`RY;XxRpJKINHZ=* zDG}oC3yzbzdKvVtc%v2fR6CgWCjF0Fh?<{pn&V5HSuq!t*_6!#ix-ds!y~Nv#a~)Hkfeh$^V@hAqbUq&J&8O> zHDgDD+P~>LzzVWE`O26qwyg7f?PX2o->eS5)|$FnRldt#t9?HvQEgUv^mQ*{C)=+k z6pi@?*wmT@^1G0>HgOO60+EC(277oOI#}wA4E|`TA5-(j?2CIwv6&bYIa5zm>LUgC zb{CIQX_U#Qha*hM&IkQ#Woo0>_O2Il)~m(^`3GrQ=vi%_p5kY2UkigH7iL`=QvoiyH36}F*zh1j}=wGTU>|q+5#S-c2Y{+5OFalJ)YcS>zQSp zw@HX##qV#}F@ma&AIgS1vcbfEk~*Z5 zXlh#hPN4ouAT%&0B6+GyaQ}Fns&hSE5#pX3j#j-?cQSNx9SN)^5rm8{u5+FV zUCUM6ZXExGc>=Ix`uU#$_^S_A)8sMRDDfuL!E&k7$bv5}*|kG-cu%R|bn(xEBzPV!afv5uM>6#+*{R(uPm|Bw^~L49t}Y#aci^nvDq zgu~D)dXW?dA22M}PdalG>=8+Rk&*nbn`X_bP(nWLs7B+NmHpF-z{bB358{g2PQ?^! zKji=;e++LrDsjoNUbjT!fjBt8ZWIM9K&Vuf?9<+&0K(%AiRp7t%QuT+(Q{(bAP?_cczRMV<(z4%)ESc)t%l=<1sNW#KxmBYFyy^sJgZ=)q1^`0(2Tn(>J0F&n@T(nkFI z`xB_8;d4nyBsnLkGQcHz&QowaMSz6g1t}XQW9G@ASo|;ZhYG&ezmu&Sgb^|u(pQkx zm#j%T(eD}wk4W_4LD7E|_>A>d0v)K%l}kJ=m!M93W4~lc+#`_NW}e2pC_n8k_-Ay3 zSmy#>5x?9mvjrjrf~*+p*v7u?=SWwB2i*JGkL00t%Ts#tu&tic1$2nh`>+}+)Xawk zi}~KU_9M+_{WYkbiy*zJGa2+(!o%q)u@2^4v0aq}+rJ$=%YT}6@(zBW?`yeka6Ci) z+@R=ug&b75KF#6&I2?1qE`>+BZ=!X{uCrjd3BPLn(cc!hL2*7`)w`!Bf7m;)8Qgf5 z69x(X9ujifq-Eg;ub5jtp2;Vn4En|xqcZXt=xbvE>K+lXbfU`u09n2Z`z;(u7t0l0 zbK7O9i=%X?2hpuX(&U3xyQCz=BqsXbRUrbnE8$)2O(7wCsK#ZsPxlvXM*B~9s8r3P z!hywAI`-s&{WP_=Hx3O|1dJMUbMuLeP=EnumegQbx2SQjcU+C-jC}bmIEZ_n2ohl3w-TF5cYF&i;?eepH^&jh8}qG z^u|)Pgl*O7zPos*o74<`G6`8YKRpn0DmBXbHGh#%sutURs3=^LC(l#GnLFRF%w0kE zi8B3`-e!#Uv(bQajbGj!3wlmr);Z2mgbetqW385!MTrcge_Z-ct+zO$c!p1jNx3** z`ENxOWh-GK^dz%hJ>e_Z-AL$kG#e!)bx?o%@Lj*`ilX+VHzmzP4&53{FmKHr!IAFV z`GX$9f1yV{tJr)t{{-9QI=_WR72Wp7yiuDxe3@Z#t8-?2OKP7Re4dA4r}Vxvjn2<= z`sfS_FXj6k^<&nI`S4g18oLwrQro_YEk_Jrpy0AWQcE-5GxopcU<#wh^St^SE3CKx z@4xQ>gI)at>>p$f0D_Rc7BFsCWTQ-y>Fw5Zh>Qh~F$EE*_cA*F^#lB6kDc7guYl6*#_{xpL%Vnx$ViXrHNCjF$S%GyhU3 z@LS1Gm6cw}1(Hp51Zy(a@(UNcF!^6p@6}mbMLsCr`k|J6Q zdFRvW5R;rJsiKDLdyI=9&o*Dpi96S|bFJ!nI@{>(kDwY>IT#uPS_7q>asCg&ol(`Y z$*IJ9HtN6PzUih7ji6bMT`zBwu$yZ)%9U6B)vzH+9}j^?6ve!wBW*p>QKOD>f7h4S#$77MzkmC1e?9=4UmY@KBrpm)SK5n}g%aUXKo`$f z$QV43$*?NoMuZoNYdQD-7*^Fg8eyfzHxz$R0*YlhU{Ew2c|AG|imK7Ek?T~a;^st} zaO>ZvNFaI9QZm4jyJr?_-qvGVZ=)UN&pIY83t9h{3nzSRSsW6%a%hk)Ux9b3{!Zz` z9<&A~99P6^7$xRYXtxq^td_7COfuGb#%3dUq1A3^>6O^2!|uUuh08jTlju{lWys}n z5TuT`yrSZGx8l*v;L*FERw?PwwCU!?^)|8B z2l|lAuN`e{l!}Uqpl3MN-l5J=CZMApv4OeQQgeI0M=&Uhv^jK$yQ>q}rh#y0o`xlC zn_F#CEIao(`Cp$W!s%M3zR)qxye22#mc-)SL1+Uj+TJdibr;kxW^z*I8$=|y7I|s z-{+~Z?38#IsztIl;=4oZqhFBTU0uU1rq{u*pf-GQk#iO{MflsMy~up)tUPrnJnLm= zUq`yzmIbhnD5Vq8OeN8{TAgrVNyqkN z1yRTD&Pyw8ZS4qgnPk3(n3xz*TL!Oyn>XFQD?Cx8$;@T;caLry*4lTodqGZ|WE?X} zs?UiheIUmESayqpRV%=5Bo}*hqOf+s!rcFQW6RR*sd~(M5serMZCP{_%{y z8ZS8-yCK7$=TST|K_dlwTjR7UKc;x9pm7dURPHC;NfV2*;)!p69@xf^Pb|LfLp+Qs zL=5$y^!b)iGc^ve)Z3AV3+lO3@>;#elXykHzuE3+O(B7=T#)Cjf@bt79zSBa3pMq6 zTv64$v8^!7n7BLvCUs9s+ep~@Pvf&Sf2sZIiL)K@%MqCm>s%azxzEN|%HpqUmfs?)*NlV8`&V1$I>UofCY^4g_-x|u2*#oVvakl!BwC7%ew9$L1qsTlWasOM1(FqOYX zEqM}3cN{;X%NNudhlW23KzJvdt6pmMjuhU|Bb&Qw`@xG^I+80~?(j|O*;b3y)=Qa) z)Z;otc0`Ho|4V!_LdsQkGri+?vrgZ0)x>}_9nWXyVId^?Jj!&ZX1jQ!h<_P0#!$hFtl%fo>aYaiH;p971_E!d{6xImvL0KRUd)=Kq7jA3-7%cRYAl^$y0_Q`i; z5VW4Jx_D74?zC|=^QB#GXP6+t zfIHCQqRxy7m$?8Ez?Is|&%uO20p_)mu&)WGd*xq5)70B}<49XHTpr4uBrPFjhMCwX zV?|!Y$6I#9ZyN_-RjbE99cu3E8StF|l^&hUzsGCV4xVA>}jLRzD9`>6JVKQ|A?udnB{Vy3!P1X`&GC zmYn?R+kE@y10v}LU%t`3`wnt?|B|-drad6iOWgoTD)4~o!-FkV-c3sZX@*cSs@u=9 zn+BNbPxXiOp%R7oUzI=Q^XIHBz{2=KLi78w{vH?jSuPr_DE4nWh^`N$V<=s z(f(yZ&J;7WB&J31J6N?p=-pmOm9?HE$Jt|d4hq3s&&`D>$bR>$qPpp%KB)oCLLs^K zafSTpV_CZ<9?sAaofoe>ovj`5_YY%_YzB; zBo#i`D6?DHnwnDQ$on|xxI9L($dv=RvL;SmpMeE8jr4eN9UF2CS6evC5ZxOEzh7DC zc|eLI0%|rT<>eb`fDL?At1xwsV!C>O#GvTMHnn6;)69I8{oNdRW!fh)Fs;a%!b0jj zIqw{i;5iR5?md&5k{M{t`vu@C%QAt>ftwh6MEIRk>HAaW)@!$*-#3XqW zAS3|8>4(S}%W=n|6|KS#(=AXx2X4^fe}Ex|UYs587zGG?c8&WAKr4r>(f1RijKzvm zQn>HDKsI9XQuFhp+y$yj>l1IcGk(;g4S&aPmk0<|cl$CU06^ChPbqMN(A3=8n%T!9 z+2fDw$Wee~*^0fHL0M&b6TWrn-P~sFf9+s;sbAX5dSBf>($(f9CzgtHL5Ph~9f~~d z1=(+u$CoB^jM5G33M?mICXt1mU1vs9v;V`lvM=fB=m1W|<6wVZ_Cnh%$hOeg`{Mu% zjLF#@z|Dv3e;@?#4Fsy`){{WGG0F;9i8369PE8%Fn@~WnjjyixgA1d=_SApggX}=xBx*xycc&fCAlYM z@fG_R#(c|)&MyAzKsHrU3?QWr5fAR;XB~%t;eG=Fm`7W=RYlxnqs*_ePg4}pknJA{ zC^scaZ#ajIgcS+7Rk6czns0|S5+s#n2zk3>g>&egfHHq*?61)*LVF9a)m!90#7 znc2Bh4EoL9Y@G$R-2tX@O3Na_F(|&Io|1>pdWnI3P(nOCH5jfDf{!D&g~=Uihl36t z$%jw2LpDOiN=4w_Dx20TV;`vR&f2hcsA#l6Ek~gnqc>$^0#GNC4HtTAOmn!S($w5j|eQ-wuUc@=2R64herJtoV20|T2%dfc}73ZqXg zxi+sge{x#VV?ci7@pwwtJ?g$vec(o0U!R2~x@gOwJ2-pp)4Rs;}W*LZZniuZPei%zIqv-Y%&t3Z9uYrz{P#(MugOCS=!&mi!(Idw32 zTm?)I#2A;SDc-jZ#UxT1Jl7ckli$DmSh7zgwlSFxTBX`IUn$*}s(aJ}gc5E|o@s&e zex=?15cTH~x-{QkUG zO*L9-;L(V|??!N=ph?#`=a&CXHoIV9Mge=(($zzP2))a0FcJ61lrME zkCA=o**{8Zof#>-OS~I#UvOn@jq_2}XQqbh(`%P!JC2e-Oy=J*z*waL1Y9iO+I_>L z3>tS_3DM|N%-dc=hfh6&CERAkDu%}sb;=AJQ zwXYp}Ew|sC^nM(uVPR=`jAu%;64U#0c{!~Pljtw64j=|ma(7WARZSU?7T{a&-fg)4 z!Yx?Oq9?r^A%;+I`_V!$n62~^h_!4&k<8x6nPadNChgM~fXVA%ZDke0w7Uq{71)LS z4VNkzl+;ta{OHr~BVXU&vVFrnWTCphgOI238j(@2|s&dgUc%`=N6b6gnX%~^-Yz9naCZOC}Y=b zV3t5pf5>a5;l5h?jW6~sbj(}}MGjq0iNLJn=P}?zo|}I)$yI9F0K!2E-hdPp)>M#Z zJrqQ6_jpS$^M@FbJxWfe#h>$vvmWy&1MQ6M4u|r?Rg;^%Iv_O=Dq4H4NL?o&cF4K- z4(P7|A;qYlAW`O~yI!{#c_tNke)5m?60o7?0;lZ50rNaydzRjr9F(8(v;sz4(#`Zs zgAzR}Al4RuJ*ouijALVF;yve2o*o&w6SXYj_o^gSAS&jz@1XpgZnSXGBJgy$oSVww z-{92yc@EiMjS-RP&l*%KO+p&Pmm{z4Hgsdb*0!+rE_7K5O9%2bZCH7YQA~RLD^jrApc_f7a&#Z;!Mc@)OX!TKB)ttzeoM zeW~_2K2&Yq!q!d2QOac!Zo!9WBI%g@l}01%FSiz!hccBqpBvqZi{Qw|N5wsi!GkE_RHx}NTdbD-*`$&3IyPXVnMNUiSf(%4Ukq3B$-qsN53R4vu!C1 zU+?$ROkDQ-G4aj7z(7YK(K-7>KmD`nTVUeD{t3+A+FpEp`4%mLCKc<#4Sg1~75oZw z!9P`?mND+YU$|VqjJ7#m)0Jnm_7Bt)aF|0vPxJq%B@(*XBU(_HVZBE4m*Gp@ulO#B zPLtIhj~mNZ;Y7Jqfem=wq^s}Ps*jiJo92u(mfnN`BQG1}$ny~8N1v|Qf+R`k$T(%~Pz0(2W#7Mj z1skcmr^i))jZ~I*zDbwYz8Muq6RP+9RB==yiEGEtaJj%$HpR@1bEKPwuQh=}6rdUA zrlj`UDK#n zHQ>LZWra0LHicU2__#BGK3M$C%Q?aP4lrkrXiYTI%|xKrBV*59J0gZI&k!*o>8Ad_ zA-_LW8a~~s>$eEno}E+!y3QCI7-#6pjW8fVH{IT@1eO@5NV+{D(2WdM@)bp2Ba~IX_B?iSx-=XJKt5P`CHokywMWA=&7 z0WgCDEnW0j5R<28_^n3eY(d8c)wRrJ!p(A1JD}K4gLwb=B_VIz)@pj2s7n;**r25) zdAP1oy%%(@u=uA^dF*!uE=VZ5GekNW6A<4Sl+A_MSxmMVdZ{`uz4kfh2U*B1m&I)` z0b!6jD}@+-|0(qizyJ(#iJ{%fRHsX7jb+cu#^9nH9Swp^6-3#xO-3xgh@hY~{%LVJ z51)#A-s14}v&e(&#dCsb&?~|8y^(QEB-#SCH_KK{RaFzv$a(Lh-2uce#GD*~IF!y5 z_`)B!>fR^XangavZM7O&)w6x~Ipg3aTkMj;l$Al_8N)9UC}aECQM`X7-nw)`rFor5 z0;EQ-E0MU<@wxKoIh@={d`$BXg_gLcO_f<7WdcMZS!3Icg>rEw;{@N@2fUpKdpY&y zeeV?d>e0~!;txwT-h6U;?eTI>)QhnE6annJU$A3*Yl|fA`@+%xxaPS&50H$BbYn`K zI|dpqBqXca)HWRkNUk;r`z?9JOv3OE9iLq%BU=aG%ku8y)kRGaR#?m{GhPWyecmN1 zm0#`wp&UZA%!o>F$4>W;0?!O_d#e_2cHCU@bS+;(r`@XSk|ab>p%Weexyz^2aBd1U zO0z6F+z4Tb9Dtl78{z!4>&@}UHZdqEw=-_qES?Xh+D2_QDmBh8jPBT6P-;Enh;CGv zPw$hXnG6=os_@xra-L6`2n_~0aEnYua$=7N#|m48M*RYBL?>habPLTHKIR79L>hUX z57SiWCO}hxH1O-K9=)uU%_;vsEP$=;=?m23NgDpx1n48|g~NJl!|o*L@^h^MH{LzKzA&W)*k}sJ>{dHr6Dlk3ii%S?60!Bz#xZ_NcdA zAW!wQjwD{$(MSNC0EW5z6IYHm3Wp9KjpIi zN&d2t?^9xb+@Ix3aTDK8!~l)QU^>6!XP|5T&^TRrL86qks2NEHbO38ik{*%v+E z*NJ)&v|UtY_r3?9iY&O77ZUGupXa^`$O;qFAOQM#ArfgZI0#{Vx=(y~nDMdt(=_QH ztf$nkGRKJJ6UbFK)0a+$^*)ZJTI?yD@Aj@QbSe9E?&-`ePdSJrRFvjCxsF%Uv!Gyj z8z|A?6kLT{4z6=oX)dGcw5UD_(T47DP_J1DvXof86ig9|a?5d6=CSWV`MJp?%QEg^ ziAMjjeR+<;sQVO*LzpWVU@SBPj&X#~%Sv`%zMN2Yh){-PgmCC$%o1`5X?6njry}%3 zrmY8-uIQ!u8F^(yb#IJ&Y_d>!rXilb57L83r)>slUf!wJ*tab1A}%wSQ5-m7O2*_& zGy90qXKk+;@0gMq;py-!Cw4~06mgxXj@m(iKt%S&h6fmsu+A6Qws_A0TN%nz%?8q% zB_Ha5XbPiqQH#&N%@hkz1&`0RodutjtV9-R`;hNy=Pd7VBL+hX z#iDL7@|J-wT9dieCBwIi?s>db`fyyB0eNmCH`Qnx4JrxFG zs=qkQ-~R;Qi2M0AQhiBm3rec*dMtkZY9J24qR13?~l zRsrrR(<|aUjrN^m?LOpo9N%0*DSNm_G)LExZj(w+Xs(lkFT)1)Je5 z4pUrWE}Z4E`%*VTFC2n5FU6#711QH*Sn61qYl7d{uDU{{du8;J|Jw5T zZGT4lycE9*WRRBn6-v`WZBul1Y6LxP3|ZxdTycW9)Dy#Z!oHnX>d2z}UMpHY4RE<| zjXTP}Zo;r|-pI?sAB?}%Si>}d!Uomi7S_uV86uy1?vyAyOu7M0BG?Ccize}fX` zGY}rcH57SukU)(T-%q5)dtOQH>f9d`a7=O@bU+aLPuv?<8M+fTU^ zzg|zqujQaAw9yqn1JB!F(amYo9H)HioSM9mFSrd9r`WGq$_d6qfM@&|( zLBnVM@;x>iWew$ohm#sM9=S{KwfQpgQG6y|l7xy*3-by2a3|zDCfMFWdF!S8P!yU7 zv~`k9i4t|FcyBQ8Tj;LKq20&RECN7TPbP!3T+8fl{}^Hzymu?<1!U4^#7UBP|7YIU zXTKh7-r`YHfmr2jjmOs^@>jeYlSinExax&{9p zkeuvgXwt%PRb|!@^Eg-X?4;4Ut28t!TA*43zry53G2<^Y9&yOKK1k2k6Jvsqay|0T zD5Eq0zMw*fD>}G`tGzqVgT0^D{%BS3&0WHcmJ9udI`hgqo&FO*8(jbbT6JB1(+^|q z$36+|L8T?P^$bvT7F4{>=gq;KYi?u>Ux`?f%j`h)*Zp2O4}ZS=f!)?j9=7u^zK5MQK<$J_TjYEV@GT*y0A!2i5C|$awwr zUQ4+Q)yzMIUnaHsQ9@}$EKvA)^?+6Wfxeo!VOeFV7*u%5E{0D~6@R^XXu?RHK0mm5 z#@Q+Tp9O82SGR^4>}DD*04DrSsY3YAZ}+_WU?tK(LT&F@m)Hk85Gqc60!EupzHART zZPy?=0CZlwb*$myW+qYhq?S5iktPiLRY$&#bH`BIdH|4}yfObulqZs|UB!&GHdK~P`4+p;K+P<8np=SL z8Yp{)-xJg_LK1mYC*JnU9GYtiH&`ORDhHs&8GZ2X(VW%GNcND0kyk6kwpzWF=fQC6 z4NEToI=5Xfh@AX?oV|BA)&CnmZXU@__Dmu(d(WZ>DMYp$BOIH93P-ZC5}`;7MI8Ic zKBbV1>|>Lat%U6FecqqzdtJXjf7kbqTvz3_p8LM<$LL8QKccCh@mKfzI^342{`e-N z7eEz(2~h=GF3(q1aG`pKmxC`li&C62=}zMMRJ8p;0sg-ekeq?16-i}##wR`N;*hhnaoN}(y(8HFgIALm_i)!#mh--gVKAtd4w66jTZ4$Auar?ag zSfn^Jm;G)1F!FjoZD-bknLETZ%=I9DNkM$X$|*s18gf1$``Y-PDy^%tjS-xLCLOp4 za|I=T@*@+6qC(4L;q1v#j*k-+$mf#$Sjeao2he52N1Bl1Fhu>bcc5R(IXr*RJqfAd zrb2kRR)BQ3Uv45t>(+{PUuI+UCdcv7rQ+?VT=M?+ZkMUHKA`@JZ19$n{QI&{JNqar z0=u?-F*nKtz@5=BhRSG*kC&lB<_HT0Kr7gumi#n%H<1_H2dW~gQ z!#||bpfq~E)RB}#g!HtYgzIA`Q=}| zWAm$(7;0*_$4kU_xB~NJg8PIT-2nrkgV<^MFnQ7JN=E)k(CVWw z&8mc7JdP?N6~*v{78xX!Mq;wmRM!xjjsG=|4McP9#*!pBvcd>|SkCB6NvE`5)zu-T zH^Pqwu88FMSzO`lf4QL4b;pS@_)MuB zzMjxdoE`GiF_GxlsS{8J3mCp2)r5}1gKw>SFa*1n7)dSl^}k6%ok82FqbGE5B(Eh? z8nSq2T74(lzJKQe4a^5mC2C@zC`A$k39yS(9v+?-kR5u0jQ3lu?uEf4GB|=&YF31% zI2@uTSr-4T4B}s^tFBROrvLAQGPX!9f#rM+IBqRIN@|6`@G4s%#J6O!fdG>|pOVj1 zBT>4hFZ&*F&!`7StP`XcfOgeNvTPTAVk1`C#U9zQ(%ai>3MRRM<_>$?I*6x}6lwc5 z8Qcz=5izk{h1oIao1%GXCFOT%Ev4ASfzjrLnpcmu z_E?u|;pRLU#ru`6Wo-EZ1wC0h*^2~e26_$|r-03uH?Fl)tzGnUFxr9L;HRjAYfv34 zJ41f&|C;Oj5{6cZ&IgkSsNmyR3p~55gb5C)ZFg!FPsB85kgr1vmCW!!veMo);n`0O zaoU*Xq1=1P+(E2TWbfZjqcjIZS0wR#>Z({O**@ZkN;_rA^Vrfu_+yjne*$9lG9U&T z%=Mm(47#LxaiW>sU&^OcdVW-=*dBywxv4^Cw;t5;vUTP_5WbId%7DaU^|srD5Cb(QNiCdqUoe(j2W~h6 z*7%PMSyxrd zP7|mJlI*ms(HCDAp7u7Ss*lV?l(uBlwEC-QaK^_<7*Zyjr#jBkS|QpH84 z7jj%>x%rnJa~ebUyY7M_XmeOUa?AZu#s$~!Z8H)$2cz`~wHc06!xQ(N6XNzMi3{+X z{|ZRWY7B~UH_(yNh*zmqXcES&TN-d`jA3DA<#kK2+O1bGnm$ppIuD0S|G(vMP$wsq0(o+XJ(Lji+_&u4%367m z01t%8;-bkguog(3M24fpX+=AUQ`l+U{MI;S#?qefZI}k2Q9dGK>sx920jDLVc>ZZ71FtV@T5e`H0tkRkdmh5QAq;{ z`R8KTj}M^al42V){uD~kjoLL3dlE&o_qJER18%zD{d))S@+JDC^bZcofX_{r>bjbO z*H{|_*IyUkz3lok_ucxhnpr$|TmsctAq%#F+DCRn-G^3+#?pS<+2Q;K%Z8fNdzQ6v zCT{AYq+QEPdj_A>85dN`;EH$XE zDNR#=yuFM|SN6A8+L1~yg#N91hi6q%UjJHkuID#aVt^}{Ri7<&(VLH6iY*gEfkZ|u zV7eoQA~qlAEk4k5NM92yu-OnV53r}+5G}=zhu`HKI-fu7^B`-yHA+htvGjhV;J>*R zlCrAB|HEv^*$Zg9m{ysRkkDz78+ud8e*|bj7iH3pXEr-Cqe;-SPd8C}ZLiVG; zRG6r!{3u>_)0PO!mPWP+N^HU}mqIx-N%JT;L0T!P84w{BLdH2Z;cGA;hCw(j6P|8i z<4eh){+F@A5%D}C;ddS6xHuqG^k3-eZ1ZoE_9lI{7yj;)n*^!b*Y-40&{NYAWZXA? z;CDulW7{e>Z{;!V;On>V5v-(qq{$@mc(hKcwBno~zV7GuEWBUkhw|p{BhpT7;f5sa z$R|gxZ1gdTarf^4qt{R(2L)y7HQgifdxED=9@0O1I*gk3`PeTj>7@f(-K66qwVoX7 zDJUk*eh(#@-6Sc`v)hFq2104KGu2pQ*oo%J5NMFys>rwe;Jz!wsCQ($1h{EZhkqNt z#^OOowmPg176ImKN)hJ_(n|sP?>8PeBp(8%5WC&=|9-}IWGR+BtLoAZ(JM_AX(Fj3 zsXIGUBXb5*iQ6JyBbo4ebKo~6Fr~j!&*Y?_w4xWLReLWP5tbZ4syOH1VB%qZwaVCX z^kOuvSHgM2TaTj0vy6;L^q$Tr4Ux23daM)K%}-fj`)tW<%vn8!?692RbHaGmZMBP) z{PUaCBSAwpovfeArQpwpiDHo_?r(uw?3=9lr|oS0PL7jr%_?rxOpSZNbe!x|HLI!S!>j9KS*YjGEYRS85|Yvd+I# zc|{zHhyNd;rLkv%6pw9nW3}e(v?zT)u_zXq?9{H`3fixDKVa_RQ8h9sDLHOa7&ysN zRqS~aHC{^T!7?WDz+X6Gs$D=;zin)1r?mzYEM-&GncMb5a@1s>-CJvLb@7r}&z@5^ znatp&_%wCjSwCw_KwvfJ3`qV-QeY8S5DV`6k?%ybJa~D0upp_q4o+fSL?|Z!*Ocbc zOi8#B4i=5W-Cep7J7awn=|5;6V1QG=i-Gh}1!xCX1y6$d-KjX8=NI0aSjnQ_UvK1_ z2qU@Q`#F?CwUKs;)*(yH`Ccst!Tc*JVQuO@6tdU)E)|A|}T=xpivotDf^Q2uLpHmct3ACdPvxPIX5_eANZ1%J8m$2zmG|E_|e zxd(6MMQgA_1CtU?A{+>Z!`;}i*Voq%73+kIz=;PfWgn#TdkBRcG5|vWF9^kjnwNK% zL>hl5dSAD(=>U!ohf2VC7{$tc?~E4?HaEjry~aMK5Lpuzb|Fz=hkNpcpnULmeX_~< zf|e?Xy*`I@+G9Pz^OBM-Aeip(GIZMwXDyQ@?=cA2U2qilJX|kW7^n;KhH_h`YLH}{ z4%w@o%#Z26MyoFXY`v@I@Iy(XBOq4)HFm>NM{v%t&6IO9N%ez6i?}r|VeWfI1&DwX zw?j>8NAM#6^x!+qQZ`)GtD4&gdbDF_@GCal+&m>`if)` z{uQmg0sNRx7S+lIfD>zp716vrOD=T*ye?r-7!yG#)BT}DckxDW&Zp__yHiFh8Ep;C zp+`Jx%$@I3N$tLGy>YN`Ch(22)Np4e@>IT*MCAU-uS6^9ENz`9O`=$%51%9>h^{I( zM6UKGI0{qJe$DR+&T4rUNA+;@9{Hx5Wn)|D=g>WJzT1&6?>F#YDn-^jA$Tk^uWb3K z`A8}F>~SRCQ@e|7Z4V(_nDj>4@1)}#2X5D)!!fJ4`+`?$POXTyM-g^v&e3kG4Abp% zekmIY!%4H}5t@<>JwB^kE?c*)#k4fsC}j35mjHLct-GnwnkN+PEt&Ms&DyX_=Jg9f zD^KtRpHRABEybF%++7*kp|@Avm&_dZg>F$34r#t!=iZ#08}TS{{``;{(!*RB=+dcL zufuL|F|)Qu!w%>E1mQU9$<<^A)#N*saKPNWps50??bkGhg#waw1hRcTQm{M99>w9(BU7+x&Tlj`xDFKFgf)d1lwo{(blHfu6`Md2 ztKJJ1&IuO4E)N=`l;$&$OE3G*CKI|N`YBFec+iB`0}CHvcOC`<94_^A_duc(j%DQ? zCU_Jd0D|Yi;{XY-TFBRz9+m)li=}W|{~y{^zQAiK2XE9am1S}8h6sa&+AP7A;!t*~ zLv7&M!$XG#)>|+}ul4q4XYu>iOeQ!mA|> z)CUWqWgz+&i-paN$*nAx^-7hzj#f#gdGf{Z)|iJ2A>bjqEVJ^&r`ur@)0zAFoI4r1 z^j{1CGu6Uf@{JY$mY_X1>OlTf1!X{3mdp8+5%yZK`lo*WU(6!Da^E<5^oT|+c{O^J zEtr?nu}Ia`L#iEaDdTN8Wo4voERp35SF!e;I`nUGsNV{Jjc( z6LvQBR^;Wl_QzQJek?s(JNZ1n_f`a*`==ubXRqb-M3O=-Kii=6<=UUMYY%Lk!R1#* z{493)2+Om;xn8@*2FlPMZHU@ZS8a#Jr$X!|q&-8HROE}X#kO!1i6 zSWVjB6b{QzWuSshI{Qcr`xs6T3L}l+FhguoLDu+hM8|;oR;$W8DR5@0?2!h8n{$Nl z`+meE`?$T%EMOQ;YZ1XRC`+ak*lQ||{_~+a086TlU0FC$ljd5K*jXmagHZ#i6D)#H zN*ec|0tg!J`XM1TuxiHn-oegTn5IxFt zyX%yRRT5fWwOc=b2d8?1wZk*w_26Mpd^uS-Rlf!_9Sn9?mJHO|){ z;2r4{HuQJ&K2GwuCELCr~S zb*Q&r5A{kTqou-vPJyHdA=Bih#>MM>aRhm(bf`Pr?N;?aAx>-r`*xMR)(k(6P2swI z@(cB6-{s}PjE-3b&kSYO3A?e|Rh*+MSv{lN-W26oqkd-QcmKV;X7T+rk}FaB)VQyb zg39>?g>ewn0LqL*Xv-#h3X(1gnQHjBXKKv2W$Yq-MP$tKs0QpP9Z#|&26qdI(J&RcGPMPj8W!3 zQSlK6Ej}r_aL7N8`o)e4~EFV{v8 z79wwky*7L7?LMOWfKbo|an&XXTJ;sNX<) z2n*#UYqoV{l&41zK8=s~E}L{Li5o?0qJlPUK{KJ&v0_f8j%##vmxj}ht+b?*j__)x zu(FkbHo1a?EBKvwb7T5F2U8>QR-AwYO?C2@u**rGa3gtUNb+=(@@k#ACrdY#Q(HcX z5VXRTaZjuB+RKJ^q&bo&6X*)DZx%~)Ij7d;DE8Ua7@=u7 zw1E2k77`jmB+WXrqxhhT)2d?ip#XLLdDsCEt4VUz_!A&hhw;w$o`irA;SCr=N{5Hi z^RE*aviCH01DBVMlT_SA_gt1AGGCigo!q6r(C`-ySr2X%y`~I@IBUz+t`jVrRGM}> zy*brg5~g!gul*!$H(==D`upeC#Pq{Oy$l;a{ZMQfs`22D0Z@n{vqzJ`@C8a7m)9p6 zhKUvqo}*r>Sq~dzO$=#y#~4*}ZL2*`QIKjLE*g_&Wk_T_d2(udGCUku@A^5xKdHMk z_E+{i3#iH9EVWhPB%2$ql&{J>>i!#n`z|_;LS;CmMPruqP(g%?&y(JgWin6j(%WQg z2aCq~l)CY9>L+^J;82$He#*s9Rg6V&Vst36mg}XWl-D@R3HZ=0BlXw)jZzXv;w7$t3r5=^BGT1(@@-K~O zxlLe|eSL_qL?NAtN<+>qzmP-%h$+r zWLgYC#yuri%}#TpWwYL2*U6jz2FS$UNNVn92$O4ZqDKTmp#M*ilQqf|KOtcKxyDhwXhiaT`_qjk@IGFIAnE-Cz^nH{^pGUlzPEk z;P!9F(K~5nkMEu>@B}k3r?(jKb3=R2s^BwaZdWJCR1-4!9$TF!g76% zGnrG^fX2lAXOx^_i(83vWhmno6CoYHMHqZ#=-u~s>nfk(pq!Xb*kb3iUBuJ^r^%C! z3iW}#^x^nacWLWKbY10^aiNmrrN@(|$1@d_*#)fqsPGf|oYffJ((z!gks9kwj#_%I zetXQSs3D~jJ?<&CYVlGIeYi=qqvnU`A3v6!mV{YEFQ`-P+gy1a7e?crO5a9ue=-~z z`mF9On(5hM?V4>%<|F&&TglPiciAPr#eZCF6n?EOT)I`t7fy1*3(L>q7 zHt^=)Y9xZr@#`ZN!I4XYb%1Lihu4dV3c%&LCv7)ZVSbU@cZw@*D2Y5aS4gleR6~G9R9+Czv==HVBC!rF0$H+d!`ME0%R4Pa3V zo+@BCt=bIExzAJrMTy*Y@RF1yW|F3~Mo;WR2F3?4hJo!Nt;u}d52=}h;cNqR>%nk( zGjnqpR3^_%wnmsYr16wZql#=jxL&H3o)Z}eAoS+mDfr|OU$#hfQbc}H*qXn0-g0Wz z%cx`F(h91Tr)=ggw-s-P5xuvEo63zh6}eBQv2W+rXG~T|c1Fu8WK1K+n_@gyYZ+BN zWK6wL2FoIZ-;t_6HYgo}ID?s)3HOb0 zxadn5HTwgel8;=S@1EUw;grhT|MCoM&J{4O@jHiH5S$!#QX&gCd5B^H7OvhlAHjO3 zYzbQjixp%p>xRc}lkkQ8I|ET-2-P9PumxK?qcR_lTe!Se{?Z;5IG`YX39wuBtxvZU zz$u}>KVZxzWf@yXUn*f<;Zmh`*bmMxmazUFPVN8Ac7nLovg^>q_H9V5khR!W^?2`T z*#eML=D8VxR-)QJGuC5b8*cqX)mBHL;4epHX|a}sH2dfqGBm>7LgGc*UA~w(iGltz zgunD(32EnL%OqUtdLC;^^zjh1TrUS_mB`~V)0^fC*8cjJju+l24|+E5f7(Y_0rS#u zb5X=Ek)fW3>-`a}YRH)(kJq*M`O}XJ$Utny30-wA^;8_T|Tu3VF-Jj7F1p(45muZ-d>t(U+8^F71Aa)~RI zVWBfOeR+eYNZ0C0z6nk%s1oBWsvdjvu(QOPKm)j5DQ|W}S30P!Rx;1DjKdtXzjN+F7Pe8 zs-Akr2ic{~^Jo%PeDb%$yZQU1xIAwWv{bOgE;DZbqhg|~hD+_EmI|t!FZ0g(^a?v8 zjRdb+6tfy0VNu{6*!0eq%=+`@yL|*&$jw0Qf#=O^l7r|Lnz(8+cmDZ4rf-452Ia0}Q0v zUf=j&E(wE24S-+V0Y9`m(~WqDR$jC4r^CwS$181G(6*@Z7*aU-018&0H>O_SSm}alFF7)6A1;e<*RDw-cs;xrl^V!x4OnZG1ja8a?PW^lsc0f~jf~`rV}+ysBbn zl$6OQlQs5Yi6)FdboB4l7j~BDLnI?jg}>ZUS=F37#p$vWis5Nu&bvJynE!2-VQ@r% zX~OHp_AL$x`#1092YVT>+oiwl*d~-YE_&k}B9p%o%yZ{!yXim8(|>3Q!&C=w+h4h* z-8{9k>6iAqbWzS^yq{E|t^Om+LR{~UtvZ?S+hzh|dLy3meAhM0s|_W-=I4;83eNKt zzqhz5O6V}J=5cGwW2Fw4A#ZOkE3sc1bvQK|wWqA0hguE3n5S?d%g>Pj>K?;k`>T1m zxjGuVv&>9o`c~Z#ht*T-0&IDFZ?*tz#lKs6!4@F{uEEN)k1tU?1}`?N`Rb%yP*!saS$9W2Qoes7e57H zG`K3@xY2~$Tdnmg<=>saEaDHWsQQc)0s`f?n-SnK@dwOxzhz1zDHxFnWAHM60M6D9 zg%}+j!4qV|p#MSf7{bT;8=*vZxn~NV&uO_741t^^z@>nCT+yvzO$;`{6OwImXAaPC zr?eoIf8m*;cTSOAgO!d>=(awW5PDd(wHKOt#m=RDa(N#-N2~)8RH*8-z-lt++;K?B-Ug&#^&ol4^W3Yq|gm zgLb>+MTx%r>&|m@1WSx_6I=Qrciqa3y_m+x)r+T5-*4VMt;xNAcit-JH8cDZ{s}MPoO;PT|N)0HqR92ouN5=v$3lY<>}jQ*sgeXY(aywnsY6#(`|=T;rwc{ zQL?YuKkjH`>$wuVBvT;mcI{QZ(V$zwE6cK7HRo`1E25lFGACkqDiji;cQ(g-tj{I7 zw$kIVYCiWq9tNyb<+OiNWa9D}aHQINXRq4erpJ4rf2(y`?qz?lY0oNibD@`$DSev` z%10jJthld^e>%OxOzD-TNOph5Unmd5yc>mqGF#bq?Yp%LxDloTXI^19WOptjE3ELn6B(vAx8m#)Mw;zz0yrFNsH3P`VNf0k^=V>h5f1+4bL$t~?LMn=DKzkm*>lOT_?Ep|fC>{RA?fR@cI1fc$;=pg&X@1qA#6 zEEcM}?QD0H>8tETHx_y56Hc!xG|z(a>p)q0L-K0;CZS9x$f0hJ_w8?Yl7)Gta8aW# z%{_MlJYjjwgK8&$_)VC-yep5y72e+^Q)O2lkPk+UY@hYdcBM3`!zXp!ko4DT_(X43 zf=AHpIM(guoDC|BO`9NmS`sVeBTRlpa4tAR{&Yt*7Txb`4e!3uJ&DvD^H-llmT7BY zSD*C-cef^;%T5gCd)d;M!7S6ROdu)Y%wk7h`zOoC)t|4CJNed^W|5VEcacztZRFiQ znuu<|HL?zRa45XX_!P#}cJny2FNx<#wTt6*ikG5k-Eb&ZD;$cWalJY?-}OoX7VGlq z0VPo|2UtsuJvD%k7>0Us=w7Ab`kn7k^hpM5<`>2qW$NxZ3%%_qPMD=Y`>x%zcssX=8g> z8ESf^FoziK#MRFo>4H_VI;E++(a{T@MnjGlP;6@yD4LGDD4Bj6eLI@3pK$N#&hGf% zr&SFOKZ(03-lss29Oe34{>C<`h=rEw|sey9v1Jld%xDqehIV~4l~Qse-#<-70WxdJ$^tD1qo}%cQ48nZBDbX zRiA&qdLG;zkz*fyJJ~vr-zBO$vIKFdMQ?wXD|HsUy+Y7OCah|>fsOH82 z!k4@Yau%g{GSmlWY*bHBGs3#y>(7c^+R?;n%|teRC_Zs+ZT3uCO zl<+yP@b++@Kuup!mP)!uhAfL9oL=eZEkYunE}aCZ5$=|isi~|tq;6DWV~0WIjE#;% zMsV5cwfZQ9gyN$*@Oi9!mWBMTx*wufkaLXsS=r?H5-rlRr&363ZA+y?x*r9u|bLy^3@^Z?8%mS?Y%T0gZC;J0ek?)e!Mhj4N&KOpEn z0+Qy_o>Gzky_Vj`ADK{6XthjGiq402Jqg&<0jnWoq)eo3`DJ+*OdrN0q6cihu1zZN zWfdG!;ko!#$u9Jkc1Q@wydqaVy{~pTU5qz zO_4aTu7rvyoc)LQ$wD9j!x0IBs{@?(kg~|`uh{r}&brjGPq%7M94wdwOR*#rUx@^C ziSEk-ak#?cvtMhw6Qf%+R5bL5_mZ;&E%>lC-K*G!lxWjSEEDl z_o9lgRR=gP2U*e>=3qZ%np_nD#HfJ{&DaA^9#bPavTe6fEX~f!kLLj884(05>hlV6 zoHw1r-7{jni8WfeiTg>~Y;vI4&kN2e1KE>~b)v^H*v)i@AY2R0 zl#Oi>3YQ?&5c9FERVC%UGt;;ew@ll9|EzQPSYkS=A`#g&|{+y#9PG03t8moN~o8<7p_d28zP($o(~lkK2!KA@ERbZj4HxWk=rlWaea5}61IqYR+SNxsj5$!qi!;UqezhL*>ilT;JQ z+1+g{xPP4}z0)HAvO$4NzHp5t^an{&tI&gn+#f898o6w3C8Y+J6Aec*}cgJ&uD!bwS(3rm*G&2{8J}a`N)$bjD(v+Xd*h2clZ~r0CNQC zDp6^_pc9l))lDfr(n%4rt&-VrDp7)$w0WDc*Gp|jqKENa4qhawsOE@$(tFNote?8> z0HQXJvj^Tul>BOhiUTJ)^egXW?#JHbn-Qjc3PsJhC#wuDw^gr6Mdj~~Ew%k>fsfDA z%DA>vz)H%DIgHq$vB@BNtX^io=Zteur?H`Mxj~gZE9G$ZdfN7_)Bg}S=W;5%G> zMO&XO<$PNKfwkrHgr zsDuJz5>-fkyd(qOP|4y2SPV%yAA5FP#c+MzPbMv4DV1CmyoXVHK=%cUK)my`p*!H@ zinS%1PrcYL6>ukbpA{`Wyd>@%_K>F!@xOP_quTOrbjO&MO2|xJ@Ub1yx>{NwCfSm| z-1b+*hsH-ZbM1-7PpK8!!ID&Uc7t6F+`UcJJ9}WzU~)$bSHp=peEgD*`}$OE<&y;R zF7mEdz6Q72_X%t5SMdF4=g#Ln45L5CY^2itDA9SCNQ`EXV*_yoCzInJRa1|kW0d?N zncCk8)#5mO4L(<#@(IY8T7T+T`TVG=#bN5a6>CD?!3jcve<5Iy-A&(+4$m*#ntHr( zq!2KQF%-a`hT5{U*1C99BgtAT+5p}EX7WYeo5cOVz8%06n_2W?ideuXGaT8WsOtnj z`5i<`uj}vv_JxMVR&&KFv59{Dm^%11Ch*%uTJ(+0)UugT+&7c+BuAuU*+oZZ+rr(N)6Bl+lFqF`($?vmEStiAN$c<9t#}S+(wcmFW&mrW#@EXTfR^;#@Fqx+AAj zO!Fbb@BD_E0{il0;Jc?1+FcZIK+9Rs4Q@pH$RVqEqm9(WBa{M0Au`8|#pV&7^75L1te(R2P(_;`Dq(GQpD<8dFhrfk- z(qo`9*lT07-yIO+#7(z<@cR+ zot{Y}NLqc-dCQ0ahR=vcO$gS?W1n8OI{w|$C`R0+#?Jt=^Cx`T^$!py9+n2~az38B zVP+;5?UVf?w&yDPFaS5i)ihb;nUQWmHYnW9O$2~( zd9cw_VsDtj_uWod%yQql%J8wA`hi37Ouw-e&yR06cJDB6@T8INPLMf@DEH_thYq|E~;5jzBwm*z18U_1FGqawP376l> zYcu9UIM3bFsde_|<85ou_<(65L1^jF$vc}t2h^N;t(S#kbo+b3;j_XWnE4U(1Ijx5cH z*a?(sgg(2e1mDC&@oBH0|K2M>drPF@OS6s3vNN53xRDG@(|yK#`B*IKj$p5W&ZFkq zzjOw9=?^KU@EgJxS}obq((0|pKMZBl<>h&sWu!Nnk_V37uJ{!`n(jAh;yjSht+6k@ z)lvpbd_~)#)m#VLz^dKN1@C9I$AI>w542UZYXf3}I8z)uGTK#bKG*2EY9&95M>N7I z1d1zp1zvj3soQ$%6m@fp^{?L}6->1*^PRzYs$gb{Ugyr9eH6cN!$juglP4jk*}_+} z$`vI%)J=KLukLB^+W?;ZZSA?Wsm*B1 zLXo!>XBQvO?D6&nC?C7IxCt^JozGM018<}1amRQGGzoB{n=?SMMi-kmkY-}r=9~RK zR?fYQ{=qA6!BZS$rJzKmFu%HD1O3Iuj&zA{OCe})GWm(W6cm_guB!q(`sRpVIY_sJ zlO8WUezeh@D-7!J&K(T0@JMV%SCrywp z=>&|aQ6zZj!B@$;X$>a8>3LC09wR+ZKske$(hjA`+YRsaH-*G7!Cm$>EQXL4FDI+> zQEbU{E4x(`?!o~MYP9Q(r;}TcN1kpG7xn&pWAbuV;)h)*)iRCa|%&`Y>ijr(8-lVz$GDGal^1- zm%Lno9WZ7@_z=CAq@*3ugX-&SW(e?+h`0KG{!w698_+8RR^q3ac0reICmgWtn>4mB zdGAfPAJ=nbny}yJ+z~rk0y4;T%R) z7Dw%Kj*-NIysmQ7=E+zDEqv*p}R{j4I;#6MBsM3mHWMQ&gb=!ypxx z59wGUU6RZ$Y_Fg2#u)F)nfu=(r142w#~V9Q_;NJ2Y2YhRmNC$EV%z|8ym8avc6f{? zc>k&RI07x|8tk^~fu0giO`ueKUtUW7P~Mw zRyXbGJEGw(D~nzs_>JTdu2V?*!0gQuMO(Ro5W{*u=qRrClxfIXGB13tn*n^pfF~8BSY9m~QE?ZI{5)FKnP#1V4tMG@k#H?l zZpF3Yx+Ewjg!W&B59o5Ya&UG1c;I4^9ILnVT;y~9)q<7!=MX8e0q@n!v;lKw`f#4C z{O>CHXvjnyK0LyMlugxtlLMutQ1hbIe3g;n(?)+NvZEFd*L6a$Mj>X!Ah{;;-_@ag zYt=&h9^J2wNC`GbS5p68-X^AC@_$r5DjNZ+U9U&l*ei6G!mMa+Vaq*Ci`}#tD;4!D zu5K~@clDxvu%eu)LMYDGTJwG?!B;<~Pl=seweOEAL7$v~Y|c|H#y#fmmyVXq`XL=g zip9~YC_|9}0iv;i*&b(!ZfBZAXPLc6eFRTMx`%BC2mU13HpDCBTnc>5f8x)!I6Lz0 zyS4aOEL%~UI}>z=yJ)Dnv#+dkpLO5!<%tg6w#id$4pckj+~j{fWEwDBuE4QeOtsntj_pwhrxzXU z{spl*ex7V8EVh<>1CE^C~TyA>M(w&d7oEG@k))a+s>qS zb4ahN5q-C1q3;L7H^dh!1r)iWfq{WXnJrd>bq61lJewM&L33_etw zLsNSzl9bGyPxa1sq1-sP!8X;!?ffX>#`K}+RIysC>5D1w{jdd5{)50)n>m}IKsUWW z>9-Zju$SD23a}aaIPbR0jG|wf2x$`iy5_ZQ%k%|Ds(oS1YB}ndc|*mAJCQ2sooXhe zL##_fzMBo*Q>qVC@8-%VhCMHL!>d?&{s>!;ZsP9$igZ{A&pwksp3G7%sor7SJbJc` z*N`X12vG+?c&l_+)C&$vdYk9Y`^<3Gjy|&KqVaYmYw9$;76_XFGro1Y#7O9;VUC)z zsDNvfb2wLWYevBBtu&JmqBZ6zdKx$%6D?`04MvLeY3j~&Z#GH_2$NVlGDojHzA+n4 z7|hBO_+toBLgR;J!X@@8>8aDGpn=zNv*)>}J3dSDhH`G0B_R)J2p8&)L#Oh^ozY*= zDH9B1T9@_`PEExSswC_Y*YeLCMu}n|FgWCH4dMMIA#f${c(n$5k9;GPY1aZ4<}~o- z9hlbu6Xgo{XJ)kHppSz^uem8x3hWlj0w&H0mQ7G9x|XHv?_&FU>7>g0CROGY!kY_a zdZ$Dm1OeQ{2C=1F7Y{tXBOWYN7VVH?UE3fNB6ckq!_a19jQim>fl2%n;+Ye`$;EhN=ED9E^K zl$zd^KUHgNl(sbSsMI$8Y?(fi?T2y&Pc;G-tfX_L$2t$LDJ8qV1G?UApwF zXd0L!><$7Wjag;+pL(L{uaoBoSaTM(;=ekovexnfRbA2XOKb!n-;NV>1l0dDPXvy? z1s6+pMK7C4G;d+i`}e=T0(pvd@$n;PAMOPq@ShyuXkCkY6NS~X;8I1SYGN}xT0s)2 z$?w~fb7Fs1Rb4tVZ(%(lEf^T5kiP4#$&KUmSWzG>Ndj z^0w#3;ts7~eW${ME5+8qxwchXNVgS0^uOOz#JXZ!{#S zxEK%3kd@cg)I(c~qM|BnAN6&lTKENG+p6$FOpjqEZnf_OJuSb}^X3Nr4Qdo>v|*18g-Y7WmIJ2J z2K)7O!eAG3CTSt8dqKMD{;N7lCqUqN$G4#c{5%9wZr%EIGLWPKWa;-YEbwWp>PiuQnmTqcCdeLQ+AT=endznxt)fC;QQk!VUUIzAfAa-#H%u1;Nd zt=`R>1E(E6Yz&!Vgd}p*K^EN{*m*3FpeL7Cbx5T$! zB&p=bX49+f_gvwVSbvY)4p85Yq~&LgKOAANp!1>lapC?yrOBP+-mymPawETh0VI4l zURdc5JY242aA=AkgKx-zn|rfjFONqq*S;j;iAP{Z?SpY7VKa0n>mv@Uw*`=+zG~lq zflUF#cG(s~7)0TBylp?H6X>-1+5N-Rc$?lew65sEytG1g1WuQHgCvd*6k|R7(s<~` z()9%C7t2rLdI3LC9e4TdF}c@fXw#(!S=iYSySUh*Rz!>IUgPm~>!g|5wI&^>8&|?{ zUmVN$*pXNxq0~_eb8~at!Y%Bw7v+5c0b$`&ahgGfbOS=iiG>Xb((yX8<&`mOqF|s} zTXU!*2=ba0wi1QNkQ@s#Zo2;DA(}b~mP(I${UC>g5UD_Woh}lAVnUF>hAud&uCK+` zK12={s{Xw7;tUj2=<7*~*izQ#PPRZJW2N(pmfeT$_T2ev#Uyf7D78s4z;;A{H+hJJ zYt%o1m=nS`LRg@hZ%#*aSS5ECNYSga4%3~Lb4Z*~>aJ>X5zT7_uw$IgPYwP7Q3dd2 z*JLf%5G}csC> z)S0Q38*eWsx_6f)NSj8y`^;|^$au&|10XFU$mbyb@~wtU^gl4yb3P{^9N*RlqbuCt zUQ@ec86o$9xnW=|F~b+vHd8REJKJ%8&h#@w=Amw`(YQ{QD$?+SkV1fbt8W+8T@EC# zSb*)x6*FW9vc~0fb%yl3@2m50)C&2Ge}CgGj$8<#y`k4SUG+`ka;uov06~(mH%)v5FhJ&yHL(*xw?+2RIwKrATrn=1_J(LX|JT z>Z`&H>~a#-ws?U0bKjGtyurmkSVT?_1qzeR=;Zd1y?{KT{glDf7aZ@|Q*nlrtT#bN zbgA@_h!%PI-UYc->A4-fD8kp8Ujvso#~I!qv08&L>-Q z2EjQN^R;Ul0Q3gpGgp7~A=kj$ z8jo`M&j&`{F-_Z#bObsnM=$H-KWaPdB#q^t+n=qK{OqtDR{NU*3jkXLS8Rv75!%*P?fkD4ZcX30 z!pV+2qD$ZafrV|)_xaSV$?6w{J0EPRmE=kse^;bZ|D(SE=YvLwRClOwtSKVc<(!56 zuq*_120W0Jjm@5EKGl*|oc4i3u;8iTzQA$eL}|q@Gruo0lf#HRR!;KA#nK|8Ne(pu zk=_6xE)7W$kzM&xSFk%C%vosQjMM3|D|Pt9ITTnzMNf_(jkLhp)I=?WjJC}DJ_jog z48UsGlpbM+EnZUWYf*E{|hrSyN0tzX}4r!vYubS_u}L1jWr{AKM`E z4UpL)_i9s=6v-anx#SS;5Vc&}P`bX;i3ha!lBQD9CgQqR!mg({E0=46@^zgq>Qp^? zG6YNo=BamXt>fQ(vqvK$3S8$=czz%U2g6y<51uv46RyX z0?IWc%oJQEsHK#OT3_*>$R~!MjnAJy=Zk9-f$41h5t{|O;IQ^kMrs<#4!uA7F$&IAz#%M{sqtX}#1_jf)ngvbl{C9m%XG1~3t zER1tee8Rd7`CjNfdfDn&)-!$lq7NfO!o&ek`uN2KNK~EUO+G%u8gJ{Kpe%4@AZ&I2 zh{mn8v6Wp`rU4UsP**H_4<2f)181+*`w)FJ+|=3bJQ(GXckf4ofqf%bV}D$(J&Ump zoosquiFsXLf4^u($%3>lUXOAkFLq_Ac1;0LInefD15e64V3H_8_KfaQ}Ar&u`{ z1$Vi)E-Wxfw`6nXOMm;eSr4^>KX8oY*kGE9Z3`X>v~Y~fJHikB;W>N2=l6d)y6Ui~ zz9xK^MM84v1{b6oq)WYslprZx64E6hDF`l7E|N+pA(D!cE+r@+t{^{3B^6i@5JX~u zCBEagfA9y-K6mfE=bV{$-kEtPnvt9mxov0u5g7Rxz<9V!dS3fMyYwVx?o?Vy(;^id z$V7N?7_it9V0w4hEomDP;fmdWJSbNTERZAE{j-@Z#mjAVn%BVj902+h9e!!5c5uda zOx#?AM(i~-Rt1KIAvsjRTBKvyt(Cs~B=p}>j3n~#{%uhyF|kOleTl#<3@sye0d!;3 z(-R|=@s}--z6b!J{A!av+k;X92Rbc$4%f zdTtJE&#)}aQ420~DuY)&jK=qis^;$>&fdt(vK^mG(&RveJO3K##HMqTpJJWThzWZdQ9SuvU~;=%3K$6R&N~CrR40kY zGcj}8Q^e!mM1il;wAr)44$8|M^QhBhWePtf~z~81tZP&Y@dU2I2t8y9O^-?34&lvE9Q-lAloe((v${tfSF1 z2?G1IBQ)Ol1X=!tcilHpHosJc^!y@^d){uGVI;pe`*fUR+hbo@uxeQYgaG*qDh!wb zR?xQPa(e2p6gjJGMgj_(1rMpaC^@e&yuDGDF699n45B&XN||c^7jQj(0L*`!J_1{&3p}M^ z^xP>|pz@z|d(v~Tl>DW8Y+3+GJz_ylo?YBJujeyBjwu`CZLT(?L7BR%6rd z)YvVZTwM*mQy=f|4S-|BXy`~d)&M6;2M(?~Dg{I>y}-v7?)(NpQG*--5TInJ$S_a~ zxx1haS`j`Fk840#9)pRdFnW&SN&~PP_uj38o z2qk(pVSX?|qf@OZ0D!|dTFL%cXTGz}X>DN2y|V^i+-KskHF$q&)497PRv<(vyebq6 zoc;|smS;|LX>K@PWAa}bE@_3bf$Cj-$OKb=ML@RE-XSk-he zL@nh31}T8%{qHs>h4rraDsTURM{_V}2wy?+FlapV!!1W@EA~aRN6dlc(vPZ(nJbY% z&mo%`v6OaG#`4}{FC5W-$zDAH8HET9ErpmUAVWn)VXx{7wq}C+?-K#9bl){}Rw|S32#!xeN zZ(Cjma3o+?ZGssEK>_QxY5qi`v8|#Zx8&!D0ZiCUGJQ8Z7J%FL5gNsBZofWr1}TGc$V+- zR4M^WQ%E08839&3K+3Uwzh`OIeat};%6 zZXozYO%0Cf2Ghyg07*8SiqkI5Kit`7pp&2NGIe0|^n}58~r^`piuhWP&Ko~To+Z(`E{Ir#$qm~tH z7AM$s{?l!s-r?5m?Ysl3YwdQ^HMr6pDc8NzBBg=X{+CJz+9uyBoj2(6K2$0HD^R$b zihvj2;?)4I6QNGLin4he`G=5@KXDeBK6azMY+KTQW{zXq6c`FU%EB-q9U-QJ+O~u* zivi(*;$ory0x!_vh?K?vGNN*j@}en!A*b6Fch#HXKQ6QW1}usS69>@9$ZNsLQzdMY zb=~^(wg3|dkE(pBQt10`%@tOxfZ|mH=ypjc#A?;VEMsdLIrh+fW z9qj#Mdm=$&inx29#K9m~=mNm;KxJhkE7V`_X6>fQRWWQ>k9J)kb(Y-LPjCFo;Z}?e z57`QK=0+7fx+?VZ+gZ#&u-l~jl>F3QJxeUcm|FXle2SdMTZ}#DNvynVS(_V|8(e;} z^tiYV52`@D9VQ)+JIq*NWOs#28)&%{>dPNna06RhPWIUDHsP7A>6}1aXWhTrnlGliPN9g*NY0&KYB0^Mr)TGtglapY^o{{XG7waiTgkcD=_fY0KSVnkOjp{ zIZr9p-;8V8ON7aN{64pTI5{~Pm!4et{LME^H9qA_SyY4=)Fs7o)|`+kK;<}MTLR5T zpdVlQoTy*RXd2}V{)DObtq#nF}g)=7ur zWtJogpWnL$Mjp>FX`b>0GFJY2p)>toFg-0$sp%5n^R-v|4g!|2_sD%&MXU>iZ~nYs z8|#ZcgcM#X^%>a|ddL^__sG^haeVaI!-=|=5%M7J?z;bH=F2$J2Jvz5@I*he<(?xh zGmh|QW@Kzw*QHOqZL-=*He2;TsUjZ@8@6vw!Z%&9I`An6*x0M-MFrYq^H2BVPzRM) zSSdg)3|V{&;)M%_ z_c}-Nl0@?;SFNiqM{*Nfa9LpRqyE?CZ^uPhy$gFOHR}eTzBpE@>a!7=D#0#YE=HgE zx~J!|J^F!!;i%+TGww_Cf{?^;Rl$3GwvbF4ALh1x%nB`W@4Vgv8@eDW(c9xTc6L+L zL44*NBp&P*H#ax?dw#%qQtI&ZMe|s)nOkJ5T|s>B%K@@J%JRxe5NFU(Qrv&|&YHsNVaw@Ag*j3a&! zaDT_Rw{Ph;HaBIIuSm0Crz*=zIy^bxoe46|uC=0q8+w>z8yI{E;FmMiH3s z%Jn-u@z&xwW+OZw5|)AV(S>D8lP1RkdAL8-MG~$|1(d{Ig zO2i9QPgUQ^zui0Yw}#LK(cr@nf(j#Ci2^n5L=SV{xu}k z7zD9?(uXfv1T&Iz4}MRR4UvhbNta{hTjAX}{`&1laCC03rmybeql4(qYV}++7qYea z7GKX+Qk!Rb`OhMPi`J+!K~s0}DzwRrRjBEBb)JD7(~~>J3-9PcJ}E24wrh_DTL7r} zss|^^xz=^K3ip{awAt4K0|3Ys8t83Hx=S&<4;Q#T&=48icYenhz&W zlOvMdpn)d7vu-VGYHDsnJPJTo@4akXS>TV?L69h0DxEc|ve4~W)5+z!U76aZOr**+ zxKkWG)0cnVg`?QPPaFMMMNf?VE8V*!g!!#Y^t7}Z7u3`at*p!cI_0-R)7J2brH=zA zh2c6|{fdjPuVl{hY~)s_-KWv@VkbSpzk61_TN@EMA8eLlieT81&d}#K3f^HvaiXd? zOPeNQIj$QFo?zGl=zK;2`>O?4(XK%Pk3No#zYU{GSuup3X4_uxyju{!i0uc$Tu*O3 ztVoa}TtuaWFxzSj<0R?>Ywux<-cRP3-NKw(;EwIx%lZ_dnTP?BkA3;-G)X?qOYG{3~hIRikA*d z4;YJaqojAusHJhYWn}!sVG*=P-}}c-U!9hg_K3nxqa@0N7?1Jt+1w5UQ@Xnl@v^j^ zLW07I-hSGnR4B1bqE(YLoKzX*bmidZ?}EC!G!KtH6b94R)_wtj&hEempT9{ zN^o#tASL_RqyV>b3{q6FCa7nxy|;xV5wa?>vai%`xtW*@ zYoOnD+medPe?vO3-VGmqHq>R9rp9uD)Ol)^*Z&FQmBD3oH*maeGbnYF9r14+=LSi!ywK5RGPCQ!S8W}p zlBo`7MH#W$Rpqy?CeYY@&?n%0XWQGjJ}xyrYiS8*LrL-T@;YD!62k>h4s`Q3Q}#>l zFlBvdIlGinYnLJ;@R&9g$_1~Z`XJH$>Bh#!kQb$n1(kqJy36WDHqS(cyT z|Fbozc{e%`v%$!H^P-V_`zL7}M=OsRWL{13pqjk-=G*(ZKFhF7q?b&N!Lr~TOjije zE9A6M(W5F`dol&*|Zf@FMx&?s3xjdR z(D)ZzfJ4wv&AjyN)kltPb7Ik+UbY+I+}R~dU8N`KnvHATD98T5gk|nk>f+4w^xqfZ z-{Z7G#DnOR7YR%f*?s-}0cZXMGBxRO2K(a>^Q@^Kl0NoYEpxeTFkuC!J!`zvwW+0~ zq$Z*LMumJQaP zmm?HjdKPFm$g1+nJ-FIdS`W56?hfO7IEv7L`RaeLJ_$7@?(KOPR{TfrBx(_y6sSpTljN67ViE%yd~RT#VkY`=&Sp`$>w#d-_0{Mq9V` z?(<{H_gW7hW3mWQ!`tmJ(j83RT+>~Qf&|bKe-DmEpoxIg>WevBbZ=YQY=1XXOni6$ z`g+A!NNVV&yg4Cwcw5@v&#wc|c`nF(iDmVP-^E($Z*y}_*34$`_mq`P-iv!IeEqJO#bLVzDhEfQC(-)SIb!A6sx>02%Tc8=>>t%GU45 zf$eMFlWV({I^7`*9YSersUMOb$md6MRhL6a(`FfwQq!ND z^}Y2d6PeZgn^vIoW!`ZVb$s3jYuxo12LoW1RJ7T!w(idpG_|sDLNW=S&$2xG~ zohRG=MiM{8gMIu2bgeJRKTy^SKv%De&0yw1Dsydm>m_V+7Z1AIiNYS8yr>3gA@s%K z{QkE%oTtKe9%N59PXI_?#FSU>!PQ!K9`;ou`GM#;C#t7SU0tVfGw5XoNW)eFrHvq; zPnMS0R_DWpWJM{?- z-MNhZOCBFnq&UK|xP5R2OgQ`#g;Fj1>l(xyNCVyQkr4S0PD@WWt}XUXYK3nqL8j$v z)^uA9OjDv2^%P%HL<@G9Z}m-50oIBe<$!QD>6dq6PrCHOruDrctayHT(o1X0POO5d ztBjc=Q))NZeEBlJ zv0C%CS35hZYNf=&jC+Sj)V_$dr-ke-jsMud{)||DE;sQGnyuTl)r$#t(QW6Te5Y&C zgZTSo5v94yy&T&Dh1Y4EGt8>bZ9G?G#FpTKp1WpP8yHXHTsS6h^dc5!8pBUs2L_kd zZpzSH?4K=5`iY516lgtzy|OV4s!>;T-3gjGX3RXE^=7ZcL3*6NlI8Kk9D!03>+SM8 z#o>~rj1iiS^~jmIlF2m_E=CQ81ZUpTOAi+sC%Rk-+)WcsQNNoeDHtv9#qy76gt92S z+>1FC_BipVvf#SHa<;3huI_iWZA|M?!+q#!Ma2gwzA0DYyM2OGbm)D-L4G^SfTN`^ zwk3wJHa8Csk0eQj-sZXSDkY=lr@_ArGWFwOcR0^DQ92X2QJR6X=~5|;+=yua(JnFy zU8`LJXE?yx;J6S|Pn|ITNZ(IQU40pZfH>Vn4#*k+$2t(0EC;vQ`xqL2!WvXPfy+PG zMm>IX3<0JmF3W6HG3BPzPik@UKT!My^k38Snc179Pbn)!n4Tyu$UbX11}*=OS;USP zF*rNH>|%^bR&nI>s;D+Fu9y%x+s`Q1RuV7en4iDp#) zZ#y|!2Z|0(Z6oJ@XpzkuPkcA0prF{G6kZXlr+9_Mc%)p#lb@J-{)y||Zg;3{ihCgH z%N8ZR&`>W}+C`t>4ESa+%t{jqJ6{?A(2rsA{rho~;O^CFdkt%w?%$LzSaRXeN*5ho zI9o9mi}^}AHAXXi@eg-&)%Y0^Q>@fC#-dwHuVOs-i4ltkuV9t)B%H_anQbGY(=aEh zR0(sEj_nZ)KEET;!Bv<-8&MW3Tx{0HJ7aj~GJLOBgy#(OQtf4L2)vU{-g`Iy;0Z?jr7~h)L9bRS zgKU8fZF{Tkw@|4aW4^?N7+2Uh!Vx!@P;ulF$F-vXyYdO zkOeg(jBWv-p#6FFA-m6S=d6{t+f=+>X{s-R?U))T`t^lInZvxV?S3ef!=gNkXOCbhlnRCT*1AVQxwHp~b-h;_qPL4e`@hEb6??M+{%fbx(kVEE1G`$Vhm z&nbW~kkR8jpB2^TcxFB-C~kK?)FNP4V>3aP_S6mr3iKOJCwQ5DZtQ(YLRxip!Jpcu zs$dSV-T1a+PR&sIw5JeiR6E{0|3C#|zf(*+=TV%AG1`f%YbpWM!0G)#nui#Istg=o zC3n(ME!4R|?aU4@^<^UC;o1!w^!KS_ZOZNqOgn8(&Mg|KQM@7^!|X?E9@$RlNhw0= za3ts5DQ=lvsvJ8nXp{)RgOSpc8Jg=<0a+vZFwW!6H*dapO-8ITed{DSOb$2t*i4^( z#*B6gOCm5j)B%m(gQV;?wzdcsKl)Dr3g@n4m?f1xP z)Z%cYd4mo$H=}MmCA5z*TV?Z@rNds|eT|v67iH;u*5sJtwTrt5hnX8lp|IbEnAO;^ z*KHyliPCJTXQ8u6Jzco!GDF?PdlM5A?D+>tW>t+?%*G8c&*B$Q$`@bSK6sREHpx7q zI`Lf{e0%v{&>sGyo-QW_Gr$Kon*0}8;4fOy2`KWEfnh*vrpXDE4h<{?8`|314vCa5 zp@ZoZT9RB^5#22a9&ks(8h6JxE~cpT3P;Knd6G0UE*_yF2DR$kAZVCrL^~rredhY* z3It>-$^zGpI;F`N8#3Lv*v?~f#yRs>oUF| zWDlqY*0W%V$u9e@N>5@wN=bTixj_#<-aI)++sp0n;cH`=si?0jfgh<-fx|@wh-~KG zI|F@39QAZSu!cRPh7lF(GDws2l-t&39tnn=TnOrjqX}CQd3+ab!n`#mEmIR-R7BZS zXM=~kx;eBhuUv9^CSIiA8$BHzbv#MF0RIs!&zAh74pL$q3DCXSVCHRLU=r(~G1NGWOV#GXesoQz32B?ACR*maA)PIDaoMFD-P!qAFA@T>28g^XLZ0jPIqo zse3(azmx>d9@F(CNk-kdLjcEdLx5_qBO?atyK?1<*>WcMk3`CfmeCSL5wd*vT*%{M zut*X9jN~$hopB4BW%h9LBjHDrOh&gT7Vp?`prnYe={V^LE4Gw6I^o+~`liL)dVMv7%!bYa8 zTJ>ncFEt`{SJ|ZpiUM;)6HkJfr>Cc2+tqAYO4G8rq=I2rkFA)l`itH8aJdj~{~m)e z+z?j>J;$UVvZm zH_8+GO-eRi4Jb|Vdyj@p4Jxwyf#j+sTzHM{m#@;udn198VHsCKoI7DSFG?DqhbZ`7 zNSnQb@PXgG^QCK;6=wk7thx@?m~UM;*Fsr%_m4FCZ5v-h03anT6DBy5DW66;1HJh( zRTvI%`J%;Oq{#f=2PkXziNuM+4z#>!K^$kVzUnc(jt^LZ()%@U-fVxK_+BDfp*1o$ zr(LoCGVPB08K$(i%C7V_%*MW;;-M054B$v!MjK4mr;Ln@D4;CY$AzqYOH+8kX{AII zl0i?D{N7S_Uxq-+Uj&Nw7Pf#B1X?fq9LK^$b8pU?N#@tz)(f&OF$YI=+eXpFKeZ5_ zv42@t&ZtpBz2DhoDs~%#v4`gUKuCo+@?<1$(0GLS^T2FXP^a9bCR^?7_4+@mfY=!X ztV8|Y-k#Xwo`3G^*|TSE5RVL+U!XK<``~CwV%)NELJzvZWNipgqMU-ZYnsjY%*^p~ zU`zxtE2eO=RTM|EDwO#w?I*fqcr#N<*E3)XKtqTD43yDeIvp}jtW6PaCY$&!0qUl} zOi-W^!=Czq`{&u#V6Qs6x+V#D(H6=C1VLBf!HCib>}M)ec?OaJ=7$#YsvBm;>h|Rq z{3YD*`AwO!vJA4SjyA?8r{uT)tyKPjC?tYZd0t%XhD=yixO)uo=)flDej2X@CYfad zdiB^09(qtSnc{WVY_-ULZvX8#6g|Anr<)|b1LhVG)J(p6K+0tCX5LP4p9QGubxRo# zEFH!TgC4Z&;B_#7Pq=7>t3gq|=N|}83Sv%DfkQ=6k%5*k>N%Ep7Zw(V1pxWIFyg2< z@?PoCuP35pq1TpQMR<-syTu~(M zG8|Pg{?*s@&KJc2aM-xI@w^bd5ZVbQpnulc+KK1H7Q8Q-fSJ|L0UL7*{oiHS1msXJ zuRlF=Z<931G$IGk!wWzgwZ9eYlbvxUWFlJ|%gWqJ3aAiBW)^O3a#Ptp`+HoHTXOl4 z7vfLl`|Mk1D8ItY#)>20&-%18;nV1U@7wvh@lKwE8qd~YZr+qY@#WNsw>bqRo2#p8 f5$X(eDdb1|mOFRlV-zJPA@I@Hy$07`a!&XkNUq0* literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/logo/rbdl_logo.png b/3rdparty/rbdl/doc/logo/rbdl_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0a75587da5de16f801a56a31d63835d120dc0f GIT binary patch literal 4731 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 + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/3rdparty/rbdl/doc/logo/rbdl_logo_16x16.png b/3rdparty/rbdl/doc/logo/rbdl_logo_16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..18146d70187b47e134c7da28b74e5e8aeb66c5d5 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6T^Rm@ z;DWu&Cj&(|3p^r=85o#lK$y{ch3;LTAbW|YuPgf#VS)s}}bD&Uzr;B5V#p$<` z4EYW#h&am!N`+{?Qr+e9=-=^-X=f~5+)q8=W9_PYIn8jI!2KWf$tn+yCy`j@Bi=QvImdo#&t*x|OWU406EexKnelF{r5}E)RHe5ac literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/logo/rbdl_logo_32x32.png b/3rdparty/rbdl/doc/logo/rbdl_logo_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..eac696ec26442974ee5d4acc41699145be8f1528 GIT binary patch literal 707 zcmV;!0zCbRP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00uVz00uV!1VG3Q00007bV*G`2iyY_ z3?CQkw}xl{00KivL_t(o!|jw$XcIvchrew?l5I?TsU(Pv)#_!phfoorLNQ)y1rbHm zyA<`L-fB?vA}EN69t08VpB}Uq1%(KDP^i#LjR7@+rq+Oz1e63!8`E^3mjRcU-RRl< zF0;&g`{p-qely_V;oKH30oVcT-V_L= zfl(}LNK7*Z1b~f*>_KEWTdO^MUajs``7x)b_@a7t?L?IyD$4bCzAH~WZWxB{gIi7U pB9KAkhL+1MDbIL#czAfUPx#24YJ`L;(K){{a7>y{D4^000SaNLh0L01R&c01R&dKNwF_00007bV*G`2iyY_ z4FU|Gd`YhW00hQKL_t(|+U=NKY!p=#$A2@kTUrWKwr#ewU`S&toh6!xEhI*(#zdPK zqfse7_z~k9RuKsjHSr@vC5C8>MjsSEVw9)}Y7weNtVSS)fB}{*rbSIDT5OemY-u;$ zo$CX4*6YT_-I?8e@&1y@o|(D#&dk|!&;8#68jVJy(P%UpjYgx05BN08vnqj=z-531%mF?CuFGimEkgX9 z5djv45k`nBfpU4Z1Jmpfw*8C|i}`YbP>#ABBH0@Qlwe+t;+o{Xka z3-ij{LqpTVVr%4-C14oVv*B<_9f0Bhubgy^)VZ9H@A4%u%|`$SpmV_j+A1na>VSYx z2VCSDA=4c5w1!8cM}YU-_5A7D+LAfI1TOdS@eRN#X-|hNYr=a97h7DjwS*CH3vi*d zlX>=C0(=5oAhY&b!!U$r2ZY1N9U&ZmkY&*n49+cP7+b&%z?;goUBFG!u9NpO2?2|w z2Dn}72!Qo+tWM8TwzoGq(%d{*69`lRtg5UG^^cADazLGwrK-wn1g-$uq!w`L26`0- zw9Dha=mwquekg>V|1FsucO7s^Sy{+$1PlXHz^gz_mVUnjuK` z@JA{&<=XYZ;QT^NfscUifE`M*a#;(!kwr4^1iG^1np&V9SPk3;+#&4_zYYjlR(jqK zCX*9|$^pZ`HNZP^-nz`q^6|*X`6bJ}pXBcj;AJ^=0>Jxm$2m67^u8k31_BGx;v|tc zQ^-|dMkc7QlxrL1#>NkOfybp?eLRt9_A^+uWu?RZ`>Clv3%v{q8P>Ck^u50Tx$AY; zm~fnhUI!78NNbs41OUbzhyK~wv&GN>eZWh~`G$@7<>gU?xGSy2dXfzs)Mx)VaG#ZUYqtR$I8ciPj1s_ILCUOx&;{X5v07*qo IM6N<$f>~>BX#fBK literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/luamodel_example.h b/3rdparty/rbdl/doc/luamodel_example.h new file mode 100644 index 0000000..2b2465f --- /dev/null +++ b/3rdparty/rbdl/doc/luamodel_example.h @@ -0,0 +1,43 @@ +/** \file example.h + * \page LuaModelExample LuaModel example + * + * Here is an example on how to use the \ref luamodel_introduction Addon both + * for loading the models from C++ code and also how the model file looks + * like. + * + * Using Lua as a description format for models instead of C++ code has + * numerous advantages such as simplified modeling, less C++ compilations, + * and easier exchange of models between multiple users. As the file format + * uses Lua code it is easy to create parameterized models. + * + * To be able to use the addon one has to enable the addon in CMake by + * setting BUILD_ADDON_LUAMODEL to true. + * + * Here is an example how \ref luamodel_introduction can be loaded by the RBDL: + * + * \include example_luamodel.cc + * + * If the library itself is already created, one can compile this example + * with CMake. In the example folder is a CMakeLists.txt file, that can be + * used to automatically create the makefiles by using CMake. It uses the script + * FindRBDL.cmake which can be used to find the library and include + * directory of the headers. + * + * The documentation about how to write the lua models can be found + * \ref luamodel_introduction "here". + * + * Here is an example: + * + * \include samplemodel.lua + * + * The FindRBDL.cmake script can use the environment variables RBDL_PATH, + * RBDL_INCLUDE_PATH, and RBDL_LIBRARY_PATH to find the required files. + * + * To build it manually you have to specify the following compiler and + * linking switches: + * \code + * g++ example.cc -I/src -I -lrbdl -L -o example + * \endcode + * + */ diff --git a/3rdparty/rbdl/doc/notes/Makefile b/3rdparty/rbdl/doc/notes/Makefile new file mode 100644 index 0000000..f9f53ae --- /dev/null +++ b/3rdparty/rbdl/doc/notes/Makefile @@ -0,0 +1,2 @@ +all: point_velocity_acceleration.tex + rubber -d point_velocity_acceleration.tex diff --git a/3rdparty/rbdl/doc/notes/acceleration_visualization.pdf b/3rdparty/rbdl/doc/notes/acceleration_visualization.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c0517ca58a8e2434068563bd93975e0608f3d130 GIT binary patch literal 35231 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3rdparty/rbdl/doc/notes/point_velocity_acceleration.tex b/3rdparty/rbdl/doc/notes/point_velocity_acceleration.tex new file mode 100644 index 0000000..25aee06 --- /dev/null +++ b/3rdparty/rbdl/doc/notes/point_velocity_acceleration.tex @@ -0,0 +1,157 @@ +% File: computation_notes.tex +% Created: Mon Dec 20 02:00 PM 2010 C +% Last Change: Mon Dec 20 02:00 PM 2010 C +% +\documentclass[a4paper]{article} +\usepackage[utf8]{inputenc} +\usepackage{graphicx} + +\newcommand{\Spa}[1]{\mathbf{\hat{#1}}} +\newcommand{\Nspa}[1]{\mathbf{\underline{#1}}} +\newcommand{\Vec}[1]{\mathbf{#1}} +\newcommand{\figref}[1]{\figurename~\ref{#1}} + +\begin{document} + +\section{Computation of the velocity of a point on a rigid body using spatial +algebra} + +The ABA computes automatically the spatial velocity of body $i$ in the +reference frame of the body such that computation of joint velocities are +easier. Now we want to compute the (linear) velocity in base coordinates of a +point $\Vec{p}$ that is attached to body $i$ with body coordinates +${}^i\Vec{r}_p$, i.e. we want to compute ${}^0 \Vec{\dot{r}}_p = {}^0 +\Vec{r}_p$. + +First of all we compute the spatial velocities ${}^0\Spa{v}$ of all bodies in +base coordinates (which can be done during the first loop of the ABA): + +\begin{equation} + {}^0\Spa{v}_i = {}^0\Spa{v}_{\lambda(i)} + {}^{0}\Spa{X}_{i} \Spa{v}_{Ji} +\end{equation} + +where $\Spa{v}_{Ji} = \Spa{S}_i \dot{q}_i $ which is the velocity that is +propagated by joint $i$ from body $\lambda(i)$ to body $i$ along joint axis +$\Spa{S}_i$ (see also RBDA p. 80). + +This is now the velocity of the body $i$ in base coordinates. What is now left +todo is to transform this velocity into the velocity of the point $\Vec{p}$. +Before we can do that we have to compute the coordinates of $P$ in base +coordinates which can be done by: + +\begin{equation} + {}^0\Vec{r}_p = {}^0 \Vec{r}_i + {}^0\Vec{R}_i {}^i \Vec{r}_p +\end{equation} + +for which ${}^0 \Vec{r}_i$ is the origin of the bodies in base coordinates and +${}^0 \Vec{R}_i$ the orientation of the base relative to the body. + +Now we can compute the velocity of point $\Vec{r}_p$ with the following +formulation: +\begin{equation} + {}^0\Spa{v}_p = \textit{xlt}({}^0\Vec{r}_p) {}^0\Spa{v}_i = + \left[ + \begin{array}{cc} + \Vec{1} & 0 \\ + -{}^0\Vec{r}_p \times & \Vec{1} + \end{array} + \right] + {}^0\Spa{v}_i +\end{equation} + +By doing so, the linear part of the spatial velocity ${}^0\Spa{v}_p$ has the +following entries: +\begin{equation} + {}^0\Spa{v}_p = \left[ + \begin{array}{c} + \Nspa{\omega} \\ + -{}^0\Nspa{r}_p \times \Nspa{\omega} + {}^0 \Nspa{\Vec{v}}_i + \end{array} + \right] + = + \left[ + \begin{array}{c} + \Nspa{\omega} \\ + {}^0 \Nspa{\Vec{v}}_i + \Nspa{\omega} \times {}^0 \Nspa{r}_p + \end{array} + \right] +\end{equation} + +For which the bottom line is the term for linear velocity in the standard 3D +notation. + +\section{Computation of the acceleration of a point on a rigid body using +spatial algebra} + +The acceleration of a point depends on three quantities: the position of the +point, velocity of the body and the acceleration of the body. We therefore +assume that we already computed the velocity as described in the previous +section. + +To compute the acceleration of a point we have to compute the spatial +acceleration of a body in base coordinates. This can be expressed as: +\begin{equation} + \Spa{a}_i = {}^0\Spa{a}_{\lambda(i)} + + {}^{0}\Spa{X}_{i} \Spa{a}_{Ji} +\end{equation} +for which $\Spa{a}_{Ji} = \Spa{S}_i \ddot{q}_i + \dot{\Spa{S}}_i \dot{q}_i$. +The last term can be written as: $\dot{\Spa{S}} = \Spa{v}_i \times \Spa{S}_i$. + +\begin{figure}[h!] + \begin{center} + \includegraphics[width=0.9\textwidth]{acceleration_visualization} + \end{center} + \caption{Visualization of the acceleration of a point} + \label{fig:acceleration_visualization} +\end{figure} + +There are three coordinate systems involved in the computation of the +acceleration of point p which are shown in +\figref{fig:acceleration_visualization}. The \emph{base coordinate system} +which is the global reference frame, \emph{link $i$ coordinate system} +which is the coordinate system of body $i$, and the \emph{point coordinate +system} that is locate at the current position of $p$ and has the same +orientation as the base coordinate system. + +First of all we build the transformation from the body coordinate system to +the point coordinate system: +\begin{equation} + {^p}\Vec{X}_i = + \left[ + \begin{array}{cc} + {^0}\Vec{E}_i & \Vec{0} \\ + \Vec{0} & {^0}\Vec{E}_i + \end{array} + \right] + \left[ + \begin{array}{cc} + \Vec{1} & \Vec{0} \\ + -{^i}\Nspa{r}_p \times & \Vec{1} + \end{array} + \right]. +\end{equation} +This can now be used to express the velocity and acceleration of the body at +the point p: +\begin{equation} + {^p}\Spa{v}_i = {^p}\Vec{X}_i \Spa{v}_i +\end{equation} +\begin{equation} + {^p}\Spa{a}_i = {^p}\Vec{X}_i \Spa{a}_i +\end{equation} + +We now want to retrieve the \emph{classical acceleration}. It is expressed in a +coordinate frame that has a pure linear velocity of ${^0}\Nspa{\dot{p}}$. +\begin{equation} + {^p}\Spa{a}'_i = {^p}\Spa{a}_i + + \left[ + \begin{array}{c} + \Vec{0}\\ + {^0}\Nspa{\omega}_i \times {^0}\Nspa{\dot{p}} + \end{array} + \right] +\end{equation} + +The linear part of this vector is then the acceleration of the point in base +coordinates. + +\end{document} diff --git a/3rdparty/rbdl/doc/python_example.h b/3rdparty/rbdl/doc/python_example.h new file mode 100644 index 0000000..1af93ff --- /dev/null +++ b/3rdparty/rbdl/doc/python_example.h @@ -0,0 +1,32 @@ +/** \file python_example.h + * \page PythonExample Python API example + * + * Here is a simple example of the Python API that showcases a subset of + * the wrapped functions and how to access them from python: + * + * \include example.py + * + * To build the wrapper one needs both Cython and the development libraries for + * NumPy installed on the system. If + * this is the case you can build the wrapper by enabling the CMake option + * RBDL_BUILD_PYTHON_WRAPPER when configuring RBDL. You can find the + * wrapper in the subdirectory python/ of your build directory. + * By running the python interpreter in this directory you can load it + * within the python shell using + * + * \code + * Python 2.7.11+ (default, Apr 17 2016, 14:00:29) + * [GCC 5.3.1 20160413] on linux2 + * Type "help", "copyright", "credits" or "license" for more information. + * >>> import rbdl + * \endcode + * + * To install the wrapper you can use the python/setup.py script: + * \code + * sudo ./setup.py install + * \endcode + * + * This installs the RBDL python module globally and allows you to import + * it from any python script. + */ diff --git a/3rdparty/rbdl/examples/luamodel/CMakeLists.txt b/3rdparty/rbdl/examples/luamodel/CMakeLists.txt new file mode 100644 index 0000000..9a0b71c --- /dev/null +++ b/3rdparty/rbdl/examples/luamodel/CMakeLists.txt @@ -0,0 +1,27 @@ +PROJECT (RBDLEXAMPLE CXX) + +CMAKE_POLICY(SET CMP0048 NEW) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +# We need to add the project source path to the CMake module path so that +# the FindRBDL.cmake script can be found. +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR} ) + +# Search for the RBDL include directory and library +FIND_PACKAGE (RBDL COMPONENTS LuaModel REQUIRED) +FIND_PACKAGE (Eigen3 3.0.0 REQUIRED) +FIND_PACKAGE (Lua51 REQUIRED) + +# Add the include directory to the include paths +INCLUDE_DIRECTORIES ( ${RBDL_INCLUDE_DIR} ${EIGEN3_INCLUDE_DIR} ${LUA_INCLUDE_DIR}) + +# Create an executable +ADD_EXECUTABLE (example_luamodel example_luamodel.cc) + +# And link the library against the executable +TARGET_LINK_LIBRARIES ( example_luamodel + ${RBDL_LIBRARY} + ${RBDL_LuaModel_LIBRARY} + ${LUA_LIBRARIES} + ) diff --git a/3rdparty/rbdl/examples/luamodel/FindEigen3.cmake b/3rdparty/rbdl/examples/luamodel/FindEigen3.cmake new file mode 100644 index 0000000..66ffe8e --- /dev/null +++ b/3rdparty/rbdl/examples/luamodel/FindEigen3.cmake @@ -0,0 +1,80 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif(NOT Eigen3_FIND_VERSION_MAJOR) + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif(NOT Eigen3_FIND_VERSION_MINOR) + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif(NOT Eigen3_FIND_VERSION_PATCH) + + set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif(NOT Eigen3_FIND_VERSION) + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK TRUE) + endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif(NOT EIGEN3_VERSION_OK) +endmacro(_eigen3_check_version) + +if (EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + +else (EIGEN3_INCLUDE_DIR) + + find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library + PATHS + ${CMAKE_INSTALL_PREFIX}/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen + ) + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif(EIGEN3_INCLUDE_DIR) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif(EIGEN3_INCLUDE_DIR) diff --git a/3rdparty/rbdl/examples/luamodel/FindRBDL.cmake b/3rdparty/rbdl/examples/luamodel/FindRBDL.cmake new file mode 100644 index 0000000..6804b11 --- /dev/null +++ b/3rdparty/rbdl/examples/luamodel/FindRBDL.cmake @@ -0,0 +1,126 @@ +# Searches for RBDL includes and library files, including Addons. +# +# Sets the variables +# RBDL_FOUND +# RBDL_INCLUDE_DIR +# RBDL_LIBRARY +# +# You can use the following components: +# LuaModel +# URDFReader +# and then link to them e.g. using RBDL_LuaModel_LIBRARY. + +SET (RBDL_FOUND FALSE) +SET (RBDL_LuaModel_FOUND FALSE) +SET (RBDL_URDFReader_FOUND FALSE) + +FIND_PATH (RBDL_INCLUDE_DIR rbdl/rbdl.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_LIBRARY NAMES rbdl + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH}/lib + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +FIND_PATH (RBDL_LuaModel_INCLUDE_DIR rbdl/addons/luamodel/luamodel.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_LuaModel_LIBRARY NAMES rbdl_luamodel + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH} + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +FIND_PATH (RBDL_URDFReader_INCLUDE_DIR rbdl/addons/urdfreader/urdfreader.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_URDFReader_LIBRARY NAMES rbdl_urdfreader + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH} + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +IF (NOT RBDL_LIBRARY) + MESSAGE (ERROR "Could not find RBDL") +ENDIF (NOT RBDL_LIBRARY) + +IF (RBDL_INCLUDE_DIR AND RBDL_LIBRARY) + SET (RBDL_FOUND TRUE) +ENDIF (RBDL_INCLUDE_DIR AND RBDL_LIBRARY) + +IF (RBDL_LuaModel_INCLUDE_DIR AND RBDL_LuaModel_LIBRARY) + SET (RBDL_LuaModel_FOUND TRUE) +ENDIF (RBDL_LuaModel_INCLUDE_DIR AND RBDL_LuaModel_LIBRARY) + +IF (RBDL_URDFReader_INCLUDE_DIR AND RBDL_URDFReader_LIBRARY) + SET (RBDL_URDFReader_FOUND TRUE) +ENDIF (RBDL_URDFReader_INCLUDE_DIR AND RBDL_URDFReader_LIBRARY) + +IF (RBDL_FOUND) + IF (NOT RBDL_FIND_QUIETLY) + MESSAGE(STATUS "Found RBDL: ${RBDL_LIBRARY}") + ENDIF (NOT RBDL_FIND_QUIETLY) + + foreach ( COMPONENT ${RBDL_FIND_COMPONENTS} ) + IF (RBDL_${COMPONENT}_FOUND) + IF (NOT RBDL_FIND_QUIETLY) + MESSAGE(STATUS "Found RBDL ${COMPONENT}: ${RBDL_${COMPONENT}_LIBRARY}") + ENDIF (NOT RBDL_FIND_QUIETLY) + ELSE (RBDL_${COMPONENT}_FOUND) + MESSAGE(SEND_ERROR "Could not find RBDL ${COMPONENT}") + ENDIF (RBDL_${COMPONENT}_FOUND) + endforeach ( COMPONENT ) +ELSE (RBDL_FOUND) + IF (RBDL_FIND_REQUIRED) + MESSAGE(SEND_ERROR "Could not find RBDL") + ENDIF (RBDL_FIND_REQUIRED) +ENDIF (RBDL_FOUND) + +MARK_AS_ADVANCED ( + RBDL_INCLUDE_DIR + RBDL_LIBRARY + RBDL_LuaModel_INCLUDE_DIR + RBDL_LuaModel_LIBRARY + RBDL_URDFReader_INCLUDE_DIR + RBDL_URDFReader_LIBRARY + ) diff --git a/3rdparty/rbdl/examples/luamodel/example_luamodel.cc b/3rdparty/rbdl/examples/luamodel/example_luamodel.cc new file mode 100644 index 0000000..3d90c05 --- /dev/null +++ b/3rdparty/rbdl/examples/luamodel/example_luamodel.cc @@ -0,0 +1,47 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include + +#include + +#ifndef RBDL_BUILD_ADDON_LUAMODEL + #error "Error: RBDL addon LuaModel not enabled." +#endif + +#include + +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +int main (int argc, char* argv[]) { + rbdl_check_api_version (RBDL_API_VERSION); + + Model* model = NULL; + + model = new Model(); + + if (!Addons::LuaModelReadFromFile ("./samplemodel.lua", model, false)) { + std::cerr << "Error loading model ./samplemodel.lua" << std::endl; + abort(); + } + + VectorNd Q = VectorNd::Zero (model->dof_count); + VectorNd QDot = VectorNd::Zero (model->dof_count); + VectorNd Tau = VectorNd::Zero (model->dof_count); + VectorNd QDDot = VectorNd::Zero (model->dof_count); + + ForwardDynamics (*model, Q, QDot, Tau, QDDot); + + std::cout << Q.transpose() << std::endl; + std::cout << QDDot.transpose() << std::endl; + + delete model; + + return 0; +} + diff --git a/3rdparty/rbdl/examples/luamodel/sampleconstrainedmodel.lua b/3rdparty/rbdl/examples/luamodel/sampleconstrainedmodel.lua new file mode 100644 index 0000000..8a93abd --- /dev/null +++ b/3rdparty/rbdl/examples/luamodel/sampleconstrainedmodel.lua @@ -0,0 +1,247 @@ +--[[ +-- This is an example model for the RBDL addon luamodel. You need to +-- enable RBDL_BUILD_ADDON_LUAMODEL to be able to use this file. +--]] + + +m1 = 2 +l1 = 2 +r1 = 0.2 +Izz1 = m1 * l1 * l1 / 3 + +m2 = 2 +l2 = 2 +r2 = 0.2 +Izz2 = m2 * l2 * l2 / 3 + +bodies = { + + virtual = { + mass = 0, + com = {0, 0, 0}, + inertia = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0}, + }, + }, + + link1 = { + mass = m1, + com = {l1/2, 0, 0}, + inertia = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, Izz1}, + }, + }, + + link2 = { + mass = m2, + com = {l2/2, 0, 0}, + inertia = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, Izz2}, + }, + }, + +} + +joints = { + + revZ = { + {0, 0, 1, 0, 0, 0}, + }, + + trnXYZ = { + {0, 0, 0, 1, 0, 0}, + {0, 0, 0, 0, 1, 0}, + {0, 0, 0, 0, 0, 1}, + }, + +} + +model = { + + gravity = {0, 0, 0}, + + configuration = { + axis_front = { 1., 0., 0.}, + axis_right = { 0., -1., 0.}, + axis_up = { 0., 0., 1.}, + }, + + frames = { + + { + name = 'base', + parent = 'ROOT', + body = bodies.virtual, + joint = joints.trnXYZ, + }, + { + name = 'l11', + parent = 'base', + body = bodies.link1, + joint = joints.revZ, + }, + { + name = 'l12', + parent = 'l11', + body = bodies.link2, + joint = joints.revZ, + joint_frame = { + r = {l1, 0, 0}, + }, + }, + { + name = 'l21', + parent = 'base', + body = bodies.link1, + joint = joints.revZ, + }, + { + name = 'l22', + parent = 'l21', + body = bodies.link2, + joint = joints.revZ, + joint_frame = { + r = {l1, 0, 0}, + }, + }, + + }, + + constraint_sets = { + loop_constraints = { + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 1, 0, 0}, + stabilization_coefficient = 0.1, + name = 'linkTX', + }, + + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 0, 1, 0}, + stabilization_coefficient = 0.1, + name = 'linkTY', + }, + }, + + all_constraints = { + { + constraint_type = 'contact', + body = 'base', + point = {0, 0, 0}, + normal = {1, 0, 0}, + name = 'baseTX', + normal_acceleration = 0, + }, + + { + constraint_type = 'contact', + body = 'base', + normal = {0, 1, 0}, + name = 'baseTY', + }, + + { + constraint_type = 'contact', + body = 'base', + normal = {0, 0, 1}, + name = 'baseTZ', + }, + + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 1, 0, 0}, + stabilization_coefficient = 0.1, + name = 'linkTX', + }, + + { + constraint_type = 'loop', + predecessor_body = 'l12', + successor_body = 'l22', + predecessor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {l2, 0, 0}, + }, + successor_transform = { + E = { + {1, 0, 0}, + {0, 1, 0}, + {0, 0, 1}, + }, + r = {0, 0, 0}, + }, + axis = {0, 0, 0, 0, 1, 0}, + stabilization_coefficient = 0.1, + name = 'linkTY', + }, + }, + }, + +} + +return model \ No newline at end of file diff --git a/3rdparty/rbdl/examples/luamodel/samplemodel.lua b/3rdparty/rbdl/examples/luamodel/samplemodel.lua new file mode 100644 index 0000000..756ed3d --- /dev/null +++ b/3rdparty/rbdl/examples/luamodel/samplemodel.lua @@ -0,0 +1,122 @@ +--[[ +-- This is an example model for the RBDL addon luamodel. You need to +-- enable RBDL_BUILD_ADDON_LUAMODEL to be able to use this file. +--]] + +print ("This is some output from the model file") + +inertia = { + {1.1, 0.1, 0.2}, + {0.3, 1.2, 0.4}, + {0.5, 0.6, 1.3} +} + +pelvis = { mass = 9.3, com = { 1.1, 1.2, 1.3}, inertia = inertia } +thigh = { mass = 4.2, com = { 1.1, 1.2, 1.3}, inertia = inertia } +shank = { mass = 4.1, com = { 1.1, 1.2, 1.3}, inertia = inertia } +foot = { mass = 1.1, com = { 1.1, 1.2, 1.3}, inertia = inertia } + +bodies = { + pelvis = pelvis, + thigh_right = thigh, + shank_right = shank, + thigh_left = thigh, + shank_left = shank +} + +joints = { + freeflyer = { + { 0., 0., 0., 1., 0., 0.}, + { 0., 0., 0., 0., 1., 0.}, + { 0., 0., 0., 0., 0., 1.}, + { 0., 0., 1., 0., 0., 0.}, + { 0., 1., 0., 0., 0., 0.}, + { 1., 0., 0., 0., 0., 0.} + }, + spherical_zyx = { + { 0., 0., 1., 0., 0., 0.}, + { 0., 1., 0., 0., 0., 0.}, + { 1., 0., 0., 0., 0., 0.} + }, + rotational_y = { + { 0., 1., 0., 0., 0., 0.} + }, + fixed = {} +} + +model = { + gravity = {0., 0., -9.81}, + + frames = { + { + name = "pelvis", + parent = "ROOT", + body = bodies.pelvis, + joint = joints.freeflyer, + }, + { + name = "thigh_right", + parent = "pelvis", + body = bodies.thigh_right, + joint = joints.spherical_zyx, + joint_frame = { + r = {0.0, -0.15, 0.}, + E = { + {1., 0., 0.}, + {0., 1., 0.}, + {0., 0., 0.} + } + } + }, + { + name = "shank_right", + parent = "thigh_right", + body = bodies.thigh_right, + joint = joints.rotational_y, + joint_frame = { + r = {0.0, 0., -0.42}, + }, + }, + { + name = "foot_right", + parent = "shank_right", + body = bodies.thigh_right, + joint_frame = { + r = {0.0, 0., -0.41} + }, + }, + { + name = "thigh_left", + parent = "pelvis", + body = bodies.thigh_left, + joint = joints.spherical_zyx, + joint_frame = { + r = {0.0, 0.15, 0.}, + E = { + {1., 0., 0.}, + {0., 1., 0.}, + {0., 0., 0.} + } + } + }, + { + name = "shank_left", + parent = "thigh_left", + body = bodies.thigh_left, + joint = joints.rotational_y, + joint_frame = { + r = {0.0, 0., -0.42}, + }, + }, + { + name = "foot_left", + parent = "shank_left", + body = bodies.thigh_left, + joint_frame = { + r = {0.0, 0., -0.41}, + }, + }, + } +} + +return model diff --git a/3rdparty/rbdl/examples/python/example.py b/3rdparty/rbdl/examples/python/example.py new file mode 100644 index 0000000..fce073f --- /dev/null +++ b/3rdparty/rbdl/examples/python/example.py @@ -0,0 +1,55 @@ +import numpy as np +import rbdl + +# Create a new model +model = rbdl.Model() + +# Create a joint from joint type +joint_rot_y = rbdl.Joint.fromJointType ("JointTypeRevoluteY") + +# Create a body for given mass, center of mass, and inertia at +# the CoM +body = rbdl.Body.fromMassComInertia ( + 1., + np.array([0., 0.5, 0.]), + np.eye(3) * 0.05) +xtrans= rbdl.SpatialTransform() +xtrans.r = np.array([0., 1., 0.]) + +# You can print all types +print (joint_rot_y) +print (model) +print (body) +print (body.mInertia) +print (xtrans) + +# Construct the model +body_1 = model.AppendBody (rbdl.SpatialTransform(), joint_rot_y, body) +body_2 = model.AppendBody (xtrans, joint_rot_y, body) +body_3 = model.AppendBody (xtrans, joint_rot_y, body) + +# Create numpy arrays for the state +q = np.zeros (model.q_size) +qdot = np.zeros (model.qdot_size) +qddot = np.zeros (model.qdot_size) +tau = np.zeros (model.qdot_size) + +# Modify the state +q[0] = 1.3 +q[1] = -0.5 +q[2] = 3.2 + +# Transform coordinates from local to global coordinates +point_local = np.array([1., 2., 3.]) +point_base = rbdl.CalcBodyToBaseCoordinates (model, q, body_3, point_local) +point_local_2 = rbdl.CalcBaseToBodyCoordinates (model, q, body_3, point_base) + +# Perform forward dynamics and print the result +rbdl.ForwardDynamics (model, q, qdot, tau, qddot) +print ("qddot = " + str(qddot.transpose())) + +# Compute and print the jacobian (note: the output parameter +# of the Jacobian G must have proper size!) +G = np.zeros([3,model.qdot_size]) +rbdl.CalcPointJacobian (model, q, body_3, point_local, G) +print ("G = \n" + str(G)) diff --git a/3rdparty/rbdl/examples/simple/CMakeLists.txt b/3rdparty/rbdl/examples/simple/CMakeLists.txt new file mode 100644 index 0000000..35672f0 --- /dev/null +++ b/3rdparty/rbdl/examples/simple/CMakeLists.txt @@ -0,0 +1,24 @@ +PROJECT (RBDLEXAMPLE CXX) + +CMAKE_POLICY(SET CMP0048 NEW) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +# We need to add the project source path to the CMake module path so that +# the FindRBDL.cmake script can be found. +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR} ) + +# Search for the RBDL include directory and library +FIND_PACKAGE (RBDL REQUIRED) +FIND_PACKAGE (Eigen3 3.0.0 REQUIRED) + +# Add the include directory to the include paths +INCLUDE_DIRECTORIES ( ${RBDL_INCLUDE_DIR} ${EIGEN3_INCLUDE_DIR} ) + +# Create an executable +ADD_EXECUTABLE (example example.cc) + +# And link the library against the executable +TARGET_LINK_LIBRARIES (example + ${RBDL_LIBRARY} + ) diff --git a/3rdparty/rbdl/examples/simple/FindEigen3.cmake b/3rdparty/rbdl/examples/simple/FindEigen3.cmake new file mode 100644 index 0000000..66ffe8e --- /dev/null +++ b/3rdparty/rbdl/examples/simple/FindEigen3.cmake @@ -0,0 +1,80 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif(NOT Eigen3_FIND_VERSION_MAJOR) + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif(NOT Eigen3_FIND_VERSION_MINOR) + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif(NOT Eigen3_FIND_VERSION_PATCH) + + set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif(NOT Eigen3_FIND_VERSION) + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK TRUE) + endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif(NOT EIGEN3_VERSION_OK) +endmacro(_eigen3_check_version) + +if (EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + +else (EIGEN3_INCLUDE_DIR) + + find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library + PATHS + ${CMAKE_INSTALL_PREFIX}/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen + ) + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif(EIGEN3_INCLUDE_DIR) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif(EIGEN3_INCLUDE_DIR) diff --git a/3rdparty/rbdl/examples/simple/FindRBDL.cmake b/3rdparty/rbdl/examples/simple/FindRBDL.cmake new file mode 100644 index 0000000..6804b11 --- /dev/null +++ b/3rdparty/rbdl/examples/simple/FindRBDL.cmake @@ -0,0 +1,126 @@ +# Searches for RBDL includes and library files, including Addons. +# +# Sets the variables +# RBDL_FOUND +# RBDL_INCLUDE_DIR +# RBDL_LIBRARY +# +# You can use the following components: +# LuaModel +# URDFReader +# and then link to them e.g. using RBDL_LuaModel_LIBRARY. + +SET (RBDL_FOUND FALSE) +SET (RBDL_LuaModel_FOUND FALSE) +SET (RBDL_URDFReader_FOUND FALSE) + +FIND_PATH (RBDL_INCLUDE_DIR rbdl/rbdl.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_LIBRARY NAMES rbdl + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH}/lib + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +FIND_PATH (RBDL_LuaModel_INCLUDE_DIR rbdl/addons/luamodel/luamodel.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_LuaModel_LIBRARY NAMES rbdl_luamodel + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH} + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +FIND_PATH (RBDL_URDFReader_INCLUDE_DIR rbdl/addons/urdfreader/urdfreader.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_URDFReader_LIBRARY NAMES rbdl_urdfreader + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH} + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +IF (NOT RBDL_LIBRARY) + MESSAGE (ERROR "Could not find RBDL") +ENDIF (NOT RBDL_LIBRARY) + +IF (RBDL_INCLUDE_DIR AND RBDL_LIBRARY) + SET (RBDL_FOUND TRUE) +ENDIF (RBDL_INCLUDE_DIR AND RBDL_LIBRARY) + +IF (RBDL_LuaModel_INCLUDE_DIR AND RBDL_LuaModel_LIBRARY) + SET (RBDL_LuaModel_FOUND TRUE) +ENDIF (RBDL_LuaModel_INCLUDE_DIR AND RBDL_LuaModel_LIBRARY) + +IF (RBDL_URDFReader_INCLUDE_DIR AND RBDL_URDFReader_LIBRARY) + SET (RBDL_URDFReader_FOUND TRUE) +ENDIF (RBDL_URDFReader_INCLUDE_DIR AND RBDL_URDFReader_LIBRARY) + +IF (RBDL_FOUND) + IF (NOT RBDL_FIND_QUIETLY) + MESSAGE(STATUS "Found RBDL: ${RBDL_LIBRARY}") + ENDIF (NOT RBDL_FIND_QUIETLY) + + foreach ( COMPONENT ${RBDL_FIND_COMPONENTS} ) + IF (RBDL_${COMPONENT}_FOUND) + IF (NOT RBDL_FIND_QUIETLY) + MESSAGE(STATUS "Found RBDL ${COMPONENT}: ${RBDL_${COMPONENT}_LIBRARY}") + ENDIF (NOT RBDL_FIND_QUIETLY) + ELSE (RBDL_${COMPONENT}_FOUND) + MESSAGE(SEND_ERROR "Could not find RBDL ${COMPONENT}") + ENDIF (RBDL_${COMPONENT}_FOUND) + endforeach ( COMPONENT ) +ELSE (RBDL_FOUND) + IF (RBDL_FIND_REQUIRED) + MESSAGE(SEND_ERROR "Could not find RBDL") + ENDIF (RBDL_FIND_REQUIRED) +ENDIF (RBDL_FOUND) + +MARK_AS_ADVANCED ( + RBDL_INCLUDE_DIR + RBDL_LIBRARY + RBDL_LuaModel_INCLUDE_DIR + RBDL_LuaModel_LIBRARY + RBDL_URDFReader_INCLUDE_DIR + RBDL_URDFReader_LIBRARY + ) diff --git a/3rdparty/rbdl/examples/simple/example.cc b/3rdparty/rbdl/examples/simple/example.cc new file mode 100644 index 0000000..15df04d --- /dev/null +++ b/3rdparty/rbdl/examples/simple/example.cc @@ -0,0 +1,65 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include + +#include + +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +int main (int argc, char* argv[]) { + rbdl_check_api_version (RBDL_API_VERSION); + + Model* model = NULL; + + unsigned int body_a_id, body_b_id, body_c_id; + Body body_a, body_b, body_c; + Joint joint_a, joint_b, joint_c; + + model = new Model(); + + model->gravity = Vector3d (0., -9.81, 0.); + + body_a = Body (1., Vector3d (0.5, 0., 0.0), Vector3d (1., 1., 1.)); + joint_a = Joint( + JointTypeRevolute, + Vector3d (0., 0., 1.) + ); + + body_a_id = model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + body_b = Body (1., Vector3d (0., 0.5, 0.), Vector3d (1., 1., 1.)); + joint_b = Joint ( + JointTypeRevolute, + Vector3d (0., 0., 1.) + ); + + body_b_id = model->AddBody(body_a_id, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + body_c = Body (0., Vector3d (0.5, 0., 0.), Vector3d (1., 1., 1.)); + joint_c = Joint ( + JointTypeRevolute, + Vector3d (0., 0., 1.) + ); + + body_c_id = model->AddBody(body_b_id, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + VectorNd Q = VectorNd::Zero (model->dof_count); + VectorNd QDot = VectorNd::Zero (model->dof_count); + VectorNd Tau = VectorNd::Zero (model->dof_count); + VectorNd QDDot = VectorNd::Zero (model->dof_count); + + ForwardDynamics (*model, Q, QDot, Tau, QDDot); + + std::cout << QDDot.transpose() << std::endl; + + delete model; + + return 0; +} + diff --git a/3rdparty/rbdl/examples/urdfreader/CMakeLists.txt b/3rdparty/rbdl/examples/urdfreader/CMakeLists.txt new file mode 100644 index 0000000..e170b76 --- /dev/null +++ b/3rdparty/rbdl/examples/urdfreader/CMakeLists.txt @@ -0,0 +1,25 @@ +PROJECT (RBDLEXAMPLE CXX) + +CMAKE_POLICY(SET CMP0048 NEW) + +CMAKE_MINIMUM_REQUIRED(VERSION 2.6) + +# We need to add the project source path to the CMake module path so that +# the FindRBDL.cmake script can be found. +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR} ) + +# Search for the RBDL include directory and library +FIND_PACKAGE (RBDL COMPONENTS URDFReader REQUIRED) +FIND_PACKAGE (Eigen3 REQUIRED) + +# Add the include directory to the include paths +INCLUDE_DIRECTORIES ( ${RBDL_INCLUDE_DIR} ${EIGEN3_INCLUDE_DIR}) + +# Create an executable +ADD_EXECUTABLE (example_urdfreader example_urdfreader.cc) + +# And link the library against the executable +TARGET_LINK_LIBRARIES ( example_urdfreader + ${RBDL_LIBRARY} + ${RBDL_URDFReader_LIBRARY} + ) diff --git a/3rdparty/rbdl/examples/urdfreader/FindEigen3.cmake b/3rdparty/rbdl/examples/urdfreader/FindEigen3.cmake new file mode 100644 index 0000000..66ffe8e --- /dev/null +++ b/3rdparty/rbdl/examples/urdfreader/FindEigen3.cmake @@ -0,0 +1,80 @@ +# - Try to find Eigen3 lib +# +# This module supports requiring a minimum version, e.g. you can do +# find_package(Eigen3 3.1.2) +# to require version 3.1.2 or newer of Eigen3. +# +# Once done this will define +# +# EIGEN3_FOUND - system has eigen lib with correct version +# EIGEN3_INCLUDE_DIR - the eigen include directory +# EIGEN3_VERSION - eigen version + +# Copyright (c) 2006, 2007 Montel Laurent, +# Copyright (c) 2008, 2009 Gael Guennebaud, +# Copyright (c) 2009 Benoit Jacob +# Redistribution and use is allowed according to the terms of the 2-clause BSD license. + +if(NOT Eigen3_FIND_VERSION) + if(NOT Eigen3_FIND_VERSION_MAJOR) + set(Eigen3_FIND_VERSION_MAJOR 2) + endif(NOT Eigen3_FIND_VERSION_MAJOR) + if(NOT Eigen3_FIND_VERSION_MINOR) + set(Eigen3_FIND_VERSION_MINOR 91) + endif(NOT Eigen3_FIND_VERSION_MINOR) + if(NOT Eigen3_FIND_VERSION_PATCH) + set(Eigen3_FIND_VERSION_PATCH 0) + endif(NOT Eigen3_FIND_VERSION_PATCH) + + set(Eigen3_FIND_VERSION "${Eigen3_FIND_VERSION_MAJOR}.${Eigen3_FIND_VERSION_MINOR}.${Eigen3_FIND_VERSION_PATCH}") +endif(NOT Eigen3_FIND_VERSION) + +macro(_eigen3_check_version) + file(READ "${EIGEN3_INCLUDE_DIR}/Eigen/src/Core/util/Macros.h" _eigen3_version_header) + + string(REGEX MATCH "define[ \t]+EIGEN_WORLD_VERSION[ \t]+([0-9]+)" _eigen3_world_version_match "${_eigen3_version_header}") + set(EIGEN3_WORLD_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MAJOR_VERSION[ \t]+([0-9]+)" _eigen3_major_version_match "${_eigen3_version_header}") + set(EIGEN3_MAJOR_VERSION "${CMAKE_MATCH_1}") + string(REGEX MATCH "define[ \t]+EIGEN_MINOR_VERSION[ \t]+([0-9]+)" _eigen3_minor_version_match "${_eigen3_version_header}") + set(EIGEN3_MINOR_VERSION "${CMAKE_MATCH_1}") + + set(EIGEN3_VERSION ${EIGEN3_WORLD_VERSION}.${EIGEN3_MAJOR_VERSION}.${EIGEN3_MINOR_VERSION}) + if(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK FALSE) + else(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + set(EIGEN3_VERSION_OK TRUE) + endif(${EIGEN3_VERSION} VERSION_LESS ${Eigen3_FIND_VERSION}) + + if(NOT EIGEN3_VERSION_OK) + + message(STATUS "Eigen3 version ${EIGEN3_VERSION} found in ${EIGEN3_INCLUDE_DIR}, " + "but at least version ${Eigen3_FIND_VERSION} is required") + endif(NOT EIGEN3_VERSION_OK) +endmacro(_eigen3_check_version) + +if (EIGEN3_INCLUDE_DIR) + + # in cache already + _eigen3_check_version() + set(EIGEN3_FOUND ${EIGEN3_VERSION_OK}) + +else (EIGEN3_INCLUDE_DIR) + + find_path(EIGEN3_INCLUDE_DIR NAMES signature_of_eigen3_matrix_library + PATHS + ${CMAKE_INSTALL_PREFIX}/include + ${KDE4_INCLUDE_DIR} + PATH_SUFFIXES eigen3 eigen + ) + + if(EIGEN3_INCLUDE_DIR) + _eigen3_check_version() + endif(EIGEN3_INCLUDE_DIR) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Eigen3 DEFAULT_MSG EIGEN3_INCLUDE_DIR EIGEN3_VERSION_OK) + + mark_as_advanced(EIGEN3_INCLUDE_DIR) + +endif(EIGEN3_INCLUDE_DIR) diff --git a/3rdparty/rbdl/examples/urdfreader/FindRBDL.cmake b/3rdparty/rbdl/examples/urdfreader/FindRBDL.cmake new file mode 100644 index 0000000..6804b11 --- /dev/null +++ b/3rdparty/rbdl/examples/urdfreader/FindRBDL.cmake @@ -0,0 +1,126 @@ +# Searches for RBDL includes and library files, including Addons. +# +# Sets the variables +# RBDL_FOUND +# RBDL_INCLUDE_DIR +# RBDL_LIBRARY +# +# You can use the following components: +# LuaModel +# URDFReader +# and then link to them e.g. using RBDL_LuaModel_LIBRARY. + +SET (RBDL_FOUND FALSE) +SET (RBDL_LuaModel_FOUND FALSE) +SET (RBDL_URDFReader_FOUND FALSE) + +FIND_PATH (RBDL_INCLUDE_DIR rbdl/rbdl.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_LIBRARY NAMES rbdl + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH}/lib + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +FIND_PATH (RBDL_LuaModel_INCLUDE_DIR rbdl/addons/luamodel/luamodel.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_LuaModel_LIBRARY NAMES rbdl_luamodel + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH} + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +FIND_PATH (RBDL_URDFReader_INCLUDE_DIR rbdl/addons/urdfreader/urdfreader.h + HINTS + $ENV{HOME}/local/include + $ENV{RBDL_PATH}/src + $ENV{RBDL_PATH}/include + $ENV{RBDL_INCLUDE_PATH} + /usr/local/include + /usr/include + ) + +FIND_LIBRARY (RBDL_URDFReader_LIBRARY NAMES rbdl_urdfreader + PATHS + $ENV{HOME}/local/lib + $ENV{HOME}/local/lib/x86_64-linux-gnu + $ENV{RBDL_PATH} + $ENV{RBDL_LIBRARY_PATH} + /usr/local/lib + /usr/local/lib/x86_64-linux-gnu + /usr/lib + /usr/lib/x86_64-linux-gnu + ) + +IF (NOT RBDL_LIBRARY) + MESSAGE (ERROR "Could not find RBDL") +ENDIF (NOT RBDL_LIBRARY) + +IF (RBDL_INCLUDE_DIR AND RBDL_LIBRARY) + SET (RBDL_FOUND TRUE) +ENDIF (RBDL_INCLUDE_DIR AND RBDL_LIBRARY) + +IF (RBDL_LuaModel_INCLUDE_DIR AND RBDL_LuaModel_LIBRARY) + SET (RBDL_LuaModel_FOUND TRUE) +ENDIF (RBDL_LuaModel_INCLUDE_DIR AND RBDL_LuaModel_LIBRARY) + +IF (RBDL_URDFReader_INCLUDE_DIR AND RBDL_URDFReader_LIBRARY) + SET (RBDL_URDFReader_FOUND TRUE) +ENDIF (RBDL_URDFReader_INCLUDE_DIR AND RBDL_URDFReader_LIBRARY) + +IF (RBDL_FOUND) + IF (NOT RBDL_FIND_QUIETLY) + MESSAGE(STATUS "Found RBDL: ${RBDL_LIBRARY}") + ENDIF (NOT RBDL_FIND_QUIETLY) + + foreach ( COMPONENT ${RBDL_FIND_COMPONENTS} ) + IF (RBDL_${COMPONENT}_FOUND) + IF (NOT RBDL_FIND_QUIETLY) + MESSAGE(STATUS "Found RBDL ${COMPONENT}: ${RBDL_${COMPONENT}_LIBRARY}") + ENDIF (NOT RBDL_FIND_QUIETLY) + ELSE (RBDL_${COMPONENT}_FOUND) + MESSAGE(SEND_ERROR "Could not find RBDL ${COMPONENT}") + ENDIF (RBDL_${COMPONENT}_FOUND) + endforeach ( COMPONENT ) +ELSE (RBDL_FOUND) + IF (RBDL_FIND_REQUIRED) + MESSAGE(SEND_ERROR "Could not find RBDL") + ENDIF (RBDL_FIND_REQUIRED) +ENDIF (RBDL_FOUND) + +MARK_AS_ADVANCED ( + RBDL_INCLUDE_DIR + RBDL_LIBRARY + RBDL_LuaModel_INCLUDE_DIR + RBDL_LuaModel_LIBRARY + RBDL_URDFReader_INCLUDE_DIR + RBDL_URDFReader_LIBRARY + ) diff --git a/3rdparty/rbdl/examples/urdfreader/example_urdfreader.cc b/3rdparty/rbdl/examples/urdfreader/example_urdfreader.cc new file mode 100644 index 0000000..106c283 --- /dev/null +++ b/3rdparty/rbdl/examples/urdfreader/example_urdfreader.cc @@ -0,0 +1,45 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include + +#include + +#ifndef RBDL_BUILD_ADDON_URDFREADER + #error "Error: RBDL addon URDFReader not enabled." +#endif + +#include + +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +int main (int argc, char* argv[]) { + rbdl_check_api_version (RBDL_API_VERSION); + + Model* model = new Model(); + + if (!Addons::URDFReadFromFile ("./samplemodel.urdf", model, false)) { + std::cerr << "Error loading model ./samplemodel.urdf" << std::endl; + abort(); + } + + VectorNd Q = VectorNd::Zero (model->dof_count); + VectorNd QDot = VectorNd::Zero (model->dof_count); + VectorNd Tau = VectorNd::Zero (model->dof_count); + VectorNd QDDot = VectorNd::Zero (model->dof_count); + + ForwardDynamics (*model, Q, QDot, Tau, QDDot); + + std::cout << Q.transpose() << std::endl; + std::cout << QDDot.transpose() << std::endl; + + delete model; + + return 0; +} + diff --git a/3rdparty/rbdl/include/rbdl/Body.h b/3rdparty/rbdl/include/rbdl/Body.h new file mode 100644 index 0000000..7d36b81 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Body.h @@ -0,0 +1,218 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_BODY_H +#define RBDL_BODY_H + +#include "rbdl/rbdl_math.h" +#include "rbdl/rbdl_mathutils.h" +#include +#include +#include "rbdl/Logging.h" + +namespace RigidBodyDynamics { + +/** \brief Describes all properties of a single body + * + * A Body contains information about mass, the location of its center of + * mass, and the ineria tensor in the center of mass. This class is + * designed to use the given information and transform it such that it can + * directly be used by the spatial algebra. + */ +struct RBDL_DLLAPI Body { + Body() : + mMass (0.), + mCenterOfMass (0., 0., 0.), + mInertia (Math::Matrix3d::Zero(3,3)), + mIsVirtual (false) + { }; + Body(const Body &body) : + mMass (body.mMass), + mCenterOfMass (body.mCenterOfMass), + mInertia (body.mInertia), + mIsVirtual (body.mIsVirtual) + {}; + Body& operator= (const Body &body) { + if (this != &body) { + mMass = body.mMass; + mInertia = body.mInertia; + mCenterOfMass = body.mCenterOfMass; + mIsVirtual = body.mIsVirtual; + } + + return *this; + } + + /** \brief Constructs a body from mass, center of mass and radii of gyration + * + * This constructor eases the construction of a new body as all the + * required parameters can be specified as parameters to the + * constructor. These are then used to generate the spatial inertia + * matrix which is expressed at the origin. + * + * \param mass the mass of the body + * \param com the position of the center of mass in the bodies coordinates + * \param gyration_radii the radii of gyration at the center of mass of the body + */ + Body(const double &mass, + const Math::Vector3d &com, + const Math::Vector3d &gyration_radii) : + mMass (mass), + mCenterOfMass(com), + mIsVirtual (false) { + mInertia = Math::Matrix3d ( + gyration_radii[0], 0., 0., + 0., gyration_radii[1], 0., + 0., 0., gyration_radii[2] + ); + } + + /** \brief Constructs a body from mass, center of mass, and a 3x3 inertia matrix + * + * This constructor eases the construction of a new body as all the + * required parameters can simply be specified as parameters to the + * constructor. These are then used to generate the spatial inertia + * matrix which is expressed at the origin. + * + * \param mass the mass of the body + * \param com the position of the center of mass in the bodies coordinates + * \param inertia_C the inertia at the center of mass + */ + Body(const double &mass, + const Math::Vector3d &com, + const Math::Matrix3d &inertia_C) : + mMass (mass), + mCenterOfMass(com), + mInertia (inertia_C), + mIsVirtual (false) { } + + /** \brief Joins inertial parameters of two bodies to create a composite + * body. + * + * This function can be used to joint inertial parameters of two bodies + * to create a composite body that has the inertial properties as if the + * two bodies were joined by a fixed joint. + * + * \note Both bodies have to have their inertial parameters expressed in + * the same orientation. + * + * \param transform The frame transformation from the origin of the + * original body to the origin of the added body + * \param other_body The other body that will be merged with *this. + */ + void Join (const Math::SpatialTransform &transform, const Body &other_body) { + // nothing to do if we join a massles body to the current. + if (other_body.mMass == 0. && other_body.mInertia == Math::Matrix3d::Zero()) { + return; + } + + double other_mass = other_body.mMass; + double new_mass = mMass + other_mass; + + if (new_mass == 0.) { + std::cerr << "Error: cannot join bodies as both have zero mass!" << std::endl; + assert (false); + abort(); + } + + Math::Vector3d other_com = transform.E.transpose() * other_body.mCenterOfMass + transform.r; + Math::Vector3d new_com = (1 / new_mass ) * (mMass * mCenterOfMass + other_mass * other_com); + + LOG << "other_com = " << std::endl << other_com.transpose() << std::endl; + LOG << "rotation = " << std::endl << transform.E << std::endl; + + // We have to transform the inertia of other_body to the new COM. This + // is done in 4 steps: + // + // 1. Transform the inertia from other origin to other COM + // 2. Rotate the inertia that it is aligned to the frame of this body + // 3. Transform inertia of other_body to the origin of the frame of + // this body + // 4. Sum the two inertias + // 5. Transform the summed inertia to the new COM + + Math::SpatialRigidBodyInertia other_rbi = Math::SpatialRigidBodyInertia::createFromMassComInertiaC (other_body.mMass, other_body.mCenterOfMass, other_body.mInertia); + Math::SpatialRigidBodyInertia this_rbi = Math::SpatialRigidBodyInertia::createFromMassComInertiaC (mMass, mCenterOfMass, mInertia); + + Math::Matrix3d inertia_other = other_rbi.toMatrix().block<3,3>(0,0); + LOG << "inertia_other = " << std::endl << inertia_other << std::endl; + + // 1. Transform the inertia from other origin to other COM + Math::Matrix3d other_com_cross = Math::VectorCrossMatrix(other_body.mCenterOfMass); + Math::Matrix3d inertia_other_com = inertia_other - other_mass * other_com_cross * other_com_cross.transpose(); + LOG << "inertia_other_com = " << std::endl << inertia_other_com << std::endl; + + // 2. Rotate the inertia that it is aligned to the frame of this body + Math::Matrix3d inertia_other_com_rotated = transform.E.transpose() * inertia_other_com * transform.E; + LOG << "inertia_other_com_rotated = " << std::endl << inertia_other_com_rotated << std::endl; + + // 3. Transform inertia of other_body to the origin of the frame of this body + Math::Matrix3d inertia_other_com_rotated_this_origin = Math::parallel_axis (inertia_other_com_rotated, other_mass, other_com); + LOG << "inertia_other_com_rotated_this_origin = " << std::endl << inertia_other_com_rotated_this_origin << std::endl; + + // 4. Sum the two inertias + Math::Matrix3d inertia_summed = Math::Matrix3d (this_rbi.toMatrix().block<3,3>(0,0)) + inertia_other_com_rotated_this_origin; + LOG << "inertia_summed = " << std::endl << inertia_summed << std::endl; + + // 5. Transform the summed inertia to the new COM + Math::Matrix3d new_inertia = inertia_summed - new_mass * Math::VectorCrossMatrix (new_com) * Math::VectorCrossMatrix(new_com).transpose(); + + LOG << "new_mass = " << new_mass << std::endl; + LOG << "new_com = " << new_com.transpose() << std::endl; + LOG << "new_inertia = " << std::endl << new_inertia << std::endl; + + *this = Body (new_mass, new_com, new_inertia); + } + + ~Body() {}; + + /// \brief The mass of the body + double mMass; + /// \brief The position of the center of mass in body coordinates + Math::Vector3d mCenterOfMass; + /// \brief Inertia matrix at the center of mass + Math::Matrix3d mInertia; + + bool mIsVirtual; +}; + +/** \brief Keeps the information of a body and how it is attached to another body. + * + * When using fixed bodies, i.e. a body that is attached to anothe via a + * fixed joint, the attached body is merged onto its parent. By doing so + * adding fixed joints do not have an impact on runtime. + */ +struct RBDL_DLLAPI FixedBody { + /// \brief The mass of the body + double mMass; + /// \brief The position of the center of mass in body coordinates + Math::Vector3d mCenterOfMass; + /// \brief The spatial inertia that contains both mass and inertia information + Math::Matrix3d mInertia; + + /// \brief Id of the movable body that this fixed body is attached to. + unsigned int mMovableParent; + /// \brief Transforms spatial quantities expressed for the parent to the + // fixed body. + Math::SpatialTransform mParentTransform; + Math::SpatialTransform mBaseTransform; + + static FixedBody CreateFromBody (const Body& body) { + FixedBody fbody; + + fbody.mMass = body.mMass; + fbody.mCenterOfMass = body.mCenterOfMass; + fbody.mInertia = body.mInertia; + + return fbody; + } +}; + +} + +/* RBDL_BODY_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Constraints.h b/3rdparty/rbdl/include/rbdl/Constraints.h new file mode 100644 index 0000000..1a4f7c6 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Constraints.h @@ -0,0 +1,936 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2015 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_CONSTRAINTS_H +#define RBDL_CONSTRAINTS_H + +#include +#include + +namespace RigidBodyDynamics { + +/** \page contacts_page External Constraints + * + * All functions related to contacts are specified in the \ref + * constraints_group "Constraints Module". + + * \defgroup constraints_group Constraints + * + * Constraints are handled by specification of a \link + * RigidBodyDynamics::ConstraintSet + * ConstraintSet \endlink which contains all informations about the + * current constraints and workspace memory. + * + * Separate constraints can be specified by calling + * ConstraintSet::AddContactConstraint() and ConstraintSet::AddLoopConstraint(). + * After all constraints have been specified, this \link + * RigidBodyDynamics::ConstraintSet + * ConstraintSet \endlink has to be bound to the model via + * ConstraintSet::Bind(). This initializes workspace memory that is + * later used when calling one of the contact functions, such as + * ForwardDynamicsContactsDirects(). + * + * The values in the vectors ConstraintSet::force and + * ConstraintSet::impulse contain the computed force or + * impulse values for each constraint when returning from one of the + * contact functions. + * + * \section solution_constraint_system Solution of the Constraint System + * + * \subsection constraint_system Linear System of the Constrained Dynamics + * + * In the presence of constraints, to compute the + * acceleration one has to solve a linear system of the form: \f[ + \left( + \begin{array}{cc} + H & G^T \\ + G & 0 + \end{array} + \right) + \left( + \begin{array}{c} + \ddot{q} \\ + - \lambda + \end{array} + \right) + = + \left( + \begin{array}{c} + -C + \tau \\ + \gamma + \end{array} + \right) + * \f] where \f$H\f$ is the joint space inertia matrix computed with the + * CompositeRigidBodyAlgorithm(), \f$G\f$ is the constraint jacobian, + * \f$C\f$ the bias force (sometimes called "non-linear + * effects"), and \f$\gamma\f$ the generalized acceleration independent + * part of the constraints. + * + * \subsection collision_system Linear System of the Contact Collision + * + * Similarly to compute the response of the model to a contact gain one has + * to solve a system of the following form: \f[ + \left( + \begin{array}{cc} + H & G^T \\ + G & 0 + \end{array} + \right) + \left( + \begin{array}{c} + \dot{q}^{+} \\ + \Lambda + \end{array} + \right) + = + \left( + \begin{array}{c} + H \dot{q}^{-} \\ + v^{+} + \end{array} + \right) + * \f] where \f$H\f$ is the joint space inertia matrix computed with the + * CompositeRigidBodyAlgorithm(), \f$G\f$ are the point jacobians of the + * contact points, \f$\dot{q}^{+}\f$ the generalized velocity after the + * impact, \f$\Lambda\f$ the impulses at each constraint, \f$\dot{q}^{-}\f$ + * the generalized velocity before the impact, and \f$v^{+}\f$ the desired + * velocity of each constraint after the impact (known beforehand, usually + * 0). The value of \f$v^{+}\f$ can is specified via the variable + * ConstraintSet::v_plus and defaults to 0. + * + * \subsection solution_methods Solution Methods + * + * There are essentially three different approaches to solve these systems: + * -# \b Direct: solve the full system to simultaneously compute + * \f$\ddot{q}\f$ and \f$\lambda\f$. This may be slow for large systems + * and many constraints. + * -# \b Range-Space: solve first for \f$\lambda\f$ and then for + * \f$\ddot{q}\f$. + * -# \b Null-Space: solve furst for \f$\ddot{q}\f$ and then for + * \f$\lambda\f$ + * The methods are the same for the contact gaints just with different + * variables on the right-hand-side. + * + * RBDL provides methods for all approaches. The implementation for the + * range-space method also exploits sparsities in the joint space inertia + * matrix using a sparse structure preserving \f$L^TL\f$ decomposition as + * described in Chapter 8.5 of "Rigid Body Dynamics Algorithms". + * + * None of the methods is generally superior to the others and each has + * different trade-offs as factors such as model topology, number of + * constraints, constrained bodies, numerical stability, and performance + * vary and evaluation has to be made on a case-by-case basis. + * + * \subsection solving_constraints_dynamics Methods for Solving Constrained + * Dynamics + * + * RBDL provides the following methods to compute the acceleration of a + * constrained system: + * + * - ForwardDynamicsConstraintsDirect() + * - ForwardDynamicsConstraintsRangeSpaceSparse() + * - ForwardDynamicsConstraintsNullSpace() + * + * \subsection solving_constraints_collisions Methods for Computing Collisions + * + * RBDL provides the following methods to compute the collision response on + * new contact gains: + * + * - ComputeConstraintImpulsesDirect() + * - ComputeConstraintImpulsesRangeSpaceSparse() + * - ComputeConstraintImpulsesNullSpace() + * + * \subsection assembly_q_qdot Computing generalized joint positions and velocities satisfying the constraint equations + * + * When considering a model subject position level constraints expressed by the + * equation \f$\phi (q) = 0\f$, it is often necessary to comput generalized joint + * position and velocities which satisfy the constraints. + * + * In order to compute a vector of generalized joint positions that satisfy + * the constraints it is necessary to solve the following optimization problem: + * \f{eqnarray*}{ + * \text{minimize} && \sum_{i = 0}^{n} (q - q_{0})^T W (q - q_{0}) \\ + * \text{over} && q \\ + * \text{subject to} && \phi (q) = 0 + * \f} + * + * In order to compute a vector of generalized joint velocities that satisfy + * the constraints it is necessary to solve the following optimization problem: + * \f{eqnarray*}{ + * \text{minimize} && \sum_{i = 0}^{n} (\dot{q} - \dot{q}_{0})^T W (\dot{q} - \dot{q}_{0}) \\ + * \text{over} && \dot{q} \\ + * \text{subject to} && \dot{\phi} (q) = \phi _{q}(q) \dot{q} + \phi _{t}(q) = 0 + * \f} + * + * \f$q_{0}\f$ and \f$\dot{q}_{0}\f$ are initial guesses, \f$\phi _{q}\f$ is the + * constraints jacobian (partial derivative of \f$\phi\f$ with respect to \f$q\f$), + * and \f$\phi _{t}(q)\f$ is the partial derivative of \f$\phi\f$ with respect + * to time. \f$W\f$ is a diagonal weighting matrix, which can be exploited + * to prioritize changes in the position/ velocity of specific joints. + * With a solver capable of handling singular matrices, it is possible to set to + * 1 the weight of the joint positions/ velocities that should not be changed + * from the initial guesses, and to 0 those corresponding to the values that + * can be changed. + * + * These problems are solved using the Lagrange multipliers method. For the + * velocity problem the solution is exact. For the position problem the + * constraints are linearized in the form + * \f$ \phi (q_{0}) + \phi _{t}(q0) + \phi _{q_0}(q) (q - q_{0}) \f$ + * and the linearized problem is solved iteratively until the constraint position + * errors are smaller than a given threshold. + * + * RBDL provides two functions to compute feasible joint position and velocities: + * - CalcAssemblyQ() + * - CalcAssemblyQDot() + * + * \subsection baumgarte_stabilization Baumgarte stabilization + * + * The constrained dynamic equations are correct in theory, but are not stable + * during numeric integration. RBDL implements Baumgarte stabilization to avoid + * the accumulation of position and velocity errors. + * + * The dynamic equations are changed to the following form: \f[ + \left( + \begin{array}{cc} + H & G^T \\ + G & 0 + \end{array} + \right) + \left( + \begin{array}{c} + \ddot{q} \\ + - \lambda + \end{array} + \right) + = + \left( + \begin{array}{c} + -C + \tau \\ + \gamma + \gamma _{stab} + \end{array} + \right) + * \f] A term \f$\gamma _{stab}\f$ is added to the right hand side of the + * equation, defined in the following way: \f[ + \gamma _{stab} = + - \frac{2}{T_{stab}} \dot{\phi} (q) + - \left(\frac{1}{T_{stab}} \right)^2 \phi (q) + * \f] where \f$\phi (q)\f$ are the position level constraint errors and + * \f$T_{stab}\f$ is a tuning coefficient. The value of \f$T_{stab}\f$ must + * be chosen carefully. If it is too large the stabilization will not be able + * to compensate the errors and the position and velocity errors will increase. + * If it is too small, the system of equations will become unnecessarily stiff. + * + * @{ + */ + +struct Model; + +/** \brief Structure that contains both constraint information and workspace memory. + * + * This structure is used to reduce the amount of memory allocations that + * are needed when computing constraint forces. + * + * The ConstraintSet has to be bound to a model using ConstraintSet::Bind() + * before it can be used in \link RigidBodyDynamics::ForwardDynamicsContacts + * ForwardDynamicsContacts \endlink. + */ +struct RBDL_DLLAPI ConstraintSet { + ConstraintSet() : + linear_solver (Math::LinearSolverColPivHouseholderQR), + bound (false) {} + + // Enum to describe the type of a constraint. + enum ConstraintType { + ContactConstraint, + LoopConstraint, + ConstraintTypeLast, + }; + + /** \brief Adds a contact constraint to the constraint set. + * + * This type of constraints ensures that the velocity and acceleration of a specified + * body point along a specified axis are null. This constraint does not act + * at the position level. + * + * \param body_id the body which is affected directly by the constraint + * \param body_point the point that is constrained relative to the + * contact body + * \param world_normal the normal along the constraint acts (in base + * coordinates) + * \param constraint_name a human readable name (optional, default: NULL) + * \param normal_acceleration the acceleration of the contact along the normal + * (optional, default: 0.) + */ + unsigned int AddContactConstraint ( + unsigned int body_id, + const Math::Vector3d &body_point, + const Math::Vector3d &world_normal, + const char *constraint_name = NULL, + double normal_acceleration = 0.); + + /** \brief Adds a loop constraint to the constraint set. + * + * This type of constraints ensures that the relative orientation and position, + * spatial velocity, and spatial acceleration between two frames in two bodies + * are null along a specified spatial constraint axis. + * + * \param id_predecessor the identifier of the predecessor body + * \param id_successor the identifier of the successor body + * \param X_predecessor a spatial transform localizing the constrained + * frames on the predecessor body, expressed with respect to the predecessor + * body frame + * \param X_successor a spatial transform localizing the constrained + * frames on the successor body, expressed with respect to the successor + * body frame + * \param axis a spatial vector indicating the axis along which the constraint + * acts + * \param T_stab_inv coefficient for Baumgarte stabilization. + * \param constraint_name a human readable name (optional, default: NULL) + * + */ + unsigned int AddLoopConstraint( + unsigned int id_predecessor, + unsigned int id_successor, + const Math::SpatialTransform &X_predecessor, + const Math::SpatialTransform &X_successor, + const Math::SpatialVector &axis, + double T_stab_inv, + const char *constraint_name = NULL + ); + + /** \brief Copies the constraints and resets its ConstraintSet::bound + * flag. + */ + ConstraintSet Copy() { + ConstraintSet result (*this); + result.bound = false; + + return result; + } + + /** \brief Specifies which method should be used for solving undelying linear systems. + */ + void SetSolver (Math::LinearSolver solver) { + linear_solver = solver; + } + + /** \brief Initializes and allocates memory for the constraint set. + * + * This function allocates memory for temporary values and matrices that + * are required for contact force computation. Both model and constraint + * set must not be changed after a binding as the required memory is + * dependent on the model size (i.e. the number of bodies and degrees of + * freedom) and the number of constraints in the Constraint set. + * + * The values of ConstraintSet::acceleration may still be + * modified after the set is bound to the model. + * + */ + bool Bind (const Model &model); + + /** \brief Returns the number of constraints. */ + size_t size() const { + return acceleration.size(); + } + + /** \brief Clears all variables in the constraint set. */ + void clear (); + + /// Method that should be used to solve internal linear systems. + Math::LinearSolver linear_solver; + /// Whether the constraint set was bound to a model (mandatory!). + bool bound; + + // Common constraints variables. + std::vector constraintType; + std::vector name; + std::vector contactConstraintIndices; + std::vector loopConstraintIndices; + + // Contact constraints variables. + std::vector body; + std::vector point; + std::vector normal; + + // Loop constraints variables. + std::vector body_p; + std::vector body_s; + std::vector X_p; + std::vector X_s; + std::vector constraintAxis; + /** Baumgarte stabilization parameter */ + std::vector T_stab_inv; + /** Position error for the Baumgarte stabilization */ + Math::VectorNd err; + /** Velocity error for the Baumgarte stabilization */ + Math::VectorNd errd; + + /** Enforced accelerations of the contact points along the contact + * normal. */ + Math::VectorNd acceleration; + /** Actual constraint forces along the contact normals. */ + Math::VectorNd force; + /** Actual constraint impulses along the contact normals. */ + Math::VectorNd impulse; + /** The velocities we want to have along the contact normals */ + Math::VectorNd v_plus; + + // Variables used by the Lagrangian methods + + /// Workspace for the joint space inertia matrix. + Math::MatrixNd H; + /// Workspace for the coriolis forces. + Math::VectorNd C; + /// Workspace of the lower part of b. + Math::VectorNd gamma; + Math::MatrixNd G; + /// Workspace for the Lagrangian left-hand-side matrix. + Math::MatrixNd A; + /// Workspace for the Lagrangian right-hand-side. + Math::VectorNd b; + /// Workspace for the Lagrangian solution. + Math::VectorNd x; + + /// Workspace when evaluating contact Jacobians + Math::MatrixNd Gi; + /// Workspace when evaluating loop Jacobians + Math::MatrixNd GSpi; + /// Workspace when evaluating loop Jacobians + Math::MatrixNd GSsi; + /// Workspace when evaluating loop Jacobians + Math::MatrixNd GSJ; + + /// Workspace for the QR decomposition of the null-space method +#ifdef RBDL_USE_SIMPLE_MATH + SimpleMath::HouseholderQR GT_qr; +#else + Eigen::HouseholderQR GT_qr; +#endif + + Math::MatrixNd GT_qr_Q; + Math::MatrixNd Y; + Math::MatrixNd Z; + Math::VectorNd qddot_y; + Math::VectorNd qddot_z; + + // Variables used by the IABI methods + + /// Workspace for the Inverse Articulated-Body Inertia. + Math::MatrixNd K; + /// Workspace for the accelerations of due to the test forces + Math::VectorNd a; + /// Workspace for the test accelerations. + Math::VectorNd QDDot_t; + /// Workspace for the default accelerations. + Math::VectorNd QDDot_0; + /// Workspace for the test forces. + std::vector f_t; + /// Workspace for the actual spatial forces. + std::vector f_ext_constraints; + /// Workspace for the default point accelerations. + std::vector point_accel_0; + + /// Workspace for the bias force due to the test force + std::vector d_pA; + /// Workspace for the acceleration due to the test force + std::vector d_a; + Math::VectorNd d_u; + + /// Workspace for the inertia when applying constraint forces + std::vector d_IA; + /// Workspace when applying constraint forces + std::vector d_U; + /// Workspace when applying constraint forces + Math::VectorNd d_d; + + std::vector d_multdof3_u; +}; + +/** \brief Computes the position errors for the given ConstraintSet. + * + * \param model the model + * \param Q the generalized positions of the joints + * \param CS the constraint set for which the error should be computed + * \param err (output) vector where the error will be stored in (should have + * the size of CS). + * \param update_kinematics whether the kinematics of the model should be + * updated from Q. + * + * \note the position error is always 0 for contact constraints. + * + */ +RBDL_DLLAPI +void CalcConstraintsPositionError( + Model& model, + const Math::VectorNd &Q, + ConstraintSet &CS, + Math::VectorNd& err, + bool update_kinematics = true +); + +/** \brief Computes the Jacobian for the given ConstraintSet + * + * \param model the model + * \param Q the generalized positions of the joints + * \param CS the constraint set for which the Jacobian should be computed + * \param G (output) matrix where the output will be stored in + * \param update_kinematics whether the kinematics of the model should be + * updated from Q + * + */ +RBDL_DLLAPI +void CalcConstraintsJacobian( + Model &model, + const Math::VectorNd &Q, + ConstraintSet &CS, + Math::MatrixNd &G, + bool update_kinematics = true +); + +/** \brief Computes the velocity errors for the given ConstraintSet. + * + * + * \param model the model + * \param Q the generalized positions of the joints + * \param QDot the generalized velocities of the joints + * \param CS the constraint set for which the error should be computed + * \param err (output) vector where the error will be stored in (should have + * the size of CS). + * \param update_kinematics whether the kinematics of the model should be + * updated from Q. + * + * \note this is equivalent to multiplying the constraint jacobian by the + * generalized velocities of the joints. + * + */ +RBDL_DLLAPI +void CalcConstraintsVelocityError( + Model& model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + ConstraintSet &CS, + Math::VectorNd& err, + bool update_kinematics = true +); + +/** \brief Computes the terms \f$H\f$, \f$G\f$, and \f$\gamma\f$ of the + * constrained dynamic problem and stores them in the ConstraintSet. + * + * + * \param model the model + * \param Q the generalized positions of the joints + * \param QDot the generalized velocities of the joints + * \param Tau the generalized forces of the joints + * \param CS the constraint set for which the error should be computed + * + * \note This function is normally called automatically in the various + * constrained dynamics functions, the user normally does not have to call it. + * + */ +RBDL_DLLAPI +void CalcConstrainedSystemVariables ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS +); + +/** \brief Computes a feasible initial value of the generalized joint positions. + * + * \param model the model + * \param QInit initial guess for the generalized positions of the joints + * \param CS the constraint set for which the error should be computed + * \param Q (output) vector of the generalized joint positions. + * \param weights weighting coefficients for the different joint positions. + * \param tolerance the function will return successfully if the constraint + * position error norm is lower than this value. + * \param max_iter the funciton will return unsuccessfully after performing + * this number of iterations. + * + * \return true if the generalized joint positions were computed successfully, + * false otherwise. + * + */ +RBDL_DLLAPI +bool CalcAssemblyQ( + Model &model, + Math::VectorNd QInit, + ConstraintSet &CS, + Math::VectorNd &Q, + const Math::VectorNd &weights, + double tolerance = 1e-12, + unsigned int max_iter = 100 +); + +/** \brief Computes a feasible initial value of the generalized joint velocities. + * + * \param model the model + * \param Q the generalized joint position of the joints. It is assumed that + * this vector satisfies the position level assemblt constraints. + * \param QDotInit initial guess for the generalized velocities of the joints + * \param CS the constraint set for which the error should be computed + * \param QDot (output) vector of the generalized joint velocities. + * \param weights weighting coefficients for the different joint positions. + * + */ +RBDL_DLLAPI +void CalcAssemblyQDot( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotInit, + ConstraintSet &CS, + Math::VectorNd &QDot, + const Math::VectorNd &weights +); + +/** \brief Computes forward dynamics with contact by constructing and solving + * the full lagrangian equation + * + * This method builds and solves the linear system \f[ + \left( + \begin{array}{cc} + H & G^T \\ + G & 0 + \end{array} + \right) + \left( + \begin{array}{c} + \ddot{q} \\ + -\lambda + \end{array} + \right) + = + \left( + \begin{array}{c} + -C + \tau \\ + \gamma + \end{array} + \right) + * \f] where \f$H\f$ is the joint space inertia matrix computed with the + * CompositeRigidBodyAlgorithm(), \f$G\f$ are the point jacobians of the + * contact points, \f$C\f$ the bias force (sometimes called "non-linear + * effects"), and \f$\gamma\f$ the generalized acceleration independent + * part of the contact point accelerations. + * + * \note So far, only constraints acting along cartesian coordinate axes + * are allowed (i.e. (1, 0, 0), (0, 1, 0), and (0, 0, 1)). Also, one must + * not specify redundant constraints! + * + * \par + * + * \note To increase performance group constraints body and pointwise such + * that constraints acting on the same body point are sequentially in + * ConstraintSet. This can save computation of point jacobians \f$G\f$. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param Tau actuations of the internal joints + * \param CS the description of all acting constraints + * \param QDDot accelerations of the internals joints (output) + * + * \note During execution of this function values such as + * ConstraintSet::force get modified and will contain the value + * of the force acting along the normal. + * + */ +RBDL_DLLAPI +void ForwardDynamicsConstraintsDirect ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS, + Math::VectorNd &QDDot +); + +RBDL_DLLAPI +void ForwardDynamicsConstraintsRangeSpaceSparse ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS, + Math::VectorNd &QDDot +); + +RBDL_DLLAPI +void ForwardDynamicsConstraintsNullSpace ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS, + Math::VectorNd &QDDot +); + +/** \brief Computes forward dynamics that accounts for active contacts in + * ConstraintSet. + * + * The method used here is the one described by Kokkevis and Metaxas in the + * Paper "Practical Physics for Articulated Characters", Game Developers + * Conference, 2004. + * + * It does this by recursively computing the inverse articulated-body inertia (IABI) + * \f$\Phi_{i,j}\f$ which is then used to build and solve a system of the form: + \f[ + \left( + \begin{array}{c} + \dot{v}_1 \\ + \dot{v}_2 \\ + \vdots \\ + \dot{v}_n + \end{array} + \right) + = + \left( + \begin{array}{cccc} + \Phi_{1,1} & \Phi_{1,2} & \cdots & \Phi{1,n} \\ + \Phi_{2,1} & \Phi_{2,2} & \cdots & \Phi{2,n} \\ + \cdots & \cdots & \cdots & \vdots \\ + \Phi_{n,1} & \Phi_{n,2} & \cdots & \Phi{n,n} + \end{array} + \right) + \left( + \begin{array}{c} + f_1 \\ + f_2 \\ + \vdots \\ + f_n + \end{array} + \right) + + + \left( + \begin{array}{c} + \phi_1 \\ + \phi_2 \\ + \vdots \\ + \phi_n + \end{array} + \right). + \f] + Here \f$n\f$ is the number of constraints and the method for building the system + uses the Articulated Body Algorithm to efficiently compute entries of the system. The + values \f$\dot{v}_i\f$ are the constraint accelerations, \f$f_i\f$ the constraint forces, + and \f$\phi_i\f$ are the constraint bias forces. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param Tau actuations of the internal joints + * \param CS a list of all contact points + * \param QDDot accelerations of the internals joints (output) + * + * \note During execution of this function values such as + * ConstraintSet::force get modified and will contain the value + * of the force acting along the normal. + * + * \note This function supports only contact constraints. + * + * \todo Allow for external forces + */ +RBDL_DLLAPI +void ForwardDynamicsContactsKokkevis ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS, + Math::VectorNd &QDDot +); + +/** \brief Computes contact gain by constructing and solving the full lagrangian + * equation + * + * This method builds and solves the linear system \f[ + \left( + \begin{array}{cc} + H & G^T \\ + G & 0 + \end{array} + \right) + \left( + \begin{array}{c} + \dot{q}^{+} \\ + \Lambda + \end{array} + \right) + = + \left( + \begin{array}{c} + H \dot{q}^{-} \\ + v^{+} + \end{array} + \right) + * \f] where \f$H\f$ is the joint space inertia matrix computed with the + * CompositeRigidBodyAlgorithm(), \f$G\f$ are the point jacobians of the + * contact points, \f$\dot{q}^{+}\f$ the generalized velocity after the + * impact, \f$\Lambda\f$ the impulses at each constraint, \f$\dot{q}^{-}\f$ + * the generalized velocity before the impact, and \f$v^{+}\f$ the desired + * velocity of each constraint after the impact (known beforehand, usually + * 0). The value of \f$v^{+}\f$ can is specified via the variable + * ConstraintSet::v_plus and defaults to 0. + * + * \note So far, only constraints acting along cartesian coordinate axes + * are allowed (i.e. (1, 0, 0), (0, 1, 0), and (0, 0, 1)). Also, one must + * not specify redundant constraints! + * + * \par + * + * \note To increase performance group constraints body and pointwise such + * that constraints acting on the same body point are sequentially in + * ConstraintSet. This can save computation of point Jacobians \f$G\f$. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDotMinus velocity vector of the internal joints before the impact + * \param CS the set of active constraints + * \param QDotPlus velocities of the internals joints after the impact (output) + */ +RBDL_DLLAPI +void ComputeConstraintImpulsesDirect ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotMinus, + ConstraintSet &CS, + Math::VectorNd &QDotPlus +); + +/** \brief Resolves contact gain using SolveContactSystemRangeSpaceSparse() + */ +RBDL_DLLAPI +void ComputeConstraintImpulsesRangeSpaceSparse ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotMinus, + ConstraintSet &CS, + Math::VectorNd &QDotPlus +); + +/** \brief Resolves contact gain using SolveContactSystemNullSpace() + */ +RBDL_DLLAPI +void ComputeConstraintImpulsesNullSpace ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotMinus, + ConstraintSet &CS, + Math::VectorNd &QDotPlus +); + +/** \brief Solves the full contact system directly, i.e. simultaneously for + * contact forces and joint accelerations. + * + * This solves a \f$ (n_\textit{dof} + + * n_c) \times (n_\textit{dof} + n_c\f$ linear system. + * + * \param H the joint space inertia matrix + * \param G the constraint jacobian + * \param c the \f$ \mathbb{R}^{n_\textit{dof}}\f$ vector of the upper part of + * the right hand side of the system + * \param gamma the \f$ \mathbb{R}^{n_c}\f$ vector of the lower part of the + * right hand side of the system + * \param qddot result: joint accelerations + * \param lambda result: constraint forces + * \param A work-space for the matrix of the linear system + * \param b work-space for the right-hand-side of the linear system + * \param x work-space for the solution of the linear system + * \param type of solver that should be used to solve the system + */ +RBDL_DLLAPI +void SolveConstrainedSystemDirect ( + Math::MatrixNd &H, + const Math::MatrixNd &G, + const Math::VectorNd &c, + const Math::VectorNd &gamma, + Math::VectorNd &qddot, + Math::VectorNd &lambda, + Math::MatrixNd &A, + Math::VectorNd &b, + Math::VectorNd &x, + Math::LinearSolver &linear_solver +); + +/** \brief Solves the contact system by first solving for the the joint + * accelerations and then the contact forces using a sparse matrix + * decomposition of the joint space inertia matrix. + * + * This method exploits the branch-induced sparsity by the structure + * preserving \f$L^TL \f$ decomposition described in RBDL, Section 6.5. + * + * \param H the joint space inertia matrix + * \param G the constraint jacobian + * \param c the \f$ \mathbb{R}^{n_\textit{dof}}\f$ vector of the upper part of + * the right hand side of the system + * \param gamma the \f$ \mathbb{R}^{n_c}\f$ vector of the lower part of the + * right hand side of the system + * \param qddot result: joint accelerations + * \param lambda result: constraint forces + * \param K work-space for the matrix of the constraint force linear system + * \param a work-space for the right-hand-side of the constraint force linear + * system + * \param linear_solver type of solver that should be used to solve the + * constraint force system + */ +RBDL_DLLAPI +void SolveConstrainedSystemRangeSpaceSparse ( + Model &model, + Math::MatrixNd &H, + const Math::MatrixNd &G, + const Math::VectorNd &c, + const Math::VectorNd &gamma, + Math::VectorNd &qddot, + Math::VectorNd &lambda, + Math::MatrixNd &K, + Math::VectorNd &a, + Math::LinearSolver linear_solver +); + +/** \brief Solves the contact system by first solving for the joint + * accelerations and then for the constraint forces. + * + * This methods requires a \f$n_\textit{dof} \times n_\textit{dof}\f$ + * matrix of the form \f$\left[ \ Y \ | Z \ \right]\f$ with the property + * \f$GZ = 0\f$ that can be computed using a QR decomposition (e.g. see + * code for ForwardDynamicsContactsNullSpace()). + * + * \param H the joint space inertia matrix + * \param G the constraint jacobian + * \param c the \f$ \mathbb{R}^{n_\textit{dof}}\f$ vector of the upper part of + * the right hand side of the system + * \param gamma the \f$ \mathbb{R}^{n_c}\f$ vector of the lower part of the + * right hand side of the system + * \param qddot result: joint accelerations + * \param lambda result: constraint forces + * \param Y basis for the range-space of the constraints + * \param Z basis for the null-space of the constraints + * \param qddot_y work-space of size \f$\mathbb{R}^{n_\textit{dof}}\f$ + * \param qddot_z work-space of size \f$\mathbb{R}^{n_\textit{dof}}\f$ + * \param linear_solver type of solver that should be used to solve the system + */ +RBDL_DLLAPI +void SolveConstrainedSystemNullSpace ( + Math::MatrixNd &H, + const Math::MatrixNd &G, + const Math::VectorNd &c, + const Math::VectorNd &gamma, + Math::VectorNd &qddot, + Math::VectorNd &lambda, + Math::MatrixNd &Y, + Math::MatrixNd &Z, + Math::VectorNd &qddot_y, + Math::VectorNd &qddot_z, + Math::LinearSolver &linear_solver +); + +/** @} */ + +} /* namespace RigidBodyDynamics */ + +/* RBDL_CONSTRAINTS_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Dynamics.h b/3rdparty/rbdl/include/rbdl/Dynamics.h new file mode 100644 index 0000000..af490e9 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Dynamics.h @@ -0,0 +1,182 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_DYNAMICS_H +#define RBDL_DYNAMICS_H + +#include +#include + +#include "rbdl/rbdl_math.h" +#include "rbdl/rbdl_mathutils.h" + +#include "rbdl/Logging.h" + +namespace RigidBodyDynamics { + +struct Model; + +/** \page dynamics_page Dynamics + * + * All functions related to kinematics are specified in the \ref + * dynamics_group "Dynamics Module". + * + * \defgroup dynamics_group Dynamics + * @{ + */ + +/** \brief Computes inverse dynamics with the Newton-Euler Algorithm + * + * This function computes the generalized forces from given generalized + * states, velocities, and accelerations: + * \f$ \tau = M(q) \ddot{q} + N(q, \dot{q}) \f$ + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param QDDot accelerations of the internals joints + * \param Tau actuations of the internal joints (output) + * \param f_ext External forces acting on the body in base coordinates (optional, defaults to NULL) + */ +RBDL_DLLAPI void InverseDynamics ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &QDDot, + Math::VectorNd &Tau, + std::vector *f_ext = NULL + ); + +/** \brief Computes the coriolis forces + * + * This function computes the generalized forces from given generalized + * states, velocities, and accelerations: + * \f$ \tau = M(q) \ddot{q} + N(q, \dot{q}) \f$ + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param Tau actuations of the internal joints (output) + */ +RBDL_DLLAPI void NonlinearEffects ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + Math::VectorNd &Tau + ); + +/** \brief Computes the joint space inertia matrix by using the Composite Rigid Body Algorithm + * + * This function computes the joint space inertia matrix from a given model and + * the generalized state vector: + * \f$ M(q) \f$ + * + * \param model rigid body model + * \param Q state vector of the model + * \param H a matrix where the result will be stored in + * \param update_kinematics whether the kinematics should be updated (safer, but at a higher computational cost!) + * + * \note This function only evaluates the entries of H that are non-zero. One + * Before calling this function one has to ensure that all other values + * have been set to zero, e.g. by calling H.setZero(). + */ +RBDL_DLLAPI void CompositeRigidBodyAlgorithm ( + Model& model, + const Math::VectorNd &Q, + Math::MatrixNd &H, + bool update_kinematics = true + ); + +/** \brief Computes forward dynamics with the Articulated Body Algorithm + * + * This function computes the generalized accelerations from given + * generalized states, velocities and forces: + * \f$ \ddot{q} = M(q)^{-1} ( -N(q, \dot{q}) + \tau)\f$ + * It does this by using the recursive Articulated Body Algorithm that runs + * in \f$O(n_{dof})\f$ with \f$n_{dof}\f$ being the number of joints. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param Tau actuations of the internal joints + * \param QDDot accelerations of the internal joints (output) + * \param f_ext External forces acting on the body in base coordinates (optional, defaults to NULL) + */ +RBDL_DLLAPI void ForwardDynamics ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + Math::VectorNd &QDDot, + std::vector *f_ext = NULL + ); + +/** \brief Computes forward dynamics by building and solving the full Lagrangian equation + * + * This method builds and solves the linear system + * \f[ H \ddot{q} = -C + \tau \f] + * for \f$\ddot{q}\f$ where \f$H\f$ is the joint space inertia matrix + * computed with the CompositeRigidBodyAlgorithm(), \f$C\f$ the bias + * force (sometimes called "non-linear effects"). + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param Tau actuations of the internal joints + * \param QDDot accelerations of the internal joints (output) + * \param linear_solver specification which method should be used for solving the linear system + * \param f_ext External forces acting on the body in base coordinates (optional, defaults to NULL) + * \param H preallocated workspace area for the joint space inertia matrix of size dof_count x dof_count (optional, defaults to NULL and allocates temporary matrix) + * \param C preallocated workspace area for the right hand side vector of size dof_count x 1 (optional, defaults to NULL and allocates temporary vector) + */ +RBDL_DLLAPI void ForwardDynamicsLagrangian ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + Math::VectorNd &QDDot, + Math::LinearSolver linear_solver = Math::LinearSolverColPivHouseholderQR, + std::vector *f_ext = NULL, + Math::MatrixNd *H = NULL, + Math::VectorNd *C = NULL + ); + +/** \brief Computes the effect of multiplying the inverse of the joint + * space inertia matrix with a vector in linear time. + * + * \param model rigid body model + * \param Q state vector of the generalized positions + * \param Tau the vector that should be multiplied with the inverse of + * the joint space inertia matrix + * \param QDDot vector where the result will be stored + * \param update_kinematics whether the kinematics should be updated (safer, but at a higher computational cost) + * + * This function uses a reduced version of the Articulated %Body Algorithm + * to compute + * + * \f$ \ddot{q} = M(q)^{-1} ( -N(q, \dot{q}) + \tau)\f$ + * + * in \f$O(n_{\textit{dof}}\f$) time. + * + * \note When calling this function repeatedly for the same values of Q make sure + * to set the last parameter to false as this avoids expensive + * recomputations of transformations and articulated body inertias. + */ +RBDL_DLLAPI void CalcMInvTimesTau ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &Tau, + Math::VectorNd &QDDot, + bool update_kinematics=true + ); + +/** @} */ + +} + +/* RBDL_DYNAMICS_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Joint.h b/3rdparty/rbdl/include/rbdl/Joint.h new file mode 100644 index 0000000..7dce1c0 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Joint.h @@ -0,0 +1,694 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_JOINT_H +#define RBDL_JOINT_H + +#include "rbdl/rbdl_math.h" +#include +#include +#include "rbdl/Logging.h" + +namespace RigidBodyDynamics { + +struct Model; + +/** \page joint_description Joint Modeling + * + * \section joint_overview Overview + * + * The Rigid Body Dynamics Library supports a multitude of joints: + * revolute, planar, fixed, singularity-free spherical joints and joints + * with multiple degrees of freedom in any combinations. + * + * Fixed joints do not cause any overhead in RBDL as the bodies that are + * rigidly connected are merged into a single body. For details see \ref + * joint_models_fixed. + * + * Joints with multiple degrees of freedom are emulated by default which + * means that they are split up into multiple single degree of freedom + * joints which results in equivalent models. This has the benefit that it + * simplifies the required algebra and also code branching in RBDL. A + * special case are joints with three degrees of freedom for which specific + * joints are available that should be used for performance reasons + * whenever possible. See \ref joint_three_dof for details. + * + * Joints are defined by their motion subspace. For each degree of freedom + * a one dimensional motion subspace is specified as a Math::SpatialVector. + * This vector follows the following convention: \f[ (r_x, r_y, r_z, t_x, + * t_y, t_z) \f] + * + * To specify a planar joint with three degrees of freedom for which the + * first two are translations in \f$x\f$ and \f$y\f$ direction and the last + * is a rotation around \f$z\f$, the following joint definition can be + * used: + + * \code Joint planar_joint = Joint ( + * Math::SpatialVector (0., 0., 0., 1., 0., 0.), + * Math::SpatialVector (0., 0., 0., 0., 1., 0.), + * Math::SpatialVector (0., 0., 1., 0., 0., 0.) + * ); + * \endcode + + * \note Please note that in the Rigid %Body Dynamics Library all angles + * are specified in radians. + * + * \section joint_models_fixed Fixed Joints + * + * Fixed joints do not add an additional degree of freedom to the model. + * When adding a body that via a fixed joint (i.e. when the type is + * JointTypeFixed) then the dynamical parameters mass and inertia are + * merged onto its moving parent. By doing so fixed bodies do not add + * computational costs when performing dynamics computations. + + * To ensure a consistent API for the Kinematics such fixed bodies have a + * different range of ids. Where as the ids start at 1 get incremented for + * each added body, fixed bodies start at Model::fixed_body_discriminator + * which has a default value of std::numeric_limits::max() / + * 2. This means theoretical a maximum of each 2147483646 movable and fixed + * bodies are possible. + + * To check whether a body is connected by a fixed joint you can use the + * function Model::IsFixedBodyId(). + + * \section joint_three_dof 3-DoF Joints + * + * RBDL has highly efficient implementations for the following three degree + * of freedom joints: + *
    + *
  • \ref JointTypeTranslationXYZ which first translates along X, then + * Y, and finally Z.
  • + *
  • \ref JointTypeEulerZYX which first rotates around Z, then Y, and + * then X.
  • + *
  • \ref JointTypeEulerXYZ which first rotates around X, then Y, and + * then Z.
  • + *
  • \ref JointTypeEulerYXZ which first rotates around Y, then X, and + * then Z.
  • + *
  • \ref JointTypeSpherical which is a singularity free joint that + * uses a Quaternion and the bodies angular velocity (see \ref + * joint_singularities for details).
  • + *
+ * + * These joints can be created by providing the joint type as an argument + * to the Joint constructor, e.g.: + * + * \code Joint joint_rot_zyx = Joint ( JointTypeEulerZYX ); \endcode + * + * Using 3-Dof joints is always favourable over using their emulated + * counterparts as they are considerably faster and describe the same + * kinematics and dynamics. + + * \section joint_floatingbase Floating-Base Joint (a.k.a. Freeflyer Joint) + * + * RBDL has a special joint type for floating-base systems that uses the + * enum JointTypeFloatingBase. The first three DoF are translations along + * X,Y, and Z. For the rotational part it uses a JointTypeSpherical joint. + * It is internally modeled by a JointTypeTranslationXYZ and a + * JointTypeSpherical joint. It is recommended to only use this joint for + * the very first body added to the model. + * + * Positional variables are translations along X, Y, and Z, and for + * rotations it uses Quaternions. To set/get the orientation use + * Model::SetQuaternion () / Model::GetQuaternion() with the body id + * returned when adding the floating base (i.e. the call to + * Model::AddBody() or Model::AppendBody()). + + * \section joint_singularities Joint Singularities + + * Singularities in the models arise when a joint has three rotational + * degrees of freedom and the rotations are described by Euler- or + * Cardan-angles. The singularities present in these rotation + * parametrizations (e.g. for ZYX Euler-angles for rotations where a + * +/- 90 degrees rotation around the Y-axis) may cause problems in + * dynamics calculations, such as a rank-deficit joint-space inertia matrix + * or exploding accelerations in the forward dynamics calculations. + * + * For this case RBDL has the special joint type + * RigidBodyDynamics::JointTypeSpherical. When using this joint type the + * model does not suffer from singularities, however this also results in + * a change of interpretation for the values \f$\mathbf{q}, \mathbf{\dot{q}}, \mathbf{\ddot{q}}\f$, and \f$\mathbf{\tau}\f$: + * + *
    + *
  • The values in \f$\mathbf{q}\f$ for the joint parameterizes the orientation of a joint using a + * Quaternion \f$q_i\f$
  • + *
  • The values in \f$\mathbf{\dot{q}}\f$ for the joint describe the angular + * velocity \f$\omega\f$ of the joint in body coordinates
  • + *
  • The values in \f$\mathbf{\ddot{q}}\f$ for the joint describe the angular + * acceleration \f$\dot{\omega}\f$ of the joint in body coordinates
  • + *
  • The values in \f$\mathbf{\tau}\f$ for the joint describe the three couples + * acting on the body in body coordinates that are actuated by the joint.
  • + *
+ + * As a result, the dimension of the vector \f$\mathbf{q}\f$ is higher than + * of the vector of the velocity variables. Additionally, the values in + * \f$\mathbf{\dot{q}}\f$ are \b not the derivative of \f$q\f$ and are therefore + * denoted by \f$\mathbf{\bar{q}}\f$ (and similarly for the joint + * accelerations). + + * RBDL stores the Quaternions in \f$\mathbf{q}\f$ such that the 4th component of + * the joint is appended to \f$\mathbf{q}\f$. E.g. for a model with the joints: + * TX, Spherical, TY, Spherical, the values of \f$\mathbf{q},\mathbf{\bar{q}},\mathbf{\bar{\bar{q}}},\mathbf{\tau}\f$ are: + * + + \f{eqnarray*} + \mathbf{q} &=& ( q_{tx}, q_{q1,x}, q_{q1,y}, q_{q1,z}, q_{ty}, q_{q2,x}, q_{q2,y}, q_{q2,z}, q_{q1,w}, q_{q2,w})^T \\ + \mathbf{\bar{q}} &=& ( \dot{q}_{tx}, \omega_{1,x}, \omega_{1,y}, \omega_{1,z}, \dot{q}_{ty}, \omega_{2,x}, \omega_{2,y}, \omega_{2,z} )^T \\ + \mathbf{\bar{\bar{q}}} &=& ( \ddot{q}_{tx}, \dot{\omega}_{1,x}, \dot{\omega}_{1,y}, \dot{\omega}_{1,z}, \ddot{q}_{ty}, \dot{\omega}_{2,x}, \dot{\omega}_{2,y}, \dot{\omega}_{2,z} )^T \\ + \mathbf{\tau} &=& ( \tau_{tx}, \tau_{1,x}, \tau_{1,y}, \tau_{1,z}, \tau_{ty}, \tau_{2,x}, \tau_{2,y}, \tau_{2,z} )^T + \f} + + * \subsection spherical_integration Numerical Integration of Quaternions + * + * An additional consequence of this is, that special treatment is required + * when numerically integrating the angular velocities. One possibility is + * to interpret the angular velocity as an axis-angle pair scaled by the + * timestep and use it create a quaternion that is applied to the previous + * Quaternion. Another is to compute the quaternion rates from the angular + * velocity. For details see James Diebel "Representing Attitude: Euler + * Angles, Unit Quaternions, and Rotation Vectors", 2006, + * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.110.5134. + * + */ + + /** \brief General types of joints + */ + enum JointType { + JointTypeUndefined = 0, + JointTypeRevolute, + JointTypePrismatic, + JointTypeRevoluteX, + JointTypeRevoluteY, + JointTypeRevoluteZ, + JointTypeSpherical, ///< 3 DoF joint using Quaternions for joint positional variables and angular velocity for joint velocity variables. + JointTypeEulerZYX, ///< 3 DoF joint that uses Euler ZYX convention (faster than emulated multi DoF joints). + JointTypeEulerXYZ, ///< 3 DoF joint that uses Euler XYZ convention (faster than emulated multi DoF joints). + JointTypeEulerYXZ, ///< 3 DoF joint that uses Euler YXZ convention (faster than emulated multi DoF joints). + JointTypeTranslationXYZ, + JointTypeFloatingBase, ///< A 6-DoF joint for floating-base (or freeflyer) systems. + JointTypeFixed, ///< Fixed joint which causes the inertial properties to be merged with the parent body. + JointTypeHelical, //1 DoF joint with both rotational and translational motion + JointType1DoF, + JointType2DoF, ///< Emulated 2 DoF joint. + JointType3DoF, ///< Emulated 3 DoF joint. + JointType4DoF, ///< Emulated 4 DoF joint. + JointType5DoF, ///< Emulated 5 DoF joint. + JointType6DoF, ///< Emulated 6 DoF joint. + JointTypeCustom, ///< User defined joints of varying size + }; + +/** \brief Describes a joint relative to the predecessor body. + * + * This class contains all information required for one single joint. This + * contains the joint type and the axis of the joint. See \ref joint_description for detailed description. + * + */ +struct RBDL_DLLAPI Joint { + Joint() : + mJointAxes (NULL), + mJointType (JointTypeUndefined), + mDoFCount (0), + q_index (0) {}; + Joint (JointType type) : + mJointAxes (NULL), + mJointType (type), + mDoFCount (0), + custom_joint_index(-1), + q_index (0) { + if (type == JointTypeRevoluteX) { + mDoFCount = 1; + mJointAxes = new Math::SpatialVector[mDoFCount]; + mJointAxes[0] = Math::SpatialVector (1., 0., 0., 0., 0., 0.); + } else if (type == JointTypeRevoluteY) { + mDoFCount = 1; + mJointAxes = new Math::SpatialVector[mDoFCount]; + mJointAxes[0] = Math::SpatialVector (0., 1., 0., 0., 0., 0.); + } else if (type == JointTypeRevoluteZ) { + mDoFCount = 1; + mJointAxes = new Math::SpatialVector[mDoFCount]; + mJointAxes[0] = Math::SpatialVector (0., 0., 1., 0., 0., 0.); + } else if (type == JointTypeSpherical) { + mDoFCount = 3; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = Math::SpatialVector (0., 0., 1., 0., 0., 0.); + mJointAxes[1] = Math::SpatialVector (0., 1., 0., 0., 0., 0.); + mJointAxes[2] = Math::SpatialVector (1., 0., 0., 0., 0., 0.); + } else if (type == JointTypeEulerZYX) { + mDoFCount = 3; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = Math::SpatialVector (0., 0., 1., 0., 0., 0.); + mJointAxes[1] = Math::SpatialVector (0., 1., 0., 0., 0., 0.); + mJointAxes[2] = Math::SpatialVector (1., 0., 0., 0., 0., 0.); + } else if (type == JointTypeEulerXYZ) { + mDoFCount = 3; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = Math::SpatialVector (1., 0., 0., 0., 0., 0.); + mJointAxes[1] = Math::SpatialVector (0., 1., 0., 0., 0., 0.); + mJointAxes[2] = Math::SpatialVector (0., 0., 1., 0., 0., 0.); + } else if (type == JointTypeEulerYXZ) { + mDoFCount = 3; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = Math::SpatialVector (0., 1., 0., 0., 0., 0.); + mJointAxes[1] = Math::SpatialVector (1., 0., 0., 0., 0., 0.); + mJointAxes[2] = Math::SpatialVector (0., 0., 1., 0., 0., 0.); + } else if (type == JointTypeTranslationXYZ) { + mDoFCount = 3; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = Math::SpatialVector (0., 0., 0., 1., 0., 0.); + mJointAxes[1] = Math::SpatialVector (0., 0., 0., 0., 1., 0.); + mJointAxes[2] = Math::SpatialVector (0., 0., 0., 0., 0., 1.); + } else if (type >= JointType1DoF && type <= JointType6DoF) { + // create a joint and allocate memory for it. + // Warning: the memory does not get initialized by this function! + mDoFCount = type - JointType1DoF + 1; + mJointAxes = new Math::SpatialVector[mDoFCount]; + std::cerr << "Warning: uninitalized vector" << std::endl; + } else if (type == JointTypeCustom) { + //This constructor cannot be used for a JointTypeCustom because + //we have no idea what mDoFCount is. + std::cerr << "Error: Invalid use of Joint constructor Joint(JointType" + << " type). Only allowed when type != JointTypeCustom" + << std::endl; + assert(0); + abort(); + } else if (type != JointTypeFixed && type != JointTypeFloatingBase) { + std::cerr << "Error: Invalid use of Joint constructor Joint(JointType type). Only allowed when type == JointTypeFixed or JointTypeSpherical." << std::endl; + assert (0); + abort(); + } + } + Joint (JointType type, int degreesOfFreedom) : + mJointAxes (NULL), + mJointType (type), + mDoFCount (0), + custom_joint_index(-1), + q_index (0) { + if (type == JointTypeCustom) { + mDoFCount = degreesOfFreedom; + mJointAxes = new Math::SpatialVector[mDoFCount]; + for(int i=0; i 0) { + assert (mJointAxes); + delete[] mJointAxes; + } + mJointType = joint.mJointType; + mDoFCount = joint.mDoFCount; + custom_joint_index = joint.custom_joint_index; + mJointAxes = new Math::SpatialVector[mDoFCount]; + + for (unsigned int i = 0; i < mDoFCount; i++) + mJointAxes[i] = joint.mJointAxes[i]; + + q_index = joint.q_index; + } + return *this; + } + ~Joint() { + if (mJointAxes) { + assert (mJointAxes); + delete[] mJointAxes; + mJointAxes = NULL; + mDoFCount = 0; + custom_joint_index = -1; + } + } + + /** \brief Constructs a joint from the given cartesian parameters. + * + * This constructor creates all the required spatial values for the given + * cartesian parameters. + * + * \param joint_type whether the joint is revolute or prismatic + * \param joint_axis the axis of rotation or translation + */ + Joint ( + const JointType joint_type, + const Math::Vector3d &joint_axis + ) { + mDoFCount = 1; + mJointAxes = new Math::SpatialVector[mDoFCount]; + + // Some assertions, as we concentrate on simple cases + + // Only rotation around the Z-axis + assert ( joint_type == JointTypeRevolute || joint_type == JointTypePrismatic ); + + mJointType = joint_type; + + if (joint_type == JointTypeRevolute) { + // make sure we have a unit axis + mJointAxes[0].set ( + joint_axis[0], + joint_axis[1], + joint_axis[2], + 0., 0., 0. + ); + + } else if (joint_type == JointTypePrismatic) { + // make sure we have a unit axis + assert (joint_axis.squaredNorm() == 1.); + + mJointAxes[0].set ( + 0., 0., 0., + joint_axis[0], + joint_axis[1], + joint_axis[2] + ); + } + } + + /** \brief Constructs a 1 DoF joint with the given motion subspaces. + * + * The motion subspaces are of the format: + * \f[ (r_x, r_y, r_z, t_x, t_y, t_z) \f] + * + * \param axis_0 Motion subspace for axis 0 + */ + Joint ( + const Math::SpatialVector &axis_0 + ) { + mDoFCount = 1; + mJointAxes = new Math::SpatialVector[mDoFCount]; + mJointAxes[0] = Math::SpatialVector (axis_0); + if (axis_0 == Math::SpatialVector(1., 0., 0., 0., 0., 0.)) { + mJointType = JointTypeRevoluteX; + } else if (axis_0 == Math::SpatialVector(0., 1., 0., 0., 0., 0.)) { + mJointType = JointTypeRevoluteY; + } else if (axis_0 == Math::SpatialVector(0., 0., 1., 0., 0., 0.)) { + mJointType = JointTypeRevoluteZ; + } else if (axis_0[0] == 0 && + axis_0[1] == 0 && + axis_0[2] == 0) { + mJointType = JointTypePrismatic; + } else { + mJointType = JointTypeHelical; + } + validate_spatial_axis (mJointAxes[0]); + } + + /** \brief Constructs a 2 DoF joint with the given motion subspaces. + * + * The motion subspaces are of the format: + * \f[ (r_x, r_y, r_z, t_x, t_y, t_z) \f] + * + * \note So far only pure rotations or pure translations are supported. + * + * \param axis_0 Motion subspace for axis 0 + * \param axis_1 Motion subspace for axis 1 + */ + Joint ( + const Math::SpatialVector &axis_0, + const Math::SpatialVector &axis_1 + ) { + mJointType = JointType2DoF; + mDoFCount = 2; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + mJointAxes[0] = axis_0; + mJointAxes[1] = axis_1; + + validate_spatial_axis (mJointAxes[0]); + validate_spatial_axis (mJointAxes[1]); + } + + /** \brief Constructs a 3 DoF joint with the given motion subspaces. + * + * The motion subspaces are of the format: + * \f[ (r_x, r_y, r_z, t_x, t_y, t_z) \f] + * + * \note So far only pure rotations or pure translations are supported. + * + * \param axis_0 Motion subspace for axis 0 + * \param axis_1 Motion subspace for axis 1 + * \param axis_2 Motion subspace for axis 2 + */ + Joint ( + const Math::SpatialVector &axis_0, + const Math::SpatialVector &axis_1, + const Math::SpatialVector &axis_2 + ) { + mJointType = JointType3DoF; + mDoFCount = 3; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = axis_0; + mJointAxes[1] = axis_1; + mJointAxes[2] = axis_2; + + validate_spatial_axis (mJointAxes[0]); + validate_spatial_axis (mJointAxes[1]); + validate_spatial_axis (mJointAxes[2]); + } + + /** \brief Constructs a 4 DoF joint with the given motion subspaces. + * + * The motion subspaces are of the format: + * \f[ (r_x, r_y, r_z, t_x, t_y, t_z) \f] + * + * \note So far only pure rotations or pure translations are supported. + * + * \param axis_0 Motion subspace for axis 0 + * \param axis_1 Motion subspace for axis 1 + * \param axis_2 Motion subspace for axis 2 + * \param axis_3 Motion subspace for axis 3 + */ + Joint ( + const Math::SpatialVector &axis_0, + const Math::SpatialVector &axis_1, + const Math::SpatialVector &axis_2, + const Math::SpatialVector &axis_3 + ) { + mJointType = JointType4DoF; + mDoFCount = 4; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = axis_0; + mJointAxes[1] = axis_1; + mJointAxes[2] = axis_2; + mJointAxes[3] = axis_3; + + validate_spatial_axis (mJointAxes[0]); + validate_spatial_axis (mJointAxes[1]); + validate_spatial_axis (mJointAxes[2]); + validate_spatial_axis (mJointAxes[3]); + } + + /** \brief Constructs a 5 DoF joint with the given motion subspaces. + * + * The motion subspaces are of the format: + * \f[ (r_x, r_y, r_z, t_x, t_y, t_z) \f] + * + * \note So far only pure rotations or pure translations are supported. + * + * \param axis_0 Motion subspace for axis 0 + * \param axis_1 Motion subspace for axis 1 + * \param axis_2 Motion subspace for axis 2 + * \param axis_3 Motion subspace for axis 3 + * \param axis_4 Motion subspace for axis 4 + */ + Joint ( + const Math::SpatialVector &axis_0, + const Math::SpatialVector &axis_1, + const Math::SpatialVector &axis_2, + const Math::SpatialVector &axis_3, + const Math::SpatialVector &axis_4 + ) { + mJointType = JointType5DoF; + mDoFCount = 5; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = axis_0; + mJointAxes[1] = axis_1; + mJointAxes[2] = axis_2; + mJointAxes[3] = axis_3; + mJointAxes[4] = axis_4; + + validate_spatial_axis (mJointAxes[0]); + validate_spatial_axis (mJointAxes[1]); + validate_spatial_axis (mJointAxes[2]); + validate_spatial_axis (mJointAxes[3]); + validate_spatial_axis (mJointAxes[4]); + } + + /** \brief Constructs a 6 DoF joint with the given motion subspaces. + * + * The motion subspaces are of the format: + * \f[ (r_x, r_y, r_z, t_x, t_y, t_z) \f] + * + * \note So far only pure rotations or pure translations are supported. + * + * \param axis_0 Motion subspace for axis 0 + * \param axis_1 Motion subspace for axis 1 + * \param axis_2 Motion subspace for axis 2 + * \param axis_3 Motion subspace for axis 3 + * \param axis_4 Motion subspace for axis 4 + * \param axis_5 Motion subspace for axis 5 + */ + Joint ( + const Math::SpatialVector &axis_0, + const Math::SpatialVector &axis_1, + const Math::SpatialVector &axis_2, + const Math::SpatialVector &axis_3, + const Math::SpatialVector &axis_4, + const Math::SpatialVector &axis_5 + ) { + mJointType = JointType6DoF; + mDoFCount = 6; + + mJointAxes = new Math::SpatialVector[mDoFCount]; + + mJointAxes[0] = axis_0; + mJointAxes[1] = axis_1; + mJointAxes[2] = axis_2; + mJointAxes[3] = axis_3; + mJointAxes[4] = axis_4; + mJointAxes[5] = axis_5; + + validate_spatial_axis (mJointAxes[0]); + validate_spatial_axis (mJointAxes[1]); + validate_spatial_axis (mJointAxes[2]); + validate_spatial_axis (mJointAxes[3]); + validate_spatial_axis (mJointAxes[4]); + validate_spatial_axis (mJointAxes[5]); + } + + /** \brief Checks whether we have pure rotational or translational axis. + * + * This function is mainly used to print out warnings when specifying an + * axis that might not be intended. + */ + bool validate_spatial_axis (Math::SpatialVector &axis) { + + bool axis_rotational = false; + bool axis_translational = false; + + Math::Vector3d rotation (axis[0], axis[1], axis[2]); + Math::Vector3d translation (axis[3], axis[4], axis[5]); + + if (fabs(rotation.norm()) > 1.0e-8) + axis_rotational = true; + + if (fabs(translation.norm()) > 1.0e-8) + axis_translational = true; + + if(axis_rotational && rotation.norm() - 1.0 > 1.0e-8) { + std::cerr << "Warning: joint rotation axis is not unit!" << std::endl; + } + + if(axis_translational && translation.norm() - 1.0 > 1.0e-8) { + std::cerr << "Warning: joint translation axis is not unit!" << std::endl; + } + + return axis_rotational != axis_translational; + } + + /// \brief The spatial axes of the joint + Math::SpatialVector* mJointAxes; + /// \brief Type of joint + JointType mJointType; + /// \brief Number of degrees of freedom of the joint. Note: CustomJoints + // have here a value of 0 and their actual numbers of degrees of freedom + // can be obtained using the CustomJoint structure. + unsigned int mDoFCount; + unsigned int q_index; + unsigned int custom_joint_index; +}; + +/** \brief Computes all variables for a joint model + * + * By appropriate modification of this function all types of joints can be + * modeled. See RBDA Section 4.4 for details. + * + * \param model the rigid body model + * \param joint_id the id of the joint we are interested in. This will be used to determine the type of joint and also the entries of \f[ q, \dot{q} \f]. + * \param q joint state variables + * \param qdot joint velocity variables + */ +RBDL_DLLAPI +void jcalc ( + Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot + ); + +RBDL_DLLAPI +Math::SpatialTransform jcalc_XJ ( + Model &model, + unsigned int joint_id, + const Math::VectorNd &q); + +RBDL_DLLAPI +void jcalc_X_lambda_S ( + Model &model, + unsigned int joint_id, + const Math::VectorNd &q + ); + +struct RBDL_DLLAPI CustomJoint { + CustomJoint() + { } + virtual ~CustomJoint() {}; + + virtual void jcalc (Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot + ) = 0; + virtual void jcalc_X_lambda_S (Model &model, + unsigned int joint_id, + const Math::VectorNd &q + ) = 0; + + unsigned int mDoFCount; + Math::SpatialTransform XJ; + Math::MatrixNd S; + Math::MatrixNd U; + Math::MatrixNd Dinv; + Math::VectorNd u; + Math::VectorNd d_u; +}; + +} + +/* RBDL_JOINT_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Kinematics.h b/3rdparty/rbdl/include/rbdl/Kinematics.h new file mode 100644 index 0000000..135fb5e --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Kinematics.h @@ -0,0 +1,415 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2015 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_KINEMATICS_H +#define RBDL_KINEMATICS_H + +#include "rbdl/rbdl_math.h" +#include +#include +#include "rbdl/Logging.h" + +namespace RigidBodyDynamics { + +/** \page kinematics_page Kinematics + * All functions related to kinematics are specified in the \ref + * kinematics_group "Kinematics Module". + * + * \note Please note that in the Rigid %Body Dynamics Library all angles + * are specified in radians. + * + * \defgroup kinematics_group Kinematics + * @{ + * + * \note Please note that in the Rigid %Body Dynamics Library all angles + * are specified in radians. + */ + +/** \brief Updates and computes velocities and accelerations of the bodies + * + * This function updates the kinematic variables such as body velocities + * and accelerations in the model to reflect the variables passed to this function. + * + * \param model the model + * \param Q the positional variables of the model + * \param QDot the generalized velocities of the joints + * \param QDDot the generalized accelerations of the joints + */ +RBDL_DLLAPI void UpdateKinematics (Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &QDDot + ); + +/** \brief Selectively updates model internal states of body positions, velocities and/or accelerations. + * + * This function updates the kinematic variables such as body velocities and + * accelerations in the model to reflect the variables passed to this function. + * + * In contrast to UpdateKinematics() this function allows to update the model + * state with values one is interested and thus reduce computations (e.g. only + * positions, only positions + accelerations, only velocities, etc.). + + * \param model the model + * \param Q the positional variables of the model + * \param QDot the generalized velocities of the joints + * \param QDDot the generalized accelerations of the joints + */ +RBDL_DLLAPI void UpdateKinematicsCustom (Model &model, + const Math::VectorNd *Q, + const Math::VectorNd *QDot, + const Math::VectorNd *QDDot + ); + +/** \brief Returns the base coordinates of a point given in body coordinates. + * + * \param model the rigid body model + * \param Q the curent genereralized positions + * \param body_id id of the body for which the point coordinates are expressed + * \param body_point_position coordinates of the point in body coordinates + * \param update_kinematics whether UpdateKinematics() should be called + * or not (default: true) + * + * \returns a 3-D vector with coordinates of the point in base coordinates + */ +RBDL_DLLAPI Math::Vector3d CalcBodyToBaseCoordinates ( + Model &model, + const Math::VectorNd &Q, + unsigned int body_id, + const Math::Vector3d &body_point_position, + bool update_kinematics = true); + +/** \brief Returns the body coordinates of a point given in base coordinates. + * + * \param model the rigid body model + * \param Q the curent genereralized positions + * \param body_id id of the body for which the point coordinates are expressed + * \param base_point_position coordinates of the point in base coordinates + * \param update_kinematics whether UpdateKinematics() should be called or not + * (default: true). + * + * \returns a 3-D vector with coordinates of the point in body coordinates + */ +RBDL_DLLAPI Math::Vector3d CalcBaseToBodyCoordinates ( + Model &model, + const Math::VectorNd &Q, + unsigned int body_id, + const Math::Vector3d &base_point_position, + bool update_kinematics = true); + +/** \brief Returns the orientation of a given body as 3x3 matrix + * + * \param model the rigid body model + * \param Q the curent genereralized positions + * \param body_id id of the body for which the point coordinates are expressed + * \param update_kinematics whether UpdateKinematics() should be called or not + * (default: true). + * + * \returns An orthonormal 3x3 matrix that rotates vectors from base coordinates + * to body coordinates. + */ +RBDL_DLLAPI Math::Matrix3d CalcBodyWorldOrientation ( + Model &model, + const Math::VectorNd &Q, + const unsigned int body_id, + bool update_kinematics = true); + +/** \brief Computes the point jacobian for a point on a body + * + * If a position of a point is computed by a function \f$g(q(t))\f$ for which its + * time derivative is \f$\frac{d}{dt} g(q(t)) = G(q)\dot{q}\f$ then this + * function computes the jacobian matrix \f$G(q)\f$. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param body_id the id of the body + * \param point_position the position of the point in body-local data + * \param G a matrix of dimensions 3 x \#qdot_size where the result will be stored in + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * The result will be returned via the G argument. + * + * \note This function only evaluates the entries of G that are non-zero. One + * Before calling this function one has to ensure that all other values + * have been set to zero, e.g. by calling G.setZero(). + * + */ +RBDL_DLLAPI void CalcPointJacobian (Model &model, + const Math::VectorNd &Q, + unsigned int body_id, + const Math::Vector3d &point_position, + Math::MatrixNd &G, + bool update_kinematics = true + ); + +/** \brief Computes a 6-D Jacobian for a point on a body + * + * Computes the 6-D Jacobian \f$G(q)\f$ that when multiplied with + * \f$\dot{q}\f$ gives a 6-D vector that has the angular velocity as the + * first three entries and the linear velocity as the last three entries. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param body_id the id of the body + * \param point_position the position of the point in body-local data + * \param G a matrix of dimensions 6 x \#qdot_size where the result will be stored in + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * The result will be returned via the G argument. + * + * \note This function only evaluates the entries of G that are non-zero. One + * Before calling this function one has to ensure that all other values + * have been set to zero, e.g. by calling G.setZero(). + * + */ +RBDL_DLLAPI void CalcPointJacobian6D (Model &model, + const Math::VectorNd &Q, + unsigned int body_id, + const Math::Vector3d &point_position, + Math::MatrixNd &G, + bool update_kinematics = true + ); + +/** \brief Computes the spatial jacobian for a body + * + * The spatial velocity of a body at the origin of the base coordinate + * system can be expressed as \f${}^0 \hat{v}_i = G(q) * \dot{q}\f$. The + * matrix \f$G(q)\f$ is called the spatial body jacobian of the body and + * can be computed using this function. + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param body_id the id of the body + * \param G a matrix of size 6 x \#qdot_size where the result will be stored in + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * The result will be returned via the G argument and represents the + * body Jacobian expressed at the origin of the body. + * + * \note This function only evaluates the entries of G that are non-zero. One + * Before calling this function one has to ensure that all other values + * have been set to zero, e.g. by calling G.setZero(). + */ +RBDL_DLLAPI void CalcBodySpatialJacobian ( + Model &model, + const Math::VectorNd &Q, + unsigned int body_id, + Math::MatrixNd &G, + bool update_kinematics = true + ); + +/** \brief Computes the velocity of a point on a body + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param body_id the id of the body + * \param point_position the position of the point in body-local data + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * \returns The cartesian velocity of the point in global frame (output) + */ +RBDL_DLLAPI Math::Vector3d CalcPointVelocity ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + unsigned int body_id, + const Math::Vector3d &point_position, + bool update_kinematics = true + ); + +/** \brief Computes angular and linear velocity of a point that is fixed on a body + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param body_id the id of the body + * \param point_position the position of the point in body-local data + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * \returns The a 6-D vector for which the first three elements are the + * angular velocity and the last three elements the linear velocity in the + * global reference system. + */ +RBDL_DLLAPI + Math::SpatialVector CalcPointVelocity6D ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + unsigned int body_id, + const Math::Vector3d &point_position, + bool update_kinematics = true + ); + +/** \brief Computes the linear acceleration of a point on a body + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param QDDot velocity vector of the internal joints + * \param body_id the id of the body + * \param point_position the position of the point in body-local data + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * \returns The cartesian acceleration of the point in global frame (output) + * + * The kinematic state of the model has to be updated before valid + * values can be obtained. This can either be done by calling + * UpdateKinematics() or setting the last parameter update_kinematics to + * true (default). + * + * \warning If this function is called after ForwardDynamics() without + * an update of the kinematic state one has to add the gravity + * acceleration has to be added to the result. + */ +RBDL_DLLAPI + Math::Vector3d CalcPointAcceleration ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &QDDot, + unsigned int body_id, + const Math::Vector3d &point_position, + bool update_kinematics = true + ); + +/** \brief Computes linear and angular acceleration of a point on a body + * + * \param model rigid body model + * \param Q state vector of the internal joints + * \param QDot velocity vector of the internal joints + * \param QDDot velocity vector of the internal joints + * \param body_id the id of the body + * \param point_position the position of the point in body-local data + * \param update_kinematics whether UpdateKinematics() should be called or not (default: true) + * + * \returns A 6-D vector where the first three elements are the angular + * acceleration and the last three elements the linear accelerations of + * the point. + * + * The kinematic state of the model has to be updated before valid + * values can be obtained. This can either be done by calling + * UpdateKinematics() or setting the last parameter update_kinematics to + * true (default). + * + * \warning If this function is called after ForwardDynamics() without + * an update of the kinematic state one has to add the gravity + * acceleration has to be added to the result. + */ +RBDL_DLLAPI + Math::SpatialVector CalcPointAcceleration6D ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &QDDot, + unsigned int body_id, + const Math::Vector3d &point_position, + bool update_kinematics = true + ); + +/** \brief Computes the inverse kinematics iteratively using a damped Levenberg-Marquardt method (also known as Damped Least Squares method) + * + * \param model rigid body model + * \param Qinit initial guess for the state + * \param body_id a vector of all bodies for which we we have kinematic target positions + * \param body_point a vector of points in body local coordinates that are + * to be matched to target positions + * \param target_pos a vector of target positions + * \param Qres output of the computed inverse kinematics + * \param step_tol tolerance used for convergence detection + * \param lambda damping factor for the least squares function + * \param max_iter maximum number of steps that should be performed + * \returns true on success, false otherwise + * + * This function repeatedly computes + * \f[ Qres = Qres + \Delta \theta\f] + * \f[ \Delta \theta = G^T (G^T G + \lambda^2 I)^{-1} e \f] + * where \f$G = G(q) = \frac{d}{dt} g(q(t))\f$ and \f$e\f$ is the + * correction of the body points so that they coincide with the target + * positions. The function returns true when \f$||\Delta \theta||_2 \le\f$ + * step_tol or if the error between body points and target gets smaller + * than step_tol. Otherwise it returns false. + * + * The parameter \f$\lambda\f$ is the damping factor that has to + * be chosen carefully. In case of unreachable positions higher values (e.g + * 0.9) can be helpful. Otherwise values of 0.0001, 0.001, 0.01, 0.1 might + * yield good results. See the literature for best practices. + * + * \warning The actual accuracy might be rather low (~1.0e-2)! Use this function with a + * grain of suspicion. + */ +RBDL_DLLAPI + bool InverseKinematics ( + Model &model, + const Math::VectorNd &Qinit, + const std::vector& body_id, + const std::vector& body_point, + const std::vector& target_pos, + Math::VectorNd &Qres, + double step_tol = 1.0e-12, + double lambda = 0.01, + unsigned int max_iter = 50 + ); + +RBDL_DLLAPI Math::Vector3d CalcCalcAngularVelocityfromMatrix ( + const Math::Matrix3d &RotMat); + +struct RBDL_DLLAPI InverseKinematicsConstraintSet { + enum ConstraintType { + ConstraintTypePosition = 0, + ConstraintTypeOrientation, + ConstraintTypeFull + }; + + InverseKinematicsConstraintSet(); + + Math::MatrixNd J; /// the Jacobian of all constraints + Math::MatrixNd G; /// temporary storage of a single body Jacobian + Math::VectorNd e; /// Vector with all the constraint residuals. + + unsigned int num_constraints; //size of all constraints + double lambda; /// Damping factor, the default value of 1.0e-6 is reasonable for most problems + unsigned int num_steps; // The number of iterations performed + unsigned int max_steps; // Maximum number of steps (default 300), abort if more steps are performed. + double step_tol; // Step tolerance (default = 1.0e-12). If the computed step length is smaller than this value the algorithm terminates successfully (i.e. returns true). If error_norm is still larger than constraint_tol then this usually means that the target is unreachable. + double constraint_tol; // Constraint tolerance (default = 1.0e-12). If error_norm is smaller than this value the algorithm terminates successfully, i.e. all constraints are satisfied. + double error_norm; // Norm of the constraint residual vector. + + // everything to define a IKin constraint + std::vector constraint_type; + std::vector body_ids; + std::vector body_points; + std::vector target_positions; + std::vector target_orientations; + std::vector constraint_row_index; + + // Adds a point constraint that tries to get a body point close to a + // point described in base coordinates. + unsigned int AddPointConstraint (unsigned int body_id, const Math::Vector3d &body_point, const Math::Vector3d &target_pos); + // Adds an orientation constraint that tries to align a body to the + // orientation specified as a rotation matrix expressed in base + // coordinates. + unsigned int AddOrientationConstraint (unsigned int body_id, const Math::Matrix3d &target_orientation); + // Adds a constraint on both location and orientation of a body. + unsigned int AddFullConstraint (unsigned int body_id, const Math::Vector3d &body_point, const Math::Vector3d &target_pos, const Math::Matrix3d &target_orientation); + // Clears all entries of the constraint setting + unsigned int ClearConstraints(); +}; + +RBDL_DLLAPI bool InverseKinematics ( + Model &model, + const Math::VectorNd &Qinit, + InverseKinematicsConstraintSet &CS, + Math::VectorNd &Qres + ); + +/** @} */ + +} + +/* RBDL_KINEMATICS_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Logging.h b/3rdparty/rbdl/include/rbdl/Logging.h new file mode 100644 index 0000000..8f63b3c --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Logging.h @@ -0,0 +1,74 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_LOGGING_H +#define RBDL_LOGGING_H + +#include +#include + +class LoggingGuard; + +/** \def RBDL_ENABLE_LOGGING + * + * Enables/Disables logging + * + * \warning Logging has a huge impact on performance. + */ +#ifndef RBDL_ENABLE_LOGGING +#define LOG if (false) LogOutput +#define SUPPRESS_LOGGING ; +#else +#define LOG LogOutput +#define SUPPRESS_LOGGING LoggingGuard _nolog +#endif + +extern RBDL_DLLAPI std::ostringstream LogOutput; +RBDL_DLLAPI void ClearLogOutput (); + +/** \brief Helper object to ignore any logs that happen during its lifetime + * + * If an instance of this class exists all logging gets suppressed. This + * allows to disable logging for a certain scope or a single function call, + * e.g. + * + * \code + * { + * // logging will be active + * do_some_stuff(); + * + * // now create a new scope in which a LoggingGuard instance exists + * { + * LoggingGuard ignore_logging; + * + * // as a _Nologging instance exists, all logging will be discarded + * do_some_crazy_stuff(); + * } + * + * // logging will be active again + * do_some_more_stuff(); + * } + * \endcode + * + */ +class RBDL_DLLAPI LoggingGuard { + public: + LoggingGuard() { + log_backup.str(""); + log_backup << LogOutput.str(); + } + ~LoggingGuard() { + LogOutput.str(""); + LogOutput << log_backup.str(); + } + + private: + std::ostringstream log_backup; +}; + +/* RBDL_LOGGING_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Model.h b/3rdparty/rbdl/include/rbdl/Model.h new file mode 100644 index 0000000..7159531 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Model.h @@ -0,0 +1,519 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_MODEL_H +#define RBDL_MODEL_H + +#include "rbdl/rbdl_math.h" +#include +#include +#include +#include +#include +#include + +#include "rbdl/Logging.h" +#include "rbdl/Joint.h" +#include "rbdl/Body.h" + +// std::vectors containing any objects that have Eigen matrices or vectors +// as members need to have a special allocater. This can be achieved with +// the following macro. + +#ifdef EIGEN_CORE_H +EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Joint); +EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Body); +EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::FixedBody); +#endif + +/** \brief Namespace for all structures of the RigidBodyDynamics library +*/ +namespace RigidBodyDynamics { + +/** \page modeling_page Model + * + * \section model_structure Model Structure + * + * RBDL stores the model internally in the \link RigidBodyDynamics::Model + * Model Structure\endlink. For each \link RigidBodyDynamics::Body Body + * \endlink it contains spatial velocities, accelerations and other + * variables that describe the state of the rigid body system. Furthermore + * it contains variables that are used as temporary variables in the + * algorithms. + * + * There are multiple ways of creating \link RigidBodyDynamics::Model Models\endlink for RBDL: + * + * \li Loading models from Lua files using the \ref luamodel_introduction + * "LuaModel" addon + * \li Loading models from URDF (the Unified Robot Description Format) xml + * files or strings using the URDFReader addon + * \li using the C++ interface. + * + * The first approach requires the addon \ref luamodel_introduction to be + * activated which is done by enabling BUILD_ADDON_LUAMODEL in CMake and is + * recommended when one is not interested in the details of RBDL and simply + * wants to create a model. + * + * \section modeling_cpp Modeling using C++ + * + * The construction of \link RigidBodyDynamics::Model Model Structures + * \endlink makes use of carefully designed constructors of the classes + * \link RigidBodyDynamics::Body Body \endlink and \link + * RigidBodyDynamics::Joint Joint \endlink to ease the process of + * creating articulated models. + * + * \link RigidBodyDynamics::Body Bodies \endlink are created by calling one + * of its constructors. Usually they are created by specifying the mass, + * center of mass and the inertia at the center of mass. + * \link RigidBodyDynamics::Joint Joints \endlink are similarly created and is + * described in detail in \ref joint_description. + * + * Adding bodies to the model is done by specifying the + * parent body by its id, the transformation from the parent origin to the + * joint origin, the joint specification as an object, and the body itself. + * These parameters are then fed to the function + * RigidBodyDynamics::Model::AddBody() or + * RigidBodyDynamics::Model::AppendBody(). + * + * To create a model with a floating base (a.k.a a model with a free-flyer + * joint) it is recommended to use \link + * RigidBodyDynamics::Model::SetFloatingBaseBody + * Model::SetFloatingBaseBody(...)\endlink. + * + * Once this is done, the model structure can be used with the functions of \ref + * kinematics_group, \ref dynamics_group, \ref contacts_page, to perform + * computations. + * + * A simple example can be found \ref SimpleExample "here". + * + * \section modeling_lua Using LuaModels + * + * For this see the documentation of \ref luamodel_introduction,\link + * RigidBodyDynamics::Addons::LuaModelReadFromFile \endlink, and \link + * RigidBodyDynamics::Addons::LuaModelReadFromFileWithConstraints \endlink. + + * \section modeling_urdf Using URDF + * + * For this see the documentation see \link + * RigidBodyDynamics::Addons::URDFReadFromFile \endlink. + */ + +/** \brief Contains all information about the rigid body model + * + * This class contains all information required to perform the forward + * dynamics calculation. The variables in this class are also used for + * storage of temporary values. It is designed for use of the Articulated + * Rigid Body Algorithm (which is implemented in ForwardDynamics()) and + * follows the numbering as described in Featherstones book. + * + * Please note that body 0 is the root body and the moving bodies start at + * index 1. This numbering scheme is very beneficial in terms of + * readability of the code as the resulting code is very similar to the + * pseudo-code in the RBDA book. The generalized variables q, qdot, qddot + * and tau however start at 0 such that the first entry (e.g. q[0]) always + * specifies the value for the first moving body. + * + * \note To query the number of degrees of freedom use Model::dof_count. + */ +struct RBDL_DLLAPI Model { + Model(); + + // Structural information + + /// \brief The id of the parents body + std::vector lambda; + /** \brief The index of the parent degree of freedom that is directly + influencing the current one*/ + std::vector lambda_q; + /// \brief Contains the ids of all the children of a given body + std::vector >mu; + + /** \brief number of degrees of freedoms of the model + * + * This value contains the number of entries in the generalized state (q) + * velocity (qdot), acceleration (qddot), and force (tau) vector. + */ + unsigned int dof_count; + + /** \brief The size of the \f$\mathbf{q}\f$-vector. + * For models without spherical joints the value is the same as + * Model::dof_count, otherwise additional values for the w-component of the + * Quaternion is stored at the end of \f$\mathbf{q}\f$. + * + * \sa \ref joint_description for more details. + */ + unsigned int q_size; + /** \brief The size of the + * + * (\f$\mathbf{\dot{q}}, \mathbf{\ddot{q}}\f$, + * and \f$\mathbf{\tau}\f$-vector. + * + * \sa \ref joint_description for more details. + */ + unsigned int qdot_size; + + /// \brief Id of the previously added body, required for Model::AppendBody() + unsigned int previously_added_body_id; + + /// \brief the cartesian vector of the gravity + Math::Vector3d gravity; + + // State information + /// \brief The spatial velocity of the bodies + std::vector v; + /// \brief The spatial acceleration of the bodies + std::vector a; + + //////////////////////////////////// + // Joints + + /// \brief All joints + + std::vector mJoints; + /// \brief The joint axis for joint i + std::vector S; + + // Joint state variables + std::vector X_J; + std::vector v_J; + std::vector c_J; + + std::vector mJointUpdateOrder; + + /// \brief Transformations from the parent body to the frame of the joint. + // It is expressed in the coordinate frame of the parent. + std::vector X_T; + /// \brief The number of fixed joints that have been declared before + /// each joint. + std::vector mFixedJointCount; + + //////////////////////////////////// + // Special variables for joints with 3 degrees of freedom + /// \brief Motion subspace for joints with 3 degrees of freedom + std::vector multdof3_S; + std::vector multdof3_U; + std::vector multdof3_Dinv; + std::vector multdof3_u; + std::vector multdof3_w_index; + + std::vector mCustomJoints; + + //////////////////////////////////// + // Dynamics variables + + /// \brief The velocity dependent spatial acceleration + std::vector c; + /// \brief The spatial inertia of the bodies + std::vector IA; + /// \brief The spatial bias force + std::vector pA; + /// \brief Temporary variable U_i (RBDA p. 130) + std::vector U; + /// \brief Temporary variable D_i (RBDA p. 130) + Math::VectorNd d; + /// \brief Temporary variable u (RBDA p. 130) + Math::VectorNd u; + /// \brief Internal forces on the body (used only InverseDynamics()) + std::vector f; + /// \brief The spatial inertia of body i (used only in + /// CompositeRigidBodyAlgorithm()) + std::vector I; + std::vector Ic; + std::vector hc; + + //////////////////////////////////// + // Bodies + + /** \brief Transformation from the parent body to the current body + * \f[ + * X_{\lambda(i)} = {}^{i} X_{\lambda(i)} + * \f] + */ + std::vector X_lambda; + /// \brief Transformation from the base to bodies reference frame + std::vector X_base; + + /// \brief All bodies that are attached to a body via a fixed joint. + std::vector mFixedBodies; + /** \brief Value that is used to discriminate between fixed and movable + * bodies. + * + * Bodies with id 1 .. (fixed_body_discriminator - 1) are moving bodies + * while bodies with id fixed_body_discriminator .. max (unsigned int) + * are fixed to a moving body. The value of max(unsigned int) is + * determined via std::numeric_limits::max() and the + * default value of fixed_body_discriminator is max (unsigned int) / 2. + * + * On normal systems max (unsigned int) is 4294967294 which means there + * could be a total of 2147483646 movable and / or fixed bodies. + */ + unsigned int fixed_body_discriminator; + + /** \brief All bodies 0 ... N_B, including the base + * + * mBodies[0] - base body
+ * mBodies[1] - 1st moveable body
+ * ...
+ * mBodies[N_B] - N_Bth moveable body
+ */ + std::vector mBodies; + + /// \brief Human readable names for the bodies + std::map mBodyNameMap; + + /** \brief Connects a given body to the model + * + * When adding a body there are basically informations required: + * - what kind of body will be added? + * - where is the new body to be added? + * - by what kind of joint should the body be added? + * + * The first information "what kind of body will be added" is contained + * in the Body class that is given as a parameter. + * + * The question "where is the new body to be added?" is split up in two + * parts: first the parent (or successor) body to which it is added and + * second the transformation to the origin of the joint that connects the + * two bodies. With these two informations one specifies the relative + * positions of the bodies when the joint is in neutral position.gk + * + * The last question "by what kind of joint should the body be added?" is + * again simply contained in the Joint class. + * + * \param parent_id id of the parent body + * \param joint_frame the transformation from the parent frame to the origin + * of the joint frame (represents X_T in RBDA) + * \param joint specification for the joint that describes the + * connection + * \param body specification of the body itself + * \param body_name human readable name for the body (can be used to + * retrieve its id with GetBodyId()) + * + * \returns id of the added body + */ + unsigned int AddBody ( + const unsigned int parent_id, + const Math::SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name = "" + ); + + unsigned int AddBodySphericalJoint ( + const unsigned int parent_id, + const Math::SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name = "" + ); + + /** \brief Adds a Body to the model such that the previously added Body + * is the Parent. + * + * This function is basically the same as Model::AddBody() however the + * most recently added body (or body 0) is taken as parent. + */ + unsigned int AppendBody ( + const Math::SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name = "" + ); + + unsigned int AddBodyCustomJoint ( + const unsigned int parent_id, + const Math::SpatialTransform &joint_frame, + CustomJoint *custom_joint, + const Body &body, + std::string body_name = "" + ); + + /** \brief Specifies the dynamical parameters of the first body and + * \brief assigns it a 6 DoF joint. + * + * The 6 DoF joint is simulated by adding 5 massless bodies at the base + * which are connected with joints. The body that is specified as a + * parameter of this function is then added by a 6th joint to the model. + * + * The floating base has the following order of degrees of freedom: + * + * \li translation X + * \li translation Y + * \li translation Z + * \li rotation Z + * \li rotation Y + * \li rotation X + * + * To specify a different ordering, it is recommended to create a 6 DoF + * joint. See \link RigidBodyDynamics::Joint Joint\endlink for more + * information. + * + * \param body Properties of the floating base body. + * + * \returns id of the body with 6 DoF + */ + unsigned int SetFloatingBaseBody (const Body &body); + + /** \brief Returns the id of a body that was passed to AddBody() + * + * Bodies can be given a human readable name. This function allows to + * resolve its name to the numeric id. + * + * \note Instead of querying this function repeatedly, it might be + * advisable to query it once and reuse the returned id. + * + * \returns the id of the body or \c std::numeric_limits\::max() if the id was not found. + */ + unsigned int GetBodyId (const char *body_name) const { + if (mBodyNameMap.count(body_name) == 0) { + return std::numeric_limits::max(); + } + + return mBodyNameMap.find(body_name)->second; + } + + /** \brief Returns the name of a body for a given body id */ + std::string GetBodyName (unsigned int body_id) const { + std::map::const_iterator iter + = mBodyNameMap.begin(); + + while (iter != mBodyNameMap.end()) { + if (iter->second == body_id) + return iter->first; + + iter++; + } + + return ""; + } + + /** \brief Checks whether the body is rigidly attached to another body. + */ + bool IsFixedBodyId (unsigned int body_id) { + if (body_id >= fixed_body_discriminator + && body_id < std::numeric_limits::max() + && body_id - fixed_body_discriminator < mFixedBodies.size()) { + return true; + } + return false; + } + + bool IsBodyId (unsigned int id) { + if (id > 0 && id < mBodies.size()) + return true; + if (id >= fixed_body_discriminator + && id < std::numeric_limits::max()) { + if (id - fixed_body_discriminator < mFixedBodies.size()) + return true; + } + return false; + } + + /** Determines id the actual parent body. + * + * When adding bodies using joints with multiple degrees of + * freedom, additional virtual bodies are added for each degree of + * freedom. This function returns the id of the actual + * non-virtual parent body. + */ + unsigned int GetParentBodyId (unsigned int id) { + if (id >= fixed_body_discriminator) { + return mFixedBodies[id - fixed_body_discriminator].mMovableParent; + } + + unsigned int parent_id = lambda[id]; + + while (mBodies[parent_id].mIsVirtual) { + parent_id = lambda[parent_id]; + } + + return parent_id; + } + + /** Returns the joint frame transformtion, i.e. the second argument to + Model::AddBody(). + */ + Math::SpatialTransform GetJointFrame (unsigned int id) { + if (id >= fixed_body_discriminator) { + return mFixedBodies[id - fixed_body_discriminator].mParentTransform; + } + + unsigned int child_id = id; + unsigned int parent_id = lambda[id]; + if (mBodies[parent_id].mIsVirtual) { + while (mBodies[parent_id].mIsVirtual) { + child_id = parent_id; + parent_id = lambda[child_id]; + } + return X_T[child_id]; + } else + return X_T[id]; + } + + /** Sets the joint frame transformtion, i.e. the second argument to + Model::AddBody(). + */ + void SetJointFrame (unsigned int id, + const Math::SpatialTransform &transform) { + if (id >= fixed_body_discriminator) { + std::cerr << "Error: setting of parent transform " + << "not supported for fixed bodies!" << std::endl; + abort(); + } + + unsigned int child_id = id; + unsigned int parent_id = lambda[id]; + if (mBodies[parent_id].mIsVirtual) { + while (mBodies[parent_id].mIsVirtual) { + child_id = parent_id; + parent_id = lambda[child_id]; + } + X_T[child_id] = transform; + } else if (id > 0) { + X_T[id] = transform; + } + } + + /** Gets the quaternion for body i (only valid if body i is connected by + * a JointTypeSpherical joint) + * + * See \ref joint_singularities for details. + */ + Math::Quaternion GetQuaternion (unsigned int i, + const Math::VectorNd &Q) const { + assert (mJoints[i].mJointType == JointTypeSpherical); + unsigned int q_index = mJoints[i].q_index; + return Math::Quaternion ( Q[q_index], + Q[q_index + 1], + Q[q_index + 2], + Q[multdof3_w_index[i]]); + } + + /** Sets the quaternion for body i (only valid if body i is connected by + * a JointTypeSpherical joint) + * + * See \ref joint_singularities for details. + */ + void SetQuaternion (unsigned int i, + const Math::Quaternion &quat, + Math::VectorNd &Q) const { + assert (mJoints[i].mJointType == JointTypeSpherical); + unsigned int q_index = mJoints[i].q_index; + + Q[q_index] = quat[0]; + Q[q_index + 1] = quat[1]; + Q[q_index + 2] = quat[2]; + Q[multdof3_w_index[i]] = quat[3]; + } +}; + +/** @} */ +} + +/* _MODEL_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/Quaternion.h b/3rdparty/rbdl/include/rbdl/Quaternion.h new file mode 100644 index 0000000..fc71aa7 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/Quaternion.h @@ -0,0 +1,211 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_QUATERNION_H +#define RBDL_QUATERNION_H + +#include + +namespace RigidBodyDynamics { + +namespace Math { + +/** \brief Quaternion that are used for \ref joint_singularities "singularity free" joints. + * + * order: x,y,z,w + */ +class Quaternion : public Vector4d { + public: + Quaternion () : + Vector4d (0., 0., 0., 1.) + {} + Quaternion (const Vector4d &vec4) : + Vector4d (vec4) + {} + Quaternion (double x, double y, double z, double w): + Vector4d (x, y, z, w) + {} + Quaternion operator* (const double &s) const { + return Quaternion ( + (*this)[0] * s, + (*this)[1] * s, + (*this)[2] * s, + (*this)[3] * s + ); + } + /** This function is equivalent to multiplicate their corresponding rotation matrices */ + Quaternion operator* (const Quaternion &q) const { + return Quaternion ( + (*this)[3] * q[0] + (*this)[0] * q[3] + (*this)[1] * q[2] - (*this)[2] * q[1], + (*this)[3] * q[1] + (*this)[1] * q[3] + (*this)[2] * q[0] - (*this)[0] * q[2], + (*this)[3] * q[2] + (*this)[2] * q[3] + (*this)[0] * q[1] - (*this)[1] * q[0], + (*this)[3] * q[3] - (*this)[0] * q[0] - (*this)[1] * q[1] - (*this)[2] * q[2] + ); + } + Quaternion& operator*=(const Quaternion &q) { + set ( + (*this)[3] * q[0] + (*this)[0] * q[3] + (*this)[1] * q[2] - (*this)[2] * q[1], + (*this)[3] * q[1] + (*this)[1] * q[3] + (*this)[2] * q[0] - (*this)[0] * q[2], + (*this)[3] * q[2] + (*this)[2] * q[3] + (*this)[0] * q[1] - (*this)[1] * q[0], + (*this)[3] * q[3] - (*this)[0] * q[0] - (*this)[1] * q[1] - (*this)[2] * q[2] + ); + return *this; + } + + static Quaternion fromGLRotate (double angle, double x, double y, double z) { + double st = std::sin (angle * M_PI / 360.); + return Quaternion ( + st * x, + st * y, + st * z, + std::cos (angle * M_PI / 360.) + ); + } + + Quaternion slerp (double alpha, const Quaternion &quat) const { + // check whether one of the two has 0 length + double s = std::sqrt (squaredNorm() * quat.squaredNorm()); + + // division by 0.f is unhealthy! + assert (s != 0.); + + double angle = acos (dot(quat) / s); + if (angle == 0. || std::isnan(angle)) { + return *this; + } + assert(!std::isnan(angle)); + + double d = 1. / std::sin (angle); + double p0 = std::sin ((1. - alpha) * angle); + double p1 = std::sin (alpha * angle); + + if (dot (quat) < 0.) { + return Quaternion( ((*this) * p0 - quat * p1) * d); + } + return Quaternion( ((*this) * p0 + quat * p1) * d); + } + + static Quaternion fromAxisAngle (const Vector3d &axis, double angle_rad) { + double d = axis.norm(); + double s2 = std::sin (angle_rad * 0.5) / d; + return Quaternion ( + axis[0] * s2, + axis[1] * s2, + axis[2] * s2, + std::cos(angle_rad * 0.5) + ); + } + + static Quaternion fromMatrix (const Matrix3d &mat) { + double w = std::sqrt (1. + mat(0,0) + mat(1,1) + mat(2,2)) * 0.5; + return Quaternion ( + (mat(1,2) - mat(2,1)) / (w * 4.), + (mat(2,0) - mat(0,2)) / (w * 4.), + (mat(0,1) - mat(1,0)) / (w * 4.), + w); + } + + static Quaternion fromZYXAngles (const Vector3d &zyx_angles) { + return Quaternion::fromAxisAngle (Vector3d (0., 0., 1.), zyx_angles[0]) + * Quaternion::fromAxisAngle (Vector3d (0., 1., 0.), zyx_angles[1]) + * Quaternion::fromAxisAngle (Vector3d (1., 0., 0.), zyx_angles[2]); + } + + static Quaternion fromYXZAngles (const Vector3d &yxz_angles) { + return Quaternion::fromAxisAngle (Vector3d (0., 1., 0.), yxz_angles[0]) + * Quaternion::fromAxisAngle (Vector3d (1., 0., 0.), yxz_angles[1]) + * Quaternion::fromAxisAngle (Vector3d (0., 0., 1.), yxz_angles[2]); + } + + static Quaternion fromXYZAngles (const Vector3d &xyz_angles) { + return Quaternion::fromAxisAngle (Vector3d (0., 0., 01.), xyz_angles[2]) + * Quaternion::fromAxisAngle (Vector3d (0., 1., 0.), xyz_angles[1]) + * Quaternion::fromAxisAngle (Vector3d (1., 0., 0.), xyz_angles[0]); + } + + Matrix3d toMatrix() const { + double x = (*this)[0]; + double y = (*this)[1]; + double z = (*this)[2]; + double w = (*this)[3]; + return Matrix3d ( + 1 - 2*y*y - 2*z*z, + 2*x*y + 2*w*z, + 2*x*z - 2*w*y, + + 2*x*y - 2*w*z, + 1 - 2*x*x - 2*z*z, + 2*y*z + 2*w*x, + + 2*x*z + 2*w*y, + 2*y*z - 2*w*x, + 1 - 2*x*x - 2*y*y + + /* + 1 - 2*y*y - 2*z*z, + 2*x*y - 2*w*z, + 2*x*z + 2*w*y, + + 2*x*y + 2*w*z, + 1 - 2*x*x - 2*z*z, + 2*y*z - 2*w*x, + + 2*x*z - 2*w*y, + 2*y*z + 2*w*x, + 1 - 2*x*x - 2*y*y + */ + ); + } + + Quaternion conjugate() const { + return Quaternion ( + -(*this)[0], + -(*this)[1], + -(*this)[2], + (*this)[3]); + } + + Quaternion timeStep (const Vector3d &omega, double dt) { + double omega_norm = omega.norm(); + return Quaternion::fromAxisAngle (omega / omega_norm, dt * omega_norm) * (*this); + } + + Vector3d rotate (const Vector3d &vec) const { + Vector3d vn (vec); + Quaternion vec_quat (vn[0], vn[1], vn[2], 0.f), res_quat; + + res_quat = vec_quat * (*this); + res_quat = conjugate() * res_quat; + + return Vector3d (res_quat[0], res_quat[1], res_quat[2]); + } + + /** \brief Converts a 3d angular velocity vector into a 4d derivative of the + * components of the quaternion. + * + * \param omega the angular velocity. + * + * \return a 4d vector containing the derivatives of the 4 components of the + * quaternion corresponding to omega. + * + */ + Vector4d omegaToQDot(const Vector3d& omega) const { + Math::Matrix43 m; + m(0, 0) = (*this)[3]; m(0, 1) = -(*this)[2]; m(0, 2) = (*this)[1]; + m(1, 0) = (*this)[2]; m(1, 1) = (*this)[3]; m(1, 2) = -(*this)[0]; + m(2, 0) = -(*this)[1]; m(2, 1) = (*this)[0]; m(2, 2) = (*this)[3]; + m(3, 0) = -(*this)[0]; m(3, 1) = -(*this)[1]; m(3, 2) = -(*this)[2]; + return Quaternion(0.5 * m * omega); + } +}; + +} + +} + +/* RBDL_QUATERNION_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/.hg_archival.txt b/3rdparty/rbdl/include/rbdl/SimpleMath/.hg_archival.txt new file mode 100644 index 0000000..d304728 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/.hg_archival.txt @@ -0,0 +1,5 @@ +repo: 1e43843734d109ad9faf98c54f37d419ae680546 +node: 5822281865de4bcd58e4a6582ea7940391314492 +branch: default +latesttag: null +latesttagdistance: 5 diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/.hgignore b/3rdparty/rbdl/include/rbdl/SimpleMath/.hgignore new file mode 100644 index 0000000..8dfdff4 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/.hgignore @@ -0,0 +1,15 @@ +syntax: glob + +doc/html/* +doc/notes/*.aux +doc/notes/*.pdf +Debug/* +Release/* +.*.swp +CMakeFiles/* +cmake_install.cmake +Makefile +tags +*.log +tests/runtests +tests/CMakeCache.txt diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/README b/3rdparty/rbdl/include/rbdl/SimpleMath/README new file mode 100644 index 0000000..e49e343 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/README @@ -0,0 +1,10 @@ +This is a highly inefficient math library. It was conceived by Martin +Felis while he was waiting for code to +compile which used a highly efficient math library. + +It is intended to be used as a fast compiling substitute for the +blazingly fast Eigen3 http://eigen.tuxfamily.org/index.php?title=Main_Page +library and tries to mimic its API to a certain extent. + +Feel free to use it wherever you like. However, no guarantees are given +that this code does what it says it would. diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMath.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMath.h new file mode 100644 index 0000000..568dc66 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMath.h @@ -0,0 +1,28 @@ +#ifndef _SIMPLEMATH_H +#define _SIMPLEMATH_H + +#include "SimpleMathFixed.h" +#include "SimpleMathDynamic.h" +#include "SimpleMathMixed.h" +#include "SimpleMathQR.h" +#include "SimpleMathCommaInitializer.h" + +namespace SimpleMath { + +typedef SimpleMath::Fixed::Matrix Vector3i; + +typedef SimpleMath::Fixed::Matrix Vector3d; +typedef SimpleMath::Fixed::Matrix Matrix33d; + +typedef SimpleMath::Fixed::Matrix Vector4d; + +typedef SimpleMath::Fixed::Matrix Vector3f; +typedef SimpleMath::Fixed::Matrix Vector4f; +typedef SimpleMath::Fixed::Matrix Matrix33f; +typedef SimpleMath::Fixed::Matrix Matrix44f; + +typedef SimpleMath::Dynamic::Matrix VectorNd; + +} + +#endif /* _SIMPLEMATH_H */ diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathBlock.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathBlock.h new file mode 100644 index 0000000..cb2e472 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathBlock.h @@ -0,0 +1,194 @@ +/** + * This is a highly inefficient math library. It was conceived by Martin + * Felis while he was compiling code + * that uses a highly efficient math library. + * + * It is intended to be used as a fast compiling substitute for the + * blazingly fast Eigen3 library and tries to mimic its API to a certain + * extend. + * + * Feel free to use it wherever you like. However, no guarantees are given + * that this code does what it says it would. + */ + +#ifndef SIMPLEMATHBLOCK_H +#define SIMPLEMATHBLOCK_H + +#include +#include +#include +#include + +#include "compileassert.h" + +// #include "SimpleMathQR.h" + +/** \brief Namespace for a highly inefficient math library + * + */ +namespace SimpleMath { + +/** \brief Namespace for fixed size elements + */ +// forward declaration +template +class Matrix; + +template +class Block { + public: + typedef val_type value_type; + + Block() : + mParentRows(0), + mParentCols(0), + mParentRowStart(0), + mParentColStart(0) + { } + Block (const matrix_type &matrix, const unsigned int row_start, const unsigned int col_start, const unsigned int row_count, const unsigned int col_count) : + mParentRows (matrix.rows()), + mParentCols (matrix.cols()), + mParentRowStart (row_start), + mParentColStart (col_start), + mRowCount (row_count), + mColCount (col_count), + mTransposed (false) { + assert (mParentRows >= mParentRowStart + mRowCount); + assert (mParentCols >= mParentColStart + mColCount); + + // without the following line we could not create blocks from const + // matrices + mParentMatrix = const_cast(&matrix); + } + + // copy data from the other block into this + Block& operator=(const Block &other) { + if (this != &other) { + if (mRowCount != other.rows() || mColCount != other.cols()) { + std::cerr << "Error: cannot assign blocks of different size (left is " << mRowCount << "x" << mColCount << " right is " << other.rows() << "x" << other.cols() << ")!" << std::endl; + abort(); + } + + value_type* temp_values = new value_type [mRowCount * mColCount]; + + for (unsigned int i = 0; i < mRowCount; i++) { + for (unsigned int j = 0; j < mColCount; j++) { + temp_values[i * mColCount + j] = static_cast(other(i,j)); + } + } + + for (unsigned int i = 0; i < mRowCount; i++) { + for (unsigned int j = 0; j < mColCount; j++) { + (*this)(i,j) = temp_values[i * mColCount + j]; + } + } + + delete[] temp_values; + } + + return *this; + } + + template + // copy data from the other block into this + Block& operator=(const other_matrix_type &other) { + if (mRowCount != other.rows() || mColCount != other.cols()) { + std::cerr << "Error: cannot assign blocks of different size (left is " << mRowCount << "x" << mColCount << " right is " << other.rows() << "x" << other.cols() << ")!" << std::endl; + abort(); + } + + value_type *temp_values = new value_type[mRowCount * mColCount]; + + for (unsigned int i = 0; i < mRowCount; i++) { + for (unsigned int j = 0; j < mColCount; j++) { + temp_values[i * mColCount + j] = static_cast(other(i,j)); + } + } + + for (unsigned int i = 0; i < mRowCount; i++) { + for (unsigned int j = 0; j < mColCount; j++) { + (*this)(i,j) = temp_values[i * mColCount + j]; + } + } + + delete[] temp_values; + + return *this; + } + + unsigned int rows() const { + if (!mTransposed) + return mRowCount; + + return mColCount; + } + unsigned int cols() const { + if (!mTransposed) + return mColCount; + + return mRowCount; + } + const val_type& operator() (const unsigned int i, const unsigned int j) const { + + if (!mTransposed) { + assert (i < mRowCount); + assert (j < mColCount); + return (*mParentMatrix) (i + mParentRowStart, j + mParentColStart); + } + + return (*mParentMatrix) (j + mParentRowStart, i + mParentColStart); + } + + val_type& operator() (const unsigned int i, const unsigned int j) { + if (!mTransposed) { + assert (i < mRowCount); + assert (j < mColCount); + return (*mParentMatrix) (i + mParentRowStart, j + mParentColStart); + } + + assert (j < mRowCount); + assert (i < mColCount); + return (*mParentMatrix) (j + mParentRowStart, i + mParentColStart); + } + + Block transpose() const { + Block result (*this); + result.mTransposed = mTransposed ^ true; + return result; + } + + private: + matrix_type *mParentMatrix; + const unsigned int mParentRows; + const unsigned int mParentCols; + const unsigned int mParentRowStart; + const unsigned int mParentColStart; + const unsigned int mRowCount; + const unsigned int mColCount; + bool mTransposed; +}; + +template +inline std::ostream& operator<<(std::ostream& output, const Block &block) { + unsigned int i,j; + for (i = 0; i < block.rows(); i++) { + output << "[ "; + for (j = 0; j < block.cols(); j++) { + output << block(i,j); + + if (j < block.cols() - 1) + output << ", "; + } + output << " ]"; + + if (block.rows() > 1 && i < block.rows() - 1) + output << std::endl; + } + + return output; +} + + +} + +#endif /* SIMPLEMATHBLOCK_H */ diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCholesky.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCholesky.h new file mode 100644 index 0000000..b51a361 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCholesky.h @@ -0,0 +1,94 @@ +#ifndef _SIMPLE_MATH_CHOLESKY_H +#define _SIMPLE_MATH_CHOLESKY_H + +#include +#include + +#include "SimpleMathDynamic.h" + +namespace SimpleMath { + + template + class LLT { + public: + typedef typename matrix_type::value_type value_type; + + private: + LLT () {} + + typedef Dynamic::Matrix MatrixXXd; + typedef Dynamic::Matrix VectorXd; + + bool mIsFactorized; + MatrixXXd mL; + + public: + LLT (const matrix_type &matrix) : + mIsFactorized(false), + mL(matrix) { + compute(); + } + LLT compute() { + for (int i = 0; i < mL.rows(); i++) { + for (int j = 0; j < mL.rows(); j++) { + if (j > i) { + mL(i,j) = 0.; + continue; + } + double s = mL(i,j); + for (int k = 0; k < j; k++) { + s = s - mL(i,k) * mL(j,k); + } + if (i > j) { + mL(i,j) = s / mL(j,j); + } else if (s > 0.) { + mL (i,i) = sqrt (s); + } else { + std::cerr << "Error computing Cholesky decomposition: matrix not symmetric positive definite!" << std::endl; + assert (false); + } + } + } + + mIsFactorized = true; + + return *this; + } + Dynamic::Matrix solve ( + const Dynamic::Matrix &rhs + ) const { + assert (mIsFactorized); + + VectorXd y (mL.rows()); + for (unsigned int i = 0; i < mL.rows(); i++) { + double temp = rhs[i]; + + for (unsigned int j = 0; j < i; j++) { + temp = temp - mL(i,j) * y[j]; + } + + y[i] = temp / mL(i,i); + } + + VectorXd x (mL.rows()); + for (int i = mL.rows() - 1; i >= 0; i--) { + double temp = y[i]; + + for (unsigned int j = i + 1; j < mL.rows(); j++) { + temp = temp - mL(j, i) * x[j]; + } + + x[i] = temp / mL(i,i); + } + + return x; + } + Dynamic::Matrix matrixL() const { + return mL; + } + }; + +} + +/* _SIMPLE_MATH_CHOLESKY_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCommaInitializer.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCommaInitializer.h new file mode 100644 index 0000000..85657ba --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathCommaInitializer.h @@ -0,0 +1,69 @@ +#ifndef _SIMPLE_MATH_COMMA_INITIALIZER_H +#define _SIMPLE_MATH_COMMA_INITIALIZER_H + +#include +#include + +#include "SimpleMathFixed.h" +#include "SimpleMathDynamic.h" + +namespace SimpleMath { + + template + class CommaInitializer { + public: + typedef typename matrix_type::value_type value_type; + + CommaInitializer(matrix_type &matrix, const value_type &value) : + mElementWasAdded (false) { + assert (matrix.cols() > 0 && matrix.rows() > 0); + mParentMatrix = &matrix; + mRowIndex = 0; + mColIndex = 0; + (*mParentMatrix)(mRowIndex, mColIndex) = value; + } + CommaInitializer(matrix_type &matrix, unsigned int row_index, unsigned int col_index) : + mRowIndex (row_index), + mColIndex (col_index), + mElementWasAdded (false) { + assert (matrix.cols() > 0 && matrix.rows() > 0); + mParentMatrix = &matrix; + mRowIndex = row_index; + mColIndex = col_index; + } + ~CommaInitializer() { + if (!mElementWasAdded + && (mColIndex + 1 < mParentMatrix->cols() || mRowIndex + 1 < mParentMatrix->rows())) { + std::cerr << "Error: too few elements passed to CommaInitializer! Expected " << mParentMatrix->size() << " but was given " << mRowIndex * mParentMatrix->cols() + mColIndex + 1 << std::endl; + abort(); + } + } + CommaInitializer operator, (const value_type &value) { + mColIndex++; + if (mColIndex >= mParentMatrix->cols()) { + mRowIndex++; + mColIndex = 0; + } + if (mRowIndex == mParentMatrix->rows() && mColIndex == 0 ) { + std::cerr << "Error: too many elements passed to CommaInitializer! Expected " << mParentMatrix->size() << " but was given " << mRowIndex * mParentMatrix->cols() + mColIndex + 1 << std::endl; + abort(); + } + (*mParentMatrix)(mRowIndex, mColIndex) = value; + mElementWasAdded = true; + + return CommaInitializer (*mParentMatrix, mRowIndex, mColIndex); + } + + private: + CommaInitializer() {} + + matrix_type *mParentMatrix; + unsigned int mRowIndex; + unsigned int mColIndex; + bool mElementWasAdded; + }; + +} + +/* _SIMPLE_MATH_COMMA_INITIALIZER_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathDynamic.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathDynamic.h new file mode 100644 index 0000000..62975e8 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathDynamic.h @@ -0,0 +1,667 @@ +/** + * This is a highly inefficient math library. It was conceived by Martin + * Felis while he was compiling code + * that uses a highly efficient math library. + * + * It is intended to be used as a fast compiling substitute for the + * blazingly fast Eigen3 library and tries to mimic its API to a certain + * extend. + * + * Feel free to use it wherever you like. However, no guarantees are given + * that this code does what it says it would. + */ + +#ifndef SIMPLEMATHDYNAMIC_H +#define SIMPLEMATHDYNAMIC_H + +#include +#include +#include +#include + +#include "compileassert.h" +#include "SimpleMathBlock.h" + +/** \brief Namespace for a highly inefficient math library + * + */ +namespace SimpleMath { + +template +class LLT; + +template +class HouseholderQR; + +template +class ColPivHouseholderQR; + +template +class CommaInitializer; + +namespace Fixed { + template class Matrix; +} + + +/** \brief Namespace for elements of varying size. + */ +namespace Dynamic { + +// forward declaration +template +class Matrix; + +/** \brief Class for both matrices and vectors. + */ +template +class Matrix { + public: + typedef Matrix matrix_type; + typedef val_type value_type; + + Matrix() : + nrows (0), + ncols (0), + mapped_data (false), + mData (NULL) {}; + Matrix(unsigned int rows) : + nrows (rows), + ncols (1), + mapped_data (false) { + mData = new val_type[rows]; + } + Matrix(unsigned int rows, unsigned int cols) : + nrows (rows), + ncols (cols), + mapped_data (false) { + mData = new val_type[rows * cols]; + } + Matrix(unsigned int rows, unsigned int cols, val_type *data_ptr) : + nrows (rows), + ncols (cols), + mapped_data (true) { + mData = data_ptr; + } + + unsigned int rows() const { + return nrows; + } + + unsigned int cols() const { + return ncols; + } + + unsigned int size() const { + return nrows * ncols; + } + void resize (unsigned int rows, unsigned int cols=1) { + if (nrows * ncols > 0 && mData != NULL && mapped_data == false) { + delete[] mData; + } + + nrows = rows; + ncols = cols; + + mData = new val_type[nrows * ncols]; + } + + void conservativeResize (unsigned int rows, unsigned int cols = 1) { + Matrix result = Matrix::Zero(rows, cols); + + unsigned int arows = std::min (rows, nrows); + unsigned int acols = std::min (cols, ncols); + + for (unsigned int i = 0; i < arows; i++) { + for (unsigned int j = 0; j < acols; j++) { + result(i,j) = (*this)(i,j); + } + } + + *this = result; + } + + Matrix(const Matrix &matrix) : + nrows (matrix.nrows), + ncols (matrix.ncols), + mapped_data (false) { + unsigned int i; + + mData = new val_type[nrows * ncols]; + + for (i = 0; i < nrows * ncols; i++) + mData[i] = matrix.mData[i]; + } + Matrix& operator=(const Matrix &matrix) { + if (this != &matrix) { + if (!mapped_data) { + delete[] mData; + + nrows = matrix.nrows; + ncols = matrix.ncols; + mapped_data = false; + + mData = new val_type[nrows * ncols]; + + unsigned int i; + for (i = 0; i < nrows * ncols; i++) + mData[i] = matrix.mData[i]; + } else { + // we overwrite any existing data + nrows = matrix.nrows; + ncols = matrix.ncols; + mapped_data = true; + + unsigned int i; + for (i = 0; i < nrows * ncols; i++) + mData[i] = matrix.mData[i]; + } + } + return *this; + } + + CommaInitializer operator<< (const val_type& value) { + return CommaInitializer (*this, value); + } + + // conversion different val_types + template + Matrix (const Matrix &matrix) : + nrows (matrix.rows()), + ncols (matrix.cols()), + mapped_data(false) { + + mData = new val_type[nrows * ncols]; + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(matrix(i,j)); + } + } + } + + // conversion from a fixed size matrix + template + Matrix (const Fixed::Matrix &fixed_matrix) : + nrows (fnrows), + ncols (fncols), + mapped_data (false), + mData (NULL) { + mData = new val_type[nrows * ncols]; + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(fixed_matrix(i,j)); + } + } + } + + template + Matrix (const Block &block) : + nrows(block.rows()), + ncols(block.cols()), + mapped_data (false) { + mData = new val_type[nrows * ncols]; + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(block(i,j)); + } + } + + } + + ~Matrix() { + if (nrows * ncols > 0 && mData != NULL && mapped_data == false) { + delete[] mData; + mData = NULL; + } + + nrows = 0; + ncols = 0; + }; + + // comparison + bool operator==(const Matrix &matrix) const { + if (nrows != matrix.nrows || ncols != matrix.ncols) + return false; + + for (unsigned int i = 0; i < nrows * ncols; i++) { + if (mData[i] != matrix.mData[i]) + return false; + } + return true; + } + bool operator!=(const Matrix &matrix) const { + if (nrows != matrix.nrows || ncols != matrix.ncols) + return true; + + for (unsigned int i = 0; i < nrows * ncols; i++) { + if (mData[i] != matrix.mData[i]) + return true; + } + return false; + } + + // access operators + const val_type& operator[](const unsigned int &index) const { + assert (index >= 0); + assert (index < nrows * ncols); + return mData[index]; + }; + val_type& operator[](const unsigned int &index) { + assert (index >= 0 && index < nrows * ncols); + return mData[index]; + } + + const val_type& operator()(const unsigned int &row, const unsigned int &col) const { + if (!(row >= 0 && row < nrows && col >= 0 && col < ncols)) { + std::cout << "row = " << row << " col = " << col << std::endl; + std::cout << "nrows = " << nrows << " ncols = " << ncols << std::endl; + std::cout << "invalid read = " << mData[100000] << std::endl; + } + assert (row >= 0 && row < nrows && col >= 0 && col < ncols); + return mData[row*ncols + col]; + }; + val_type& operator()(const unsigned int &row, const unsigned int &col) { + assert (row >= 0 && row < nrows && col >= 0 && col < ncols); + return mData[row*ncols + col]; + }; + + void zero() { + for (unsigned int i = 0; i < ncols * nrows; i++) + mData[i] = 0.; + } + void setZero() { + zero(); + } + + val_type norm() const { + return sqrt(this->squaredNorm()); + } + + void normalize() { + val_type length = this->norm(); + + for (unsigned int i = 0; i < ncols * nrows; i++) + mData[i] /= length; + } + + Matrix normalized() const { + return Matrix (*this) / this->norm(); + } + + Matrix cross(const Matrix &other_vector) { + assert (nrows * ncols == 3); + assert (other_vector.nrows * other_vector.ncols == 3); + + Matrix result (3, 1); + result[0] = mData[1] * other_vector[2] - mData[2] * other_vector[1]; + result[1] = mData[2] * other_vector[0] - mData[0] * other_vector[2]; + result[2] = mData[0] * other_vector[1] - mData[1] * other_vector[0]; + + return result; + } + + val_type trace() const { + assert (rows() == cols()); + val_type result = 0.; + + for (unsigned int i = 0; i < rows(); i++) { + result += operator()(i,i); + } + + return result; + } + + val_type mean() const { + assert (rows() == 1 || cols() == 1); + + val_type result = 0.; + for (unsigned int i = 0; i < rows() * cols(); i++) { + result += operator[](i); + } + + return result / static_cast(rows() * cols()); + } + + static matrix_type Zero() { + matrix_type result; + result.setZero(); + return result; + } + + static matrix_type Zero(int rows, int cols = 1) { + matrix_type result (rows, cols); + result.setZero(); + return result; + } + + static matrix_type Constant (int rows, val_type value) { + matrix_type result (rows, 1); + unsigned int i; + for (i = 0; i < result.size(); i++) + result[i] = value; + + return result; + } + + static matrix_type Constant (int rows, int cols, val_type value) { + matrix_type result (rows, cols); + unsigned int i; + for (i = 0; i < result.size(); i++) + result[i] = value; + + return result; + } + + static matrix_type Identity (int rows, int cols = 1) { + assert (rows == cols); + + matrix_type result (rows, cols); + result.identity(); + + return result; + } + + void identity() { + assert (nrows == ncols); + + setZero(); + for (unsigned int i = 0; i < ncols; i++) + mData[i * ncols + i] = 1.; + } + + void random() { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] = static_cast (rand()) / static_cast (RAND_MAX); + } + + val_type squaredNorm() const { + assert (ncols == 1 || nrows == 1); + val_type result = 0; + + for (unsigned int i = 0; i < nrows * ncols; i++) + result += mData[i] * mData[i]; + + return result; + } + + val_type dot(const matrix_type &matrix) const { + assert (ncols == 1 || nrows == 1); + val_type result = 0; + + for (unsigned int i = 0; i < nrows * ncols; i++) + result += mData[i] * matrix[i]; + + return result; + } + + // Blocks + Block + block (unsigned int row_start, unsigned int col_start, unsigned int row_count, unsigned int col_count) { + return Block(*this, row_start, col_start, row_count, col_count); + } + + template + Block + block (unsigned int row_start, unsigned int col_start) { + return Block(*this, row_start, col_start, row_count, col_count); + } + + Block + block (unsigned int row_start, unsigned int col_start, unsigned int row_count, unsigned int col_count) const { + return Block(*this, row_start, col_start, row_count, col_count); + } + + template + Block + block (unsigned int row_start, unsigned int col_start) const { + return Block(*this, row_start, col_start, row_count, col_count); + } + + // row and col accessors + Block + col(unsigned int index) const { + return Block(*this, 0, index, rows(), 1); + } + + Block + col(unsigned int index) { + return Block(*this, 0, index, rows(), 1); + } + + Block + row(unsigned int index) const { + return Block(*this, index, 0, 1, cols()); + } + + Block + row(unsigned int index) { + return Block(*this, index, 0, 1, cols()); + } + + // Operators with scalars + void operator*=(const val_type &scalar) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] *= scalar; + }; + void operator/=(const val_type &scalar) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] /= scalar; + } + Matrix operator/(const val_type& scalar) const { + matrix_type result (*this); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result[i] /= scalar; + + return result; + } + + // Operators with other matrices + Matrix operator+(const Matrix &matrix) const { + matrix_type result (*this); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result[i] += matrix[i]; + + return result; + } + void operator+=(const matrix_type &matrix) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] += matrix.mData[i]; + } + Matrix operator-(const Matrix &matrix) const { + matrix_type result (*this); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result[i] -= matrix[i]; + + return result; + } + void operator-=(const Matrix &matrix) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] -= matrix.mData[i]; + } + + Matrix operator*(const Matrix &other_matrix) const { + assert (ncols == other_matrix.nrows); + + Matrix result(nrows, other_matrix.ncols); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < other_matrix.cols(); j++) { + for (k = 0; k < other_matrix.rows(); k++) { + result(i,j) += mData[i * ncols + k] * other_matrix(k,j); + } + } + } + + return result; + } + + template + Matrix operator*(const Fixed::Matrix &other_matrix) const { + assert (ncols == other_matrix.rows()); + + Matrix result(nrows, other_matrix.cols()); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < other_matrix.cols(); j++) { + for (k = 0; k < other_matrix.rows(); k++) { + result(i,j) += mData[i * ncols + k] * other_matrix(k,j); + } + } + } + + return result; + } + + Matrix operator*(const Block &other_matrix) const { + assert (ncols == other_matrix.rows()); + + Matrix result(nrows, other_matrix.cols()); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < other_matrix.cols(); j++) { + for (k = 0; k < other_matrix.rows(); k++) { + result(i,j) += mData[i * ncols + k] * other_matrix(k,j); + } + } + } + + return result; + } + + void operator*=(const Matrix &matrix) { + matrix_type temp (*this); + *this = temp * matrix; + } + + // Special operators + val_type *data(){ + return mData; + } + + // regular transpose of a 6 dimensional matrix + Matrix transpose() const { + Matrix result(ncols, nrows); + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + result(j,i) = mData[i * ncols + j]; + } + } + + return result; + } + + operator val_type() { + + assert (nrows == 1); + assert (nrows == 1); + + return mData[0]; + } + + Matrix operator-() const { + return *this * -1.0; + }; + + Matrix inverse() const { + return colPivHouseholderQr().inverse(); + } + + const LLT llt() const { + return LLT(*this); + } + + const HouseholderQR householderQr() const { + return HouseholderQR(*this); + } + const ColPivHouseholderQR colPivHouseholderQr() const { + return ColPivHouseholderQR(*this); + } + + private: + unsigned int nrows; + unsigned int ncols; + bool mapped_data; + + val_type* mData; +}; + +template +inline Matrix operator*(val_type scalar, const Matrix &matrix) { + Matrix result (matrix); + + for (unsigned int i = 0; i < matrix.rows() * matrix.cols(); i++) + result.data()[i] *= scalar; + + return result; +} + +template +inline Matrix operator*(const Matrix &matrix, other_type scalar) { + Matrix result (matrix); + + for (unsigned int i = 0; i < matrix.rows() * matrix.cols(); i++) + result.data()[i] *= static_cast(scalar); + + return result; +} + +template +inline std::ostream& operator<<(std::ostream& output, const Matrix &matrix) { + size_t max_width = 0; + size_t out_width = output.width(); + + // get the widest number + for (size_t i = 0; i < matrix.rows(); i++) { + for (size_t j = 0; j < matrix.cols(); j++) { + std::stringstream out_stream; + out_stream << matrix(i,j); + max_width = std::max (out_stream.str().size(),max_width); + } + } + + // overwrite width if it was explicitly prescribed + if (out_width != 0) { + max_width = out_width; + } + + for (unsigned int i = 0; i < matrix.rows(); i++) { + output.width(0); + output << "[ "; + output.width(out_width); + for (unsigned int j = 0; j < matrix.cols(); j++) { + std::stringstream out_stream; + out_stream.width (max_width); + out_stream << matrix(i,j); + output << out_stream.str(); + + if (j < matrix.cols() - 1) + output << ", "; + } + output << " ]"; + + if (matrix.rows() > 1 && i < matrix.rows() - 1) + output << std::endl; + } + return output;} + +} + +} + +#endif /* SIMPLEMATHDYNAMIC_H */ diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathFixed.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathFixed.h new file mode 100644 index 0000000..7a39b2b --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathFixed.h @@ -0,0 +1,881 @@ +/** + * This is a highly inefficient math library. It was conceived by Martin + * Felis while he was compiling code + * that uses a highly efficient math library. + * + * It is intended to be used as a fast compiling substitute for the + * blazingly fast Eigen3 library and tries to mimic its API to a certain + * extend. + * + * Feel free to use it wherever you like. However, no guarantees are given + * that this code does what it says it would. + */ + +#ifndef SIMPLEMATHFIXED_H +#define SIMPLEMATHFIXED_H + +#include +#include +#include +#include +#include + +#include "compileassert.h" +#include "SimpleMathBlock.h" + +/** \brief Namespace for a highly inefficient math library + * + */ +namespace SimpleMath { + +template +class LLT; + +template +class HouseholderQR; + +template +class ColPivHouseholderQR; + +template +class CommaInitializer; + +namespace Dynamic { +template class Matrix; +} + +/** \brief Namespace for fixed size elements + */ +namespace Fixed { + +// forward declaration +template +class Matrix; + +/** \brief Fixed size matrix class + */ +template +class Matrix { + public: + typedef Matrix matrix_type; + typedef val_type value_type; + + unsigned int rows() const { + return nrows; + } + + unsigned int cols() const { + return ncols; + } + + unsigned int size() const { + return nrows * ncols; + } + + Matrix() {}; + Matrix(const Matrix &matrix) { + unsigned int i; + for (i = 0; i < nrows * ncols; i++) + mData[i] = matrix.mData[i]; + } + Matrix& operator=(const Matrix &matrix) { + if (this != &matrix) { + unsigned int i; + for (i = 0; i < nrows * ncols; i++) + mData[i] = matrix.mData[i]; + } + return *this; + } + + // conversion different val_types + + template + Matrix (const Block &block) { + assert (nrows == block.rows()); + assert (ncols == block.cols()); + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(block(i,j)); + } + } + } + template + Matrix& operator= (const Block &block) { + assert (nrows == block.rows()); + assert (ncols == block.cols()); + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(block(i,j)); + } + } + + return *this; + } + + template + Matrix (const Matrix &matrix) { + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(matrix(i,j)); + } + } + } + + template + Matrix& operator=(const Matrix &matrix) { + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + (*this)(i,j) = static_cast(matrix(i,j)); + } + } + + return *this; + } + + CommaInitializer operator<< (const val_type& value) { + return CommaInitializer (*this, value); + } + + // conversion Dynamic->Fixed + Matrix(const Dynamic::Matrix &dynamic_matrix); + Matrix& operator=(const Dynamic::Matrix &dynamic_matrix); + + ~Matrix() {}; + + Matrix ( + const val_type &v00, const val_type &v01, const val_type &v02 + ) { + assert (nrows == 3); + assert (ncols == 1); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + } + + void set( + const val_type &v00, const val_type &v01, const val_type &v02 + ) { + COMPILE_ASSERT (nrows * ncols == 3); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + } + + Matrix ( + const val_type &v00, const val_type &v01, const val_type &v02, + const val_type &v10, const val_type &v11, const val_type &v12, + const val_type &v20, const val_type &v21, const val_type &v22 + ) { + COMPILE_ASSERT (nrows == 3); + COMPILE_ASSERT (ncols == 3); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + + mData[1 * 3 + 0] = v10; + mData[1 * 3 + 1] = v11; + mData[1 * 3 + 2] = v12; + + mData[2 * 3 + 0] = v20; + mData[2 * 3 + 1] = v21; + mData[2 * 3 + 2] = v22; + } + + void set( + const val_type v00, const val_type v01, const val_type v02, + const val_type v10, const val_type v11, const val_type v12, + const val_type v20, const val_type v21, const val_type v22 + ) { + COMPILE_ASSERT (nrows == 3); + COMPILE_ASSERT (ncols == 3); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + + mData[1 * 3 + 0] = v10; + mData[1 * 3 + 1] = v11; + mData[1 * 3 + 2] = v12; + + mData[2 * 3 + 0] = v20; + mData[2 * 3 + 1] = v21; + mData[2 * 3 + 2] = v22; + } + + Matrix ( + const val_type &v00, const val_type &v01, const val_type &v02, const val_type &v03 + ) { + assert (nrows == 4); + assert (ncols == 1); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + } + + void set( + const val_type &v00, const val_type &v01, const val_type &v02, const val_type &v03 + ) { + COMPILE_ASSERT (nrows * ncols == 4); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + } + + Matrix ( + const val_type &v00, const val_type &v01, const val_type &v02, const val_type &v03, + const val_type &v10, const val_type &v11, const val_type &v12, const val_type &v13, + const val_type &v20, const val_type &v21, const val_type &v22, const val_type &v23, + const val_type &v30, const val_type &v31, const val_type &v32, const val_type &v33 + ) { + COMPILE_ASSERT (nrows == 4); + COMPILE_ASSERT (ncols == 4); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + + mData[1 * 4 + 0] = v10; + mData[1 * 4 + 1] = v11; + mData[1 * 4 + 2] = v12; + mData[1 * 4 + 3] = v13; + + mData[2 * 4 + 0] = v20; + mData[2 * 4 + 1] = v21; + mData[2 * 4 + 2] = v22; + mData[2 * 4 + 3] = v23; + + mData[3 * 4 + 0] = v30; + mData[3 * 4 + 1] = v31; + mData[3 * 4 + 2] = v32; + mData[3 * 4 + 3] = v33; + } + + void set( + const val_type &v00, const val_type &v01, const val_type &v02, const val_type &v03, + const val_type &v10, const val_type &v11, const val_type &v12, const val_type &v13, + const val_type &v20, const val_type &v21, const val_type &v22, const val_type &v23, + const val_type &v30, const val_type &v31, const val_type &v32, const val_type &v33 + ) { + COMPILE_ASSERT (nrows == 4); + COMPILE_ASSERT (ncols == 4); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + + mData[1 * 4 + 0] = v10; + mData[1 * 4 + 1] = v11; + mData[1 * 4 + 2] = v12; + mData[1 * 4 + 3] = v13; + + mData[2 * 4 + 0] = v20; + mData[2 * 4 + 1] = v21; + mData[2 * 4 + 2] = v22; + mData[2 * 4 + 3] = v23; + + mData[3 * 4 + 0] = v30; + mData[3 * 4 + 1] = v31; + mData[3 * 4 + 2] = v32; + mData[3 * 4 + 3] = v33; + } + + Matrix ( + const val_type &v00, const val_type &v01, const val_type &v02, + const val_type &v03, const val_type &v04, const val_type &v05 + ) { + COMPILE_ASSERT (nrows == 6); + COMPILE_ASSERT (ncols == 1); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + mData[4] = v04; + mData[5] = v05; + } + + void set( + const val_type &v00, const val_type &v01, const val_type &v02, + const val_type &v03, const val_type &v04, const val_type &v05 + ) { + COMPILE_ASSERT (nrows * ncols == 6); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + mData[4] = v04; + mData[5] = v05; + } + + Matrix ( + const val_type &v00, const val_type &v01, const val_type &v02, + const val_type &v03, const val_type &v04, const val_type &v05, + + const val_type &v10, const val_type &v11, const val_type &v12, + const val_type &v13, const val_type &v14, const val_type &v15, + + const val_type &v20, const val_type &v21, const val_type &v22, + const val_type &v23, const val_type &v24, const val_type &v25, + + const val_type &v30, const val_type &v31, const val_type &v32, + const val_type &v33, const val_type &v34, const val_type &v35, + + const val_type &v40, const val_type &v41, const val_type &v42, + const val_type &v43, const val_type &v44, const val_type &v45, + + const val_type &v50, const val_type &v51, const val_type &v52, + const val_type &v53, const val_type &v54, const val_type &v55 + ) { + COMPILE_ASSERT (nrows == 6); + COMPILE_ASSERT (ncols == 6); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + mData[4] = v04; + mData[5] = v05; + + mData[6 + 0] = v10; + mData[6 + 1] = v11; + mData[6 + 2] = v12; + mData[6 + 3] = v13; + mData[6 + 4] = v14; + mData[6 + 5] = v15; + + mData[12 + 0] = v20; + mData[12 + 1] = v21; + mData[12 + 2] = v22; + mData[12 + 3] = v23; + mData[12 + 4] = v24; + mData[12 + 5] = v25; + + mData[18 + 0] = v30; + mData[18 + 1] = v31; + mData[18 + 2] = v32; + mData[18 + 3] = v33; + mData[18 + 4] = v34; + mData[18 + 5] = v35; + + mData[24 + 0] = v40; + mData[24 + 1] = v41; + mData[24 + 2] = v42; + mData[24 + 3] = v43; + mData[24 + 4] = v44; + mData[24 + 5] = v45; + + mData[30 + 0] = v50; + mData[30 + 1] = v51; + mData[30 + 2] = v52; + mData[30 + 3] = v53; + mData[30 + 4] = v54; + mData[30 + 5] = v55; + }; + + void set( + const val_type v00, const val_type v01, const val_type v02, + const val_type v03, const val_type v04, const val_type v05, + + const val_type v10, const val_type v11, const val_type v12, + const val_type v13, const val_type v14, const val_type v15, + + const val_type v20, const val_type v21, const val_type v22, + const val_type v23, const val_type v24, const val_type v25, + + const val_type v30, const val_type v31, const val_type v32, + const val_type v33, const val_type v34, const val_type v35, + + const val_type v40, const val_type v41, const val_type v42, + const val_type v43, const val_type v44, const val_type v45, + + const val_type v50, const val_type v51, const val_type v52, + const val_type v53, const val_type v54, const val_type v55 + ) { + COMPILE_ASSERT (nrows == 6); + COMPILE_ASSERT (ncols == 6); + + mData[0] = v00; + mData[1] = v01; + mData[2] = v02; + mData[3] = v03; + mData[4] = v04; + mData[5] = v05; + + mData[6 + 0] = v10; + mData[6 + 1] = v11; + mData[6 + 2] = v12; + mData[6 + 3] = v13; + mData[6 + 4] = v14; + mData[6 + 5] = v15; + + mData[12 + 0] = v20; + mData[12 + 1] = v21; + mData[12 + 2] = v22; + mData[12 + 3] = v23; + mData[12 + 4] = v24; + mData[12 + 5] = v25; + + mData[18 + 0] = v30; + mData[18 + 1] = v31; + mData[18 + 2] = v32; + mData[18 + 3] = v33; + mData[18 + 4] = v34; + mData[18 + 5] = v35; + + mData[24 + 0] = v40; + mData[24 + 1] = v41; + mData[24 + 2] = v42; + mData[24 + 3] = v43; + mData[24 + 4] = v44; + mData[24 + 5] = v45; + + mData[30 + 0] = v50; + mData[30 + 1] = v51; + mData[30 + 2] = v52; + mData[30 + 3] = v53; + mData[30 + 4] = v54; + mData[30 + 5] = v55; + } + + // comparison + bool operator==(const Matrix &matrix) const { + for (unsigned int i = 0; i < nrows * ncols; i++) { + if (mData[i] != matrix.mData[i]) + return false; + } + return true; + } + bool operator!=(const Matrix &matrix) const { + for (unsigned int i = 0; i < nrows * ncols; i++) { + if (mData[i] != matrix.mData[i]) + return true; + } + return false; + } + + // access operators + const val_type& operator[](const unsigned int &index) const { + assert (index >= 0 && index < nrows * ncols); + return mData[index]; + }; + val_type& operator[](const unsigned int &index) { + assert (index >= 0 && index < nrows * ncols); + return mData[index]; + } + + const val_type& operator()(const unsigned int &row, const unsigned int &col) const { + assert (row >= 0 && row < nrows && col >= 0 && col < ncols); + return mData[row*ncols + col]; + }; + val_type& operator()(const unsigned int &row, const unsigned int &col) { + assert (row >= 0 && row < nrows && col >= 0 && col < ncols); + return mData[row*ncols + col]; + }; + + void zero() { + for (unsigned int i = 0; i < ncols * nrows; i++) + mData[i] = 0.; + } + void setZero() { + zero(); + } + + val_type norm() const { + return sqrt(this->squaredNorm()); + } + + matrix_type normalize() { + val_type length = this->norm(); + + for (unsigned int i = 0; i < ncols * nrows; i++) + mData[i] /= length; + + return *this; + } + + matrix_type normalized() const { + return matrix_type (*this) / this->norm(); + } + + Matrix cross(const Matrix &other_vector) const { + COMPILE_ASSERT (nrows * ncols == 3); + + Matrix result; + result[0] = mData[1] * other_vector[2] - mData[2] * other_vector[1]; + result[1] = mData[2] * other_vector[0] - mData[0] * other_vector[2]; + result[2] = mData[0] * other_vector[1] - mData[1] * other_vector[0]; + + return result; + } + + val_type trace() const { + COMPILE_ASSERT(nrows == ncols); + val_type result = 0.; + + for (unsigned int i = 0; i < rows(); i++) { + result += operator()(i,i); + } + + return result; + } + + val_type mean() const { + COMPILE_ASSERT(nrows == 1 || ncols == 1); + + val_type result = 0.; + for (unsigned int i = 0; i < rows() * cols(); i++) { + result += operator[](i); + } + + return result / static_cast(nrows * ncols); + } + + static matrix_type Zero() { + matrix_type result; + result.setZero(); + return result; + } + + static matrix_type Zero(int ignore_me) { + matrix_type result; + result.setZero(); + return result; + } + + static matrix_type Zero(int ignore_me, int ignore_me_too) { + matrix_type result; + result.setZero(); + return result; + } + + static matrix_type Identity() { + matrix_type result; + result.identity(); + return result; + } + + static matrix_type Identity(int ignore_me, int ignore_me_too) { + matrix_type result; + result.identity(); + return result; + } + + void identity() { + COMPILE_ASSERT (nrows == ncols); + + setZero(); + for (unsigned int i = 0; i < ncols; i++) + mData[i * ncols + i] = 1.; + } + + void random() { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] = static_cast (rand()) / static_cast (RAND_MAX); + } + + val_type squaredNorm() const { + COMPILE_ASSERT (ncols == 1 || nrows == 1); + val_type result = 0; + + for (unsigned int i = 0; i < nrows * ncols; i++) + result += mData[i] * mData[i]; + + return result; + } + + val_type dot(const matrix_type &matrix) const { + COMPILE_ASSERT (ncols == 1 || nrows == 1); + val_type result = 0; + + for (unsigned int i = 0; i < nrows * ncols; i++) + result += mData[i] * matrix[i]; + + return result; + } + + // Blocks using block(i,j,r,c) syntax + Block + block (unsigned int row_start, unsigned int col_start, unsigned int row_count, unsigned int col_count) { + return Block(*this, row_start, col_start, row_count, col_count); + } + + const Block + block (unsigned int row_start, unsigned int col_start, unsigned int row_count, unsigned int col_count) const { + return Block(*this, row_start, col_start, row_count, col_count); + } + + // Blocks using block(i,j) syntax + template + Block + block (unsigned int row_start, unsigned int col_start) { + return Block(*this, row_start, col_start, block_row_count, block_col_count); + } + + template + const Block + block (unsigned int row_start, unsigned int col_start) const { + return Block(*this, row_start, col_start, block_row_count, block_col_count); + } + + // row and col accessors + Block + col(unsigned int index) const { + return Block(*this, 0, index, rows(), 1); + } + + Block + col(unsigned int index) { + return Block(*this, 0, index, rows(), 1); + } + + Block + row(unsigned int index) const { + return Block(*this, index, 0, 1, cols()); + } + + Block + row(unsigned int index) { + return Block(*this, index, 0, 1, cols()); + } + + // Operators with scalars + void operator*=(const val_type &scalar) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] *= scalar; + }; + void operator/=(const val_type &scalar) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] /= scalar; + } + Matrix operator/(const val_type& scalar) const { + matrix_type result (*this); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result[i] /= scalar; + + return result; + } + + // Operators with other matrices + Matrix operator+(const Matrix &matrix) const { + matrix_type result (*this); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result[i] += matrix[i]; + + return result; + } + void operator+=(const matrix_type &matrix) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] += matrix.mData[i]; + } + Matrix operator-(const Matrix &matrix) const { + matrix_type result (*this); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result[i] -= matrix[i]; + + return result; + } + void operator-=(const Matrix &matrix) { + for (unsigned int i = 0; i < nrows * ncols; i++) + mData[i] -= matrix.mData[i]; + } + + template + Matrix operator*(const Matrix &matrix) const { + COMPILE_ASSERT (ncols == other_rows); + + Matrix result; + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < other_cols; j++) { + for (k = 0; k < other_rows; k++) { + result(i,j) += mData[i * ncols + k] * matrix(k,j); + } + } + } + + return result; + } + + // multiplication with dynamic sized matrix + template + Dynamic::Matrix operator*(const Dynamic::Matrix &other_matrix) { + assert (ncols == other_matrix.rows()); + + Dynamic::Matrix result(nrows, other_matrix.cols()); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < other_matrix.cols(); j++) { + for (k = 0; k < other_matrix.rows(); k++) { + result(i,j) += mData[i * ncols + k] * static_cast(other_matrix(k,j)); + } + } + } + + return result; + } + + // Multiplication with a block + template + Dynamic::Matrix operator*(const Block &block) { + assert (ncols == block.rows()); + + Dynamic::Matrix result(nrows, block.cols()); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < block.cols(); j++) { + for (k = 0; k < block.rows(); k++) { + result(i,j) += mData[i * ncols + k] * static_cast(block(k,j)); + } + } + } + + return result; + } + + + void operator*=(const Matrix &matrix) { + matrix_type temp (*this); + *this = temp * matrix; + } + + // Special operators + val_type *data(){ + return mData; + } + + const val_type *data() const{ + return mData; + } + + // regular transpose of a 6 dimensional matrix + Matrix transpose() const { + Matrix result; + + for (unsigned int i = 0; i < nrows; i++) { + for (unsigned int j = 0; j < ncols; j++) { + result(j,i) = mData[i * ncols + j]; + } + } + + return result; + } + + operator val_type() { + COMPILE_ASSERT (nrows == 1); + COMPILE_ASSERT (nrows == 1); + + return mData[0]; + } + + Matrix operator-() const { + return *this * -1.; + } + + Matrix inverse() const { + return colPivHouseholderQr().inverse(); + } + + const LLT llt() const { + return LLT(*this); + } + + const HouseholderQR householderQr() const { + return HouseholderQR(*this); + } + const ColPivHouseholderQR colPivHouseholderQr() const { + return ColPivHouseholderQR(*this); + } + + private: + val_type mData[nrows * ncols]; +}; + +template +inline Matrix operator*(val_type scalar, const Matrix &matrix) { + Matrix result (matrix); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result.data()[i] *= scalar; + + return result; +} + +template +inline Matrix operator*(const Matrix &matrix, other_type scalar) { + Matrix result (matrix); + + for (unsigned int i = 0; i < nrows * ncols; i++) + result.data()[i] *= static_cast (scalar); + + return result; +} + +template +inline std::ostream& operator<<(std::ostream& output, const Matrix &matrix) { + size_t max_width = 0; + size_t out_width = output.width(); + + // get the widest number + for (size_t i = 0; i < matrix.rows(); i++) { + for (size_t j = 0; j < matrix.cols(); j++) { + std::stringstream out_stream; + out_stream << matrix(i,j); + max_width = std::max (out_stream.str().size(),max_width); + } + } + + // overwrite width if it was explicitly prescribed + if (out_width != 0) { + max_width = out_width; + } + + for (unsigned int i = 0; i < matrix.rows(); i++) { + output.width(0); + output << "[ "; + output.width(out_width); + for (unsigned int j = 0; j < matrix.cols(); j++) { + std::stringstream out_stream; + out_stream.width (max_width); + out_stream << matrix(i,j); + output << out_stream.str(); + + if (j < matrix.cols() - 1) + output << ", "; + } + output << " ]"; + + if (matrix.rows() > 1 && i < matrix.rows() - 1) + output << std::endl; + } + return output; +} + +} + +} + +#endif /* SIMPLEMATHFIXED_H */ diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathGL.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathGL.h new file mode 100644 index 0000000..15e45ee --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathGL.h @@ -0,0 +1,253 @@ +#ifndef _SIMPLEMATHGL_H_ +#define _SIMPLEMATHGL_H_ + +#include "SimpleMath.h" +#include + +namespace SimpleMath { + +namespace GL { + +inline Matrix44f RotateMat44 (float rot_deg, float x, float y, float z) { + float c = cosf (rot_deg * M_PI / 180.f); + float s = sinf (rot_deg * M_PI / 180.f); + return Matrix44f ( + x * x * (1.0f - c) + c, + y * x * (1.0f - c) + z * s, + x * z * (1.0f - c) - y * s, + 0.f, + + x * y * (1.0f - c) - z * s, + y * y * (1.0f - c) + c, + y * z * (1.0f - c) + x * s, + 0.f, + + x * z * (1.0f - c) + y * s, + y * z * (1.0f - c) - x * s, + z * z * (1.0f - c) + c, + 0.f, + + 0.f, 0.f, 0.f, 1.f + ); +} + +inline Matrix44f TranslateMat44 (float x, float y, float z) { + return Matrix44f ( + 1.f, 0.f, 0.f, 0.f, + 0.f, 1.f, 0.f, 0.f, + 0.f, 0.f, 1.f, 0.f, + x, y, z, 1.f + ); +} + +inline Matrix44f ScaleMat44 (float x, float y, float z) { + return Matrix44f ( + x, 0.f, 0.f, 0.f, + 0.f, y, 0.f, 0.f, + 0.f, 0.f, z, 0.f, + 0.f, 0.f, 0.f, 1.f + ); +} + +/** Quaternion + * + * order: x,y,z,w + */ +class Quaternion : public Vector4f { + public: + Quaternion () : + Vector4f (0.f, 0.f, 0.f, 1.f) + {} + Quaternion (const Vector4f vec4) : + Vector4f (vec4) + {} + Quaternion (float x, float y, float z, float w): + Vector4f (x, y, z, w) + {} + /** This function is equivalent to multiplicate their corresponding rotation matrices */ + Quaternion operator* (const Quaternion &q) const { + return Quaternion ( + q[3] * (*this)[0] + q[0] * (*this)[3] + q[1] * (*this)[2] - q[2] * (*this)[1], + q[3] * (*this)[1] + q[1] * (*this)[3] + q[2] * (*this)[0] - q[0] * (*this)[2], + q[3] * (*this)[2] + q[2] * (*this)[3] + q[0] * (*this)[1] - q[1] * (*this)[0], + q[3] * (*this)[3] - q[0] * (*this)[0] - q[1] * (*this)[1] - q[2] * (*this)[2] + ); + } + Quaternion& operator*=(const Quaternion &q) { + set ( + q[3] * (*this)[0] + q[0] * (*this)[3] + q[1] * (*this)[2] - q[2] * (*this)[1], + q[3] * (*this)[1] + q[1] * (*this)[3] + q[2] * (*this)[0] - q[0] * (*this)[2], + q[3] * (*this)[2] + q[2] * (*this)[3] + q[0] * (*this)[1] - q[1] * (*this)[0], + q[3] * (*this)[3] - q[0] * (*this)[0] - q[1] * (*this)[1] - q[2] * (*this)[2] + ); + return *this; + } + + static Quaternion fromGLRotate (float angle, float x, float y, float z) { + float st = sinf (angle * M_PI / 360.f); + return Quaternion ( + st * x, + st * y, + st * z, + cosf (angle * M_PI / 360.f) + ); + } + + Quaternion normalize() { + return Vector4f::normalize(); + } + + Quaternion slerp (float alpha, const Quaternion &quat) const { + // check whether one of the two has 0 length + float s = sqrt (squaredNorm() * quat.squaredNorm()); + + // division by 0.f is unhealthy! + assert (s != 0.f); + + float angle = acos (dot(quat) / s); + if (angle == 0.f || isnan(angle)) { + return *this; + } + assert(!isnan(angle)); + + float d = 1.f / sinf (angle); + float p0 = sinf ((1.f - alpha) * angle); + float p1 = sinf (alpha * angle); + + if (dot (quat) < 0.f) { + return Quaternion( ((*this) * p0 - quat * p1) * d); + } + return Quaternion( ((*this) * p0 + quat * p1) * d); + } + + Matrix44f toGLMatrix() const { + float x = (*this)[0]; + float y = (*this)[1]; + float z = (*this)[2]; + float w = (*this)[3]; + return Matrix44f ( + 1 - 2*y*y - 2*z*z, + 2*x*y + 2*w*z, + 2*x*z - 2*w*y, + 0.f, + + 2*x*y - 2*w*z, + 1 - 2*x*x - 2*z*z, + 2*y*z + 2*w*x, + 0.f, + + 2*x*z + 2*w*y, + 2*y*z - 2*w*x, + 1 - 2*x*x - 2*y*y, + 0.f, + + 0.f, + 0.f, + 0.f, + 1.f); + } + + static Quaternion fromGLMatrix(const Matrix44f &mat) { + float w = sqrt (1.f + mat(0,0) + mat(1,1) + mat(2,2)) * 0.5f; + return Quaternion ( + -(mat(2,1) - mat(1,2)) / (w * 4.f), + -(mat(0,2) - mat(2,0)) / (w * 4.f), + -(mat(1,0) - mat(0,1)) / (w * 4.f), + w); + } + + static Quaternion fromMatrix (const Matrix33f &mat) { + float w = sqrt (1.f + mat(0,0) + mat(1,1) + mat(2,2)) * 0.5f; + return Quaternion ( + (mat(2,1) - mat(1,2)) / (w * 4.f), + (mat(0,2) - mat(2,0)) / (w * 4.f), + (mat(1,0) - mat(0,1)) / (w * 4.f), + w); + } + + static Quaternion fromEulerZYX (const Vector3f &zyx_euler) { + return Quaternion::fromGLRotate (zyx_euler[0] * 180.f / M_PI, 0.f, 0.f, 1.f) + * Quaternion::fromGLRotate (zyx_euler[1] * 180.f / M_PI, 0.f, 1.f, 0.f) + * Quaternion::fromGLRotate (zyx_euler[2] * 180.f / M_PI, 1.f, 0.f, 0.f); + } + + Vector3f toEulerZYX () const { + return Vector3f ( + atan2 (-2.f * (*this)[0] * (*this)[1] + 2.f * (*this)[3] * (*this)[2], + (*this)[0] * (*this)[0] + (*this)[3] * (*this)[3] + -(*this)[2] * (*this)[2] - (*this)[1] * (*this)[1]), + asin (2.f * (*this)[0] * (*this)[2] + 2.f * (*this)[3] * (*this)[1]), + atan2 (-2.f * (*this)[1] * (*this)[2] + 2.f * (*this)[3] * (*this)[0], + (*this)[2] * (*this)[2] - (*this)[1] * (*this)[1] + -(*this)[0] * (*this)[0] + (*this)[3] * (*this)[3] + ) + ); + } + + + static Quaternion fromEulerYXZ (const Vector3f &yxz_euler) { + return Quaternion::fromGLRotate (yxz_euler[0] * 180.f / M_PI, 0.f, 1.f, 0.f) + * Quaternion::fromGLRotate (yxz_euler[1] * 180.f / M_PI, 1.f, 0.f, 0.f) + * Quaternion::fromGLRotate (yxz_euler[2] * 180.f / M_PI, 0.f, 0.f, 1.f); + } + + Vector3f toEulerYXZ() const { + return Vector3f ( + atan2 (-2.f * (*this)[0] * (*this)[2] + 2.f * (*this)[3] * (*this)[1], + (*this)[2] * (*this)[2] - (*this)[1] * (*this)[1] + -(*this)[0] * (*this)[0] + (*this)[3] * (*this)[3]), + asin (2.f * (*this)[1] * (*this)[2] + 2.f * (*this)[3] * (*this)[0]), + atan2 (-2.f * (*this)[0] * (*this)[1] + 2.f * (*this)[3] * (*this)[2], + (*this)[1] * (*this)[1] - (*this)[2] * (*this)[2] + +(*this)[3] * (*this)[3] - (*this)[0] * (*this)[0] + ) + ); + } + + Matrix33f toMatrix() const { + float x = (*this)[0]; + float y = (*this)[1]; + float z = (*this)[2]; + float w = (*this)[3]; + return Matrix33f ( + 1 - 2*y*y - 2*z*z, + 2*x*y - 2*w*z, + 2*x*z + 2*w*y, + + 2*x*y + 2*w*z, + 1 - 2*x*x - 2*z*z, + 2*y*z - 2*w*x, + + 2*x*z - 2*w*y, + 2*y*z + 2*w*x, + 1 - 2*x*x - 2*y*y + ); + } + + Quaternion conjugate() const { + return Quaternion ( + -(*this)[0], + -(*this)[1], + -(*this)[2], + (*this)[3]); + } + + Vector3f rotate (const Vector3f &vec) const { + Vector3f vn (vec); + Quaternion vec_quat (vn[0], vn[1], vn[2], 0.f), res_quat; + + res_quat = vec_quat * (*this); + res_quat = conjugate() * res_quat; + + return Vector3f (res_quat[0], res_quat[1], res_quat[2]); + } +}; + +// namespace GL +} + +// namespace SimpleMath +} + +/* _SIMPLEMATHGL_H_ */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMap.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMap.h new file mode 100644 index 0000000..dbb757a --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMap.h @@ -0,0 +1,22 @@ +#ifndef SIMPLEMATHMAP_H +#define SIMPLEMATHMAP_H + +#include "compileassert.h" + +namespace SimpleMath { + +/** \brief \brief Wraps a varying size matrix type around existing data + * + * \warning If you create a matrix using the map function and then assign + * a bigger matrix you invalidate the original memory! + * + */ +template < typename MatrixType > +MatrixType Map (typename MatrixType::value_type *data, unsigned int rows, unsigned int cols) { + return MatrixType (rows, cols, data); +} + +} + +// SIMPLEMATHMAP_H +#endif diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMixed.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMixed.h new file mode 100644 index 0000000..777670f --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathMixed.h @@ -0,0 +1,138 @@ +/** + * This is a highly inefficient math library. It was conceived by Martin + * Felis while he was compiling code + * that uses a highly efficient math library. + * + * It is intended to be used as a fast compiling substitute for the + * blazingly fast Eigen3 library and tries to mimic its API to a certain + * extend. + * + * Feel free to use it wherever you like. However, no guarantees are given + * that this code does what it says it would. + */ + +#ifndef SIMPLEMATHMIXED_H +#define SIMPLEMATHMIXED_H + +#include +#include +#include +#include + +#include "compileassert.h" + +/** \brief Namespace for a highly inefficient math library + * + */ +namespace SimpleMath { + +// conversion Dynamic->Fixed +template +inline Fixed::Matrix::Matrix(const Dynamic::Matrix &dynamic_matrix) { + if (dynamic_matrix.cols() != ncols + || dynamic_matrix.rows() != nrows) { + std::cerr << "Error: cannot assign a dynamic sized matrix of size " << dynamic_matrix.rows() << "x" << dynamic_matrix.cols() << " to a fixed size matrix of size " << nrows << "x" << ncols << "!" << std::endl; + abort(); + } + + for (unsigned int i = 0; i < nrows * ncols; i++) { + mData[i] = dynamic_matrix[i]; + } +} + +template +inline Fixed::Matrix& Fixed::Matrix::operator=(const Dynamic::Matrix &dynamic_matrix) { + if (dynamic_matrix.cols() != ncols + || dynamic_matrix.rows() != nrows) { + std::cerr << "Error: cannot assign a dynamic sized matrix of size " << dynamic_matrix.rows() << "x" << dynamic_matrix.cols() << " to a fixed size matrix of size " << nrows << "x" << ncols << "!" << std::endl; + abort(); + } + + for (unsigned int i = 0; i < nrows * ncols; i++) { + mData[i] = dynamic_matrix[i]; + } + + return *this; +} + +// multiplication +template +inline Dynamic::Matrix operator*( + const Fixed::Matrix &matrix_a, + const Dynamic::Matrix &matrix_b) { + assert (matrix_a.cols() == matrix_b.rows()); + + Dynamic::Matrix result (nrows, matrix_b.cols()); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < nrows; i++) { + for (j = 0; j < matrix_b.cols(); j++) { + for (k = 0; k < matrix_b.rows(); k++) { + result(i,j) += matrix_a(i,k) * matrix_b(k,j); + } + } + } + + return result; +} + +template +inline Dynamic::Matrix operator*( + const Dynamic::Matrix &matrix_a, + const Fixed::Matrix &matrix_b) { + assert (matrix_a.cols() == matrix_b.rows()); + + Dynamic::Matrix result (matrix_a.rows(), ncols); + + result.setZero(); + + unsigned int i,j, k; + for (i = 0; i < matrix_a.rows(); i++) { + for (j = 0; j < matrix_b.cols(); j++) { + for (k = 0; k < matrix_b.rows(); k++) { + result(i,j) += matrix_a(i,k) * matrix_b(k,j); + } + } + } + + return result; +} + +// equality +template +inline bool operator==( + const Fixed::Matrix &matrix_a, + const Dynamic::Matrix &matrix_b) { + assert (nrows == matrix_a.rows()); + assert (ncols == matrix_a.cols()); + + unsigned int i; + for (i = 0; i < matrix_a.size(); i++) { + if (matrix_a[i] != matrix_b[i]) + return false; + } + + return true; +} + +template +inline bool operator==( + const Dynamic::Matrix &matrix_b, + const Fixed::Matrix &matrix_a) { + assert (nrows == matrix_a.rows()); + assert (ncols == matrix_a.cols()); + + unsigned int i; + for (i = 0; i < matrix_a.size(); i++) { + if (matrix_a[i] != matrix_b[i]) + return false; + } + + return true; +} + +} + +#endif /* SIMPLEMATHMIXED_H */ diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathQR.h b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathQR.h new file mode 100644 index 0000000..91832f2 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/SimpleMathQR.h @@ -0,0 +1,324 @@ +#ifndef _SIMPLE_MATH_QR_H +#define _SIMPLE_MATH_QR_H + +#include +#include + +#include "SimpleMathFixed.h" +#include "SimpleMathDynamic.h" +#include "SimpleMathBlock.h" + +namespace SimpleMath { + + template + class HouseholderQR { + public: + typedef typename matrix_type::value_type value_type; + HouseholderQR() : + mIsFactorized(false) + {} + + private: + typedef Dynamic::Matrix MatrixXXd; + typedef Dynamic::Matrix VectorXd; + + bool mIsFactorized; + MatrixXXd mQ; + MatrixXXd mR; + + public: + HouseholderQR(const matrix_type &matrix) : + mIsFactorized(false), + mQ(matrix.rows(), matrix.rows()) + { + compute(matrix); + } + HouseholderQR compute(const matrix_type &matrix) { + mR = matrix; + mQ = Dynamic::Matrix::Identity (mR.rows(), mR.rows()); + + for (unsigned int i = 0; i < mR.cols(); i++) { + unsigned int block_rows = mR.rows() - i; + unsigned int block_cols = mR.cols() - i; + + MatrixXXd current_block = mR.block(i,i, block_rows, block_cols); + VectorXd column = current_block.block(0, 0, block_rows, 1); + + value_type alpha = - column.norm(); + if (current_block(0,0) < 0) { + alpha = - alpha; + } + + VectorXd v = current_block.block(0, 0, block_rows, 1); + v[0] = v[0] - alpha; + + MatrixXXd Q (MatrixXXd::Identity(mR.rows(), mR.rows())); + Q.block(i, i, block_rows, block_rows) = MatrixXXd (Q.block(i, i, block_rows, block_rows)) + - MatrixXXd(v * v.transpose() / (v.squaredNorm() * 0.5)); + + mR = Q * mR; + + // Normalize so that we have positive diagonal elements + if (mR(i,i) < 0) { + mR.block(i,i,block_rows, block_cols) = MatrixXXd(mR.block(i,i,block_rows, block_cols)) * -1.; + Q.block(i,i,block_rows, block_rows) = MatrixXXd(Q.block(i,i,block_rows, block_rows)) * -1.; + } + + mQ = mQ * Q; + } + + mIsFactorized = true; + + return *this; + } + Dynamic::Matrix solve ( + const Dynamic::Matrix &rhs + ) const { + assert (mIsFactorized); + + VectorXd y = mQ.transpose() * rhs; + VectorXd x = VectorXd::Zero(mR.cols()); + + for (int i = mR.cols() - 1; i >= 0; --i) { + value_type z = y[i]; + + for (unsigned int j = i + 1; j < mR.cols(); j++) { + z = z - x[j] * mR(i,j); + } + + if (fabs(mR(i,i)) < std::numeric_limits::epsilon() * 10) { + std::cerr << "HouseholderQR: Cannot back-substitute as diagonal element is near zero!" << std::endl; + abort(); + } + x[i] = z / mR(i,i); + } + + return x; + } + Dynamic::Matrix inverse() const { + assert (mIsFactorized); + + VectorXd rhs_temp = VectorXd::Zero(mQ.cols()); + MatrixXXd result (mQ.cols(), mQ.cols()); + + for (unsigned int i = 0; i < mQ.cols(); i++) { + rhs_temp[i] = 1.; + + result.block(0, i, mQ.cols(), 1) = solve(rhs_temp); + + rhs_temp[i] = 0.; + } + + return result; + } + Dynamic::Matrix householderQ () const { + return mQ; + } + Dynamic::Matrix matrixR () const { + return mR; + } + }; + + template + class ColPivHouseholderQR { + public: + typedef typename matrix_type::value_type value_type; + private: + typedef Dynamic::Matrix MatrixXXd; + typedef Dynamic::Matrix VectorXd; + + bool mIsFactorized; + MatrixXXd mQ; + MatrixXXd mR; + unsigned int *mPermutations; + value_type mThreshold; + unsigned int mRank; + + public: + ColPivHouseholderQR(): + mIsFactorized(false) { + mPermutations = new unsigned int[1]; + } + + ColPivHouseholderQR (const ColPivHouseholderQR& other) { + mIsFactorized = other.mIsFactorized; + mQ = other.mQ; + mR = other.mR; + mPermutations = new unsigned int[mQ.cols()]; + mThreshold = other.mThreshold; + mRank = other.mRank; + } + + ColPivHouseholderQR& operator= (const ColPivHouseholderQR& other) { + if (this != &other) { + mIsFactorized = other.mIsFactorized; + mQ = other.mQ; + mR = other.mR; + delete[] mPermutations; + mPermutations = new unsigned int[mQ.cols()]; + mThreshold = other.mThreshold; + mRank = other.mRank; + } + + return *this; + } + + ColPivHouseholderQR(const matrix_type &matrix) : + mIsFactorized(false), + mQ(matrix.rows(), matrix.rows()), + mThreshold (std::numeric_limits::epsilon() * matrix.cols()) { + mPermutations = new unsigned int [matrix.cols()]; + for (unsigned int i = 0; i < matrix.cols(); i++) { + mPermutations[i] = i; + } + compute(matrix); + } + ~ColPivHouseholderQR() { + delete[] mPermutations; + } + + ColPivHouseholderQR& setThreshold (const value_type& threshold) { + mThreshold = threshold; + + return *this; + } + ColPivHouseholderQR& compute(const matrix_type &matrix) { + mR = matrix; + mQ = Dynamic::Matrix::Identity (mR.rows(), mR.rows()); + + for (unsigned int i = 0; i < mR.cols(); i++) { + unsigned int block_rows = mR.rows() - i; + unsigned int block_cols = mR.cols() - i; + + // find and swap the column with the highest norm + unsigned int col_index_norm_max = i; + value_type col_norm_max = VectorXd(mR.block(i,i, block_rows, 1)).squaredNorm(); + + for (unsigned int j = i + 1; j < mR.cols(); j++) { + VectorXd column = mR.block(i, j, block_rows, 1); + + if (column.squaredNorm() > col_norm_max) { + col_index_norm_max = j; + col_norm_max = column.squaredNorm(); + } + } + + if (col_norm_max < mThreshold) { + // if all entries of the column is close to zero, we bail out + break; + } + + + if (col_index_norm_max != i) { + VectorXd temp_col = mR.block(0, i, mR.rows(), 1); + mR.block(0,i,mR.rows(),1) = mR.block(0, col_index_norm_max, mR.rows(), 1); + mR.block(0, col_index_norm_max, mR.rows(), 1) = temp_col; + + unsigned int temp_index = mPermutations[i]; + mPermutations[i] = mPermutations[col_index_norm_max]; + mPermutations[col_index_norm_max] = temp_index; + } + + MatrixXXd current_block = mR.block(i,i, block_rows, block_cols); + VectorXd column = current_block.block(0, 0, block_rows, 1); + + value_type alpha = - column.norm(); + if (current_block(0,0) < 0) { + alpha = - alpha; + } + + VectorXd v = current_block.block(0, 0, block_rows, 1); + v[0] = v[0] - alpha; + + MatrixXXd Q (MatrixXXd::Identity(mR.rows(), mR.rows())); + + Q.block(i, i, block_rows, block_rows) = MatrixXXd (Q.block(i, i, block_rows, block_rows)) + - MatrixXXd(v * v.transpose() / (v.squaredNorm() * 0.5)); + + mR = Q * mR; + + // Normalize so that we have positive diagonal elements + if (mR(i,i) < 0) { + mR.block(i,i,block_rows, block_cols) = MatrixXXd(mR.block(i,i,block_rows, block_cols)) * -1.; + Q.block(i,i,block_rows, block_rows) = MatrixXXd(Q.block(i,i,block_rows, block_rows)) * -1.; + } + + mQ = mQ * Q; + } + + mIsFactorized = true; + + return *this; + } + Dynamic::Matrix solve ( + const Dynamic::Matrix &rhs + ) const { + assert (mIsFactorized); + + VectorXd y = mQ.transpose() * rhs; + VectorXd x = VectorXd::Zero(mR.cols()); + + for (int i = mR.cols() - 1; i >= 0; --i) { + value_type z = y[i]; + + for (unsigned int j = i + 1; j < mR.cols(); j++) { + z = z - x[mPermutations[j]] * mR(i,j); + } + + if (fabs(mR(i,i)) < std::numeric_limits::epsilon() * 10) { + std::cerr << "HouseholderQR: Cannot back-substitute as diagonal element is near zero!" << std::endl; + abort(); + } + x[mPermutations[i]] = z / mR(i,i); + } + + return x; + } + Dynamic::Matrix inverse() const { + assert (mIsFactorized); + + VectorXd rhs_temp = VectorXd::Zero(mQ.cols()); + MatrixXXd result (mQ.cols(), mQ.cols()); + + for (unsigned int i = 0; i < mQ.cols(); i++) { + rhs_temp[i] = 1.; + + result.block(0, i, mQ.cols(), 1) = solve(rhs_temp); + + rhs_temp[i] = 0.; + } + + return result; + } + + Dynamic::Matrix householderQ () const { + return mQ; + } + Dynamic::Matrix matrixR () const { + return mR; + } + Dynamic::Matrix matrixP () const { + MatrixXXd P = MatrixXXd::Identity(mR.cols(), mR.cols()); + MatrixXXd identity = MatrixXXd::Identity(mR.cols(), mR.cols()); + for (unsigned int i = 0; i < mR.cols(); i++) { + P.block(0,i,mR.cols(),1) = identity.block(0,mPermutations[i], mR.cols(), 1); + } + return P; + } + + unsigned int rank() const { + value_type abs_threshold = fabs(mR(0,0)) * mThreshold; + + for (unsigned int i = 1; i < mR.cols(); i++) { + if (fabs(mR(i,i) < abs_threshold)) + return i; + } + + return mR.cols(); + } + }; + +} + +/* _SIMPLE_MATH_QR_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/SimpleMath/compileassert.h b/3rdparty/rbdl/include/rbdl/SimpleMath/compileassert.h new file mode 100644 index 0000000..4d12fdb --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SimpleMath/compileassert.h @@ -0,0 +1,39 @@ +#ifndef _COMPILE_ASSERT_H +#define _COMPILE_ASSERT_H + +/* + * This is a simple compile time assertion tool taken from: + * http://blogs.msdn.com/b/abhinaba/archive/2008/10/27/c-c-compile-time-asserts.aspx + * written by Abhinaba Basu! + * + * Thanks! + */ + +#ifdef __cplusplus + +#define JOIN( X, Y ) JOIN2(X,Y) +#define JOIN2( X, Y ) X##Y + +namespace static_assert_compat +{ + template struct STATIC_ASSERT_FAILURE; + template <> struct STATIC_ASSERT_FAILURE { enum { value = 1 }; }; + + template struct static_assert_test{}; +} + +#define COMPILE_ASSERT(x) \ + typedef ::static_assert_compat::static_assert_test<\ + sizeof(::static_assert_compat::STATIC_ASSERT_FAILURE< (bool)( x ) >)>\ + JOIN(_static_assert_typedef, __LINE__) + +#else // __cplusplus + +#define COMPILE_ASSERT(x) extern int __dummy[(int)x] + +#endif // __cplusplus + +#define VERIFY_EXPLICIT_CAST(from, to) COMPILE_ASSERT(sizeof(from) == sizeof(to)) + +// _COMPILE_ASSERT_H_ +#endif diff --git a/3rdparty/rbdl/include/rbdl/SpatialAlgebraOperators.h b/3rdparty/rbdl/include/rbdl/SpatialAlgebraOperators.h new file mode 100644 index 0000000..2ec9bed --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/SpatialAlgebraOperators.h @@ -0,0 +1,452 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_SPATIALALGEBRAOPERATORS_H +#define RBDL_SPATIALALGEBRAOPERATORS_H + +#include +#include + +namespace RigidBodyDynamics { + +namespace Math { + +inline Matrix3d VectorCrossMatrix (const Vector3d &vector) { + return Matrix3d ( + 0., -vector[2], vector[1], + vector[2], 0., -vector[0], + -vector[1], vector[0], 0. + ); +} + +/** \brief Compact representation for Spatial Inertia. */ +struct RBDL_DLLAPI SpatialRigidBodyInertia { + SpatialRigidBodyInertia() : + m (0.), + h (Vector3d::Zero(3,1)), + Ixx (0.), Iyx(0.), Iyy(0.), Izx(0.), Izy(0.), Izz(0.) + {} + SpatialRigidBodyInertia ( + double mass, const Vector3d &com_mass, const Matrix3d &inertia) : + m (mass), h (com_mass), + Ixx (inertia(0,0)), + Iyx (inertia(1,0)), Iyy(inertia(1,1)), + Izx (inertia(2,0)), Izy(inertia(2,1)), Izz(inertia(2,2)) + { } + SpatialRigidBodyInertia (double m, const Vector3d &h, + const double &Ixx, + const double &Iyx, const double &Iyy, + const double &Izx, const double &Izy, const double &Izz + ) : + m (m), h (h), + Ixx (Ixx), + Iyx (Iyx), Iyy(Iyy), + Izx (Izx), Izy(Izy), Izz(Izz) + { } + + SpatialVector operator* (const SpatialVector &mv) { + Vector3d mv_lower (mv[3], mv[4], mv[5]); + + Vector3d res_upper = Vector3d ( + Ixx * mv[0] + Iyx * mv[1] + Izx * mv[2], + Iyx * mv[0] + Iyy * mv[1] + Izy * mv[2], + Izx * mv[0] + Izy * mv[1] + Izz * mv[2] + ) + h.cross(mv_lower); + Vector3d res_lower = m * mv_lower - h.cross (Vector3d (mv[0], mv[1], mv[2])); + + return SpatialVector ( + res_upper[0], res_upper[1], res_upper[2], + res_lower[0], res_lower[1], res_lower[2] + ); + } + + SpatialRigidBodyInertia operator+ (const SpatialRigidBodyInertia &rbi) { + return SpatialRigidBodyInertia ( + m + rbi.m, + h + rbi.h, + Ixx + rbi.Ixx, + Iyx + rbi.Iyx, Iyy + rbi.Iyy, + Izx + rbi.Izx, Izy + rbi.Izy, Izz + rbi.Izz + ); + } + + void createFromMatrix (const SpatialMatrix &Ic) { + m = Ic(3,3); + h.set (-Ic(1,5), Ic(0,5), -Ic(0,4)); + Ixx = Ic(0,0); + Iyx = Ic(1,0); Iyy = Ic(1,1); + Izx = Ic(2,0); Izy = Ic(2,1); Izz = Ic(2,2); + } + + SpatialMatrix toMatrix() const { + SpatialMatrix result; + result(0,0) = Ixx; result(0,1) = Iyx; result(0,2) = Izx; + result(1,0) = Iyx; result(1,1) = Iyy; result(1,2) = Izy; + result(2,0) = Izx; result(2,1) = Izy; result(2,2) = Izz; + + result.block<3,3>(0,3) = VectorCrossMatrix(h); + result.block<3,3>(3,0) = - VectorCrossMatrix(h); + result.block<3,3>(3,3) = Matrix3d::Identity(3,3) * m; + + return result; + } + + void setSpatialMatrix (SpatialMatrix &mat) const { + mat(0,0) = Ixx; mat(0,1) = Iyx; mat(0,2) = Izx; + mat(1,0) = Iyx; mat(1,1) = Iyy; mat(1,2) = Izy; + mat(2,0) = Izx; mat(2,1) = Izy; mat(2,2) = Izz; + + mat(3,0) = 0.; mat(3,1) = h[2]; mat(3,2) = -h[1]; + mat(4,0) = -h[2]; mat(4,1) = 0.; mat(4,2) = h[0]; + mat(5,0) = h[1]; mat(5,1) = -h[0]; mat(5,2) = 0.; + + mat(0,3) = 0.; mat(0,4) = -h[2]; mat(0,5) = h[1]; + mat(1,3) = h[2]; mat(1,4) = 0.; mat(1,5) = -h[0]; + mat(2,3) = -h[1]; mat(2,4) = h[0]; mat(2,5) = 0.; + + mat(3,3) = m; mat(3,4) = 0.; mat(3,5) = 0.; + mat(4,3) = 0.; mat(4,4) = m; mat(4,5) = 0.; + mat(5,3) = 0.; mat(5,4) = 0.; mat(5,5) = m; + } + + static SpatialRigidBodyInertia createFromMassComInertiaC (double mass, const Vector3d &com, const Matrix3d &inertia_C) { + SpatialRigidBodyInertia result; + result.m = mass; + result.h = com * mass; + Matrix3d I = inertia_C + VectorCrossMatrix (com) * VectorCrossMatrix(com).transpose() * mass; + result.Ixx = I(0,0); + result.Iyx = I(1,0); + result.Iyy = I(1,1); + result.Izx = I(2,0); + result.Izy = I(2,1); + result.Izz = I(2,2); + return result; + } + + /// Mass + double m; + /// Coordinates of the center of mass + Vector3d h; + /// Inertia expressed at the origin + double Ixx, Iyx, Iyy, Izx, Izy, Izz; +}; + +/** \brief Compact representation of spatial transformations. + * + * Instead of using a verbose 6x6 matrix, this structure only stores a 3x3 + * matrix and a 3-d vector to store spatial transformations. It also + * encapsulates efficient operations such as concatenations and + * transformation of spatial vectors. + */ +struct RBDL_DLLAPI SpatialTransform { + SpatialTransform() : + E (Matrix3d::Identity(3,3)), + r (Vector3d::Zero(3,1)) + {} + SpatialTransform (const Matrix3d &rotation, const Vector3d &translation) : + E (rotation), + r (translation) + {} + + /** Same as X * v. + * + * \returns (E * w, - E * rxw + E * v) + */ + SpatialVector apply (const SpatialVector &v_sp) { + Vector3d v_rxw ( + v_sp[3] - r[1]*v_sp[2] + r[2]*v_sp[1], + v_sp[4] - r[2]*v_sp[0] + r[0]*v_sp[2], + v_sp[5] - r[0]*v_sp[1] + r[1]*v_sp[0] + ); + return SpatialVector ( + E(0,0) * v_sp[0] + E(0,1) * v_sp[1] + E(0,2) * v_sp[2], + E(1,0) * v_sp[0] + E(1,1) * v_sp[1] + E(1,2) * v_sp[2], + E(2,0) * v_sp[0] + E(2,1) * v_sp[1] + E(2,2) * v_sp[2], + E(0,0) * v_rxw[0] + E(0,1) * v_rxw[1] + E(0,2) * v_rxw[2], + E(1,0) * v_rxw[0] + E(1,1) * v_rxw[1] + E(1,2) * v_rxw[2], + E(2,0) * v_rxw[0] + E(2,1) * v_rxw[1] + E(2,2) * v_rxw[2] + ); + } + + /** Same as X^T * f. + * + * \returns (E^T * n + rx * E^T * f, E^T * f) + */ + SpatialVector applyTranspose (const SpatialVector &f_sp) { + Vector3d E_T_f ( + E(0,0) * f_sp[3] + E(1,0) * f_sp[4] + E(2,0) * f_sp[5], + E(0,1) * f_sp[3] + E(1,1) * f_sp[4] + E(2,1) * f_sp[5], + E(0,2) * f_sp[3] + E(1,2) * f_sp[4] + E(2,2) * f_sp[5] + ); + + return SpatialVector ( + E(0,0) * f_sp[0] + E(1,0) * f_sp[1] + E(2,0) * f_sp[2] - r[2] * E_T_f[1] + r[1] * E_T_f[2], + E(0,1) * f_sp[0] + E(1,1) * f_sp[1] + E(2,1) * f_sp[2] + r[2] * E_T_f[0] - r[0] * E_T_f[2], + E(0,2) * f_sp[0] + E(1,2) * f_sp[1] + E(2,2) * f_sp[2] - r[1] * E_T_f[0] + r[0] * E_T_f[1], + E_T_f [0], + E_T_f [1], + E_T_f [2] + ); + } + + /** Same as X^* I X^{-1} + */ + SpatialRigidBodyInertia apply (const SpatialRigidBodyInertia &rbi) { + return SpatialRigidBodyInertia ( + rbi.m, + E * (rbi.h - rbi.m * r), + E * + ( + Matrix3d ( + rbi.Ixx, rbi.Iyx, rbi.Izx, + rbi.Iyx, rbi.Iyy, rbi.Izy, + rbi.Izx, rbi.Izy, rbi.Izz + ) + + VectorCrossMatrix (r) * VectorCrossMatrix (rbi.h) + + (VectorCrossMatrix(rbi.h - rbi.m * r) * VectorCrossMatrix (r)) + ) + * E.transpose() + ); + } + + /** Same as X^T I X + */ + SpatialRigidBodyInertia applyTranspose (const SpatialRigidBodyInertia &rbi) { + Vector3d E_T_mr = E.transpose() * rbi.h + rbi.m * r; + return SpatialRigidBodyInertia ( + rbi.m, + E_T_mr, + E.transpose() * + Matrix3d ( + rbi.Ixx, rbi.Iyx, rbi.Izx, + rbi.Iyx, rbi.Iyy, rbi.Izy, + rbi.Izx, rbi.Izy, rbi.Izz + ) * E + - VectorCrossMatrix(r) * VectorCrossMatrix (E.transpose() * rbi.h) + - VectorCrossMatrix (E_T_mr) * VectorCrossMatrix (r)); + } + + SpatialVector applyAdjoint (const SpatialVector &f_sp) { + Vector3d En_rxf = E * (Vector3d (f_sp[0], f_sp[1], f_sp[2]) - r.cross(Vector3d (f_sp[3], f_sp[4], f_sp[5]))); + // Vector3d En_rxf = E * (Vector3d (f_sp[0], f_sp[1], f_sp[2]) - r.cross(Eigen::Map (&(f_sp[3])))); + + return SpatialVector ( + En_rxf[0], + En_rxf[1], + En_rxf[2], + E(0,0) * f_sp[3] + E(0,1) * f_sp[4] + E(0,2) * f_sp[5], + E(1,0) * f_sp[3] + E(1,1) * f_sp[4] + E(1,2) * f_sp[5], + E(2,0) * f_sp[3] + E(2,1) * f_sp[4] + E(2,2) * f_sp[5] + ); + } + + SpatialMatrix toMatrix () const { + Matrix3d _Erx = + E * Matrix3d ( + 0., -r[2], r[1], + r[2], 0., -r[0], + -r[1], r[0], 0. + ); + SpatialMatrix result; + result.block<3,3>(0,0) = E; + result.block<3,3>(0,3) = Matrix3d::Zero(3,3); + result.block<3,3>(3,0) = -_Erx; + result.block<3,3>(3,3) = E; + + return result; + } + + SpatialMatrix toMatrixAdjoint () const { + Matrix3d _Erx = + E * Matrix3d ( + 0., -r[2], r[1], + r[2], 0., -r[0], + -r[1], r[0], 0. + ); + SpatialMatrix result; + result.block<3,3>(0,0) = E; + result.block<3,3>(0,3) = -_Erx; + result.block<3,3>(3,0) = Matrix3d::Zero(3,3); + result.block<3,3>(3,3) = E; + + return result; + } + + SpatialMatrix toMatrixTranspose () const { + Matrix3d _Erx = + E * Matrix3d ( + 0., -r[2], r[1], + r[2], 0., -r[0], + -r[1], r[0], 0. + ); + SpatialMatrix result; + result.block<3,3>(0,0) = E.transpose(); + result.block<3,3>(0,3) = -_Erx.transpose(); + result.block<3,3>(3,0) = Matrix3d::Zero(3,3); + result.block<3,3>(3,3) = E.transpose(); + + return result; + } + + SpatialTransform inverse() const { + return SpatialTransform ( + E.transpose(), + - E * r + ); + } + + SpatialTransform operator* (const SpatialTransform &XT) const { + return SpatialTransform (E * XT.E, XT.r + XT.E.transpose() * r); + } + + void operator*= (const SpatialTransform &XT) { + r = XT.r + XT.E.transpose() * r; + E *= XT.E; + } + + Matrix3d E; + Vector3d r; +}; + +inline std::ostream& operator<<(std::ostream& output, const SpatialRigidBodyInertia &rbi) { + output << "rbi.m = " << rbi.m << std::endl; + output << "rbi.h = " << rbi.h.transpose(); + output << "rbi.Ixx = " << rbi.Ixx << std::endl; + output << "rbi.Iyx = " << rbi.Iyx << " rbi.Iyy = " << rbi.Iyy << std::endl; + output << "rbi.Izx = " << rbi.Izx << " rbi.Izy = " << rbi.Izy << " rbi.Izz = " << rbi.Izz << std::endl; + return output; +} + +inline std::ostream& operator<<(std::ostream& output, const SpatialTransform &X) { + output << "X.E = " << std::endl << X.E << std::endl; + output << "X.r = " << X.r.transpose(); + return output; +} + +inline SpatialTransform Xrot (double angle_rad, const Vector3d &axis) { + double s, c; + s = sin(angle_rad); + c = cos(angle_rad); + + return SpatialTransform ( + Matrix3d ( + axis[0] * axis[0] * (1.0f - c) + c, + axis[1] * axis[0] * (1.0f - c) + axis[2] * s, + axis[0] * axis[2] * (1.0f - c) - axis[1] * s, + + axis[0] * axis[1] * (1.0f - c) - axis[2] * s, + axis[1] * axis[1] * (1.0f - c) + c, + axis[1] * axis[2] * (1.0f - c) + axis[0] * s, + + axis[0] * axis[2] * (1.0f - c) + axis[1] * s, + axis[1] * axis[2] * (1.0f - c) - axis[0] * s, + axis[2] * axis[2] * (1.0f - c) + c + + ), + Vector3d (0., 0., 0.) + ); +} + +inline SpatialTransform Xrotx (const double &xrot) { + double s, c; + s = sin (xrot); + c = cos (xrot); + return SpatialTransform ( + Matrix3d ( + 1., 0., 0., + 0., c, s, + 0., -s, c + ), + Vector3d (0., 0., 0.) + ); +} + +inline SpatialTransform Xroty (const double &yrot) { + double s, c; + s = sin (yrot); + c = cos (yrot); + return SpatialTransform ( + Matrix3d ( + c, 0., -s, + 0., 1., 0., + s, 0., c + ), + Vector3d (0., 0., 0.) + ); +} + +inline SpatialTransform Xrotz (const double &zrot) { + double s, c; + s = sin (zrot); + c = cos (zrot); + return SpatialTransform ( + Matrix3d ( + c, s, 0., + -s, c, 0., + 0., 0., 1. + ), + Vector3d (0., 0., 0.) + ); +} + +inline SpatialTransform Xtrans (const Vector3d &r) { + return SpatialTransform ( + Matrix3d::Identity(3,3), + r + ); +} + +inline SpatialMatrix crossm (const SpatialVector &v) { + return SpatialMatrix ( + 0, -v[2], v[1], 0, 0, 0, + v[2], 0, -v[0], 0, 0, 0, + -v[1], v[0], 0, 0, 0, 0, + 0, -v[5], v[4], 0, -v[2], v[1], + v[5], 0, -v[3], v[2], 0, -v[0], + -v[4], v[3], 0, -v[1], v[0], 0 + ); +} + +inline SpatialVector crossm (const SpatialVector &v1, const SpatialVector &v2) { + return SpatialVector ( + -v1[2] * v2[1] + v1[1] * v2[2], + v1[2] * v2[0] - v1[0] * v2[2], + -v1[1] * v2[0] + v1[0] * v2[1], + -v1[5] * v2[1] + v1[4] * v2[2] - v1[2] * v2[4] + v1[1] * v2[5], + v1[5] * v2[0] - v1[3] * v2[2] + v1[2] * v2[3] - v1[0] * v2[5], + -v1[4] * v2[0] + v1[3] * v2[1] - v1[1] * v2[3] + v1[0] * v2[4] + ); +} + +inline SpatialMatrix crossf (const SpatialVector &v) { + return SpatialMatrix ( + 0, -v[2], v[1], 0, -v[5], v[4], + v[2], 0, -v[0], v[5], 0, -v[3], + -v[1], v[0], 0, -v[4], v[3], 0, + 0, 0, 0, 0, -v[2], v[1], + 0, 0, 0, v[2], 0, -v[0], + 0, 0, 0, -v[1], v[0], 0 + ); +} + +inline SpatialVector crossf (const SpatialVector &v1, const SpatialVector &v2) { + return SpatialVector ( + -v1[2] * v2[1] + v1[1] * v2[2] - v1[5] * v2[4] + v1[4] * v2[5], + v1[2] * v2[0] - v1[0] * v2[2] + v1[5] * v2[3] - v1[3] * v2[5], + -v1[1] * v2[0] + v1[0] * v2[1] - v1[4] * v2[3] + v1[3] * v2[4], + - v1[2] * v2[4] + v1[1] * v2[5], + + v1[2] * v2[3] - v1[0] * v2[5], + - v1[1] * v2[3] + v1[0] * v2[4] + ); +} + +} /* Math */ + +} /* RigidBodyDynamics */ + +/* RBDL_SPATIALALGEBRAOPERATORS_H*/ +#endif diff --git a/3rdparty/rbdl/include/rbdl/compileassert.h b/3rdparty/rbdl/include/rbdl/compileassert.h new file mode 100644 index 0000000..613006d --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/compileassert.h @@ -0,0 +1,46 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_COMPILE_ASSERT_H +#define RBDL_COMPILE_ASSERT_H + +/* + * This is a simple compile time assertion tool taken from: + * http://blogs.msdn.com/b/abhinaba/archive/2008/10/27/c-c-compile-time-asserts.aspx + * written by Abhinaba Basu! + * + * Thanks! + */ + +#ifdef __cplusplus + +#define JOIN( X, Y ) JOIN2(X,Y) +#define JOIN2( X, Y ) X##Y + +namespace custom_static_assert +{ +template struct STATIC_ASSERT_FAILURE; +template <> struct STATIC_ASSERT_FAILURE { enum { value = 1 }; }; + +template struct custom_static_assert_test{}; +} + +#define COMPILE_ASSERT(x) \ + typedef ::custom_static_assert::custom_static_assert_test<\ +sizeof(::custom_static_assert::STATIC_ASSERT_FAILURE< (bool)( x ) >)>\ +JOIN(_custom_static_assert_typedef, __LINE__) + +#else // __cplusplus + +#define COMPILE_ASSERT(x) extern int __dummy[(int)x] + +#endif // __cplusplus + +#define VERIFY_EXPLICIT_CAST(from, to) COMPILE_ASSERT(sizeof(from) == sizeof(to)) + +// RBDL_COMPILE_ASSERT_H_ +#endif diff --git a/3rdparty/rbdl/include/rbdl/rbdl.h b/3rdparty/rbdl/include/rbdl/rbdl.h new file mode 100644 index 0000000..22505bc --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/rbdl.h @@ -0,0 +1,69 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_H +#define RBDL_H + +#include "rbdl/rbdl_math.h" +#include "rbdl/rbdl_mathutils.h" + +#include "rbdl/Logging.h" + +#include "rbdl/Body.h" +#include "rbdl/Model.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Joint.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Constraints.h" + +#include "rbdl/rbdl_utils.h" + +/** \page api_version_checking_page API Changes + * @{ + * + * This documentation was created for API version 2.2.0. + * + * Here is a list of changes introduced by the different versions and what + * adjustements have to be made to migrate. + * + * \include api_changes.txt + */ + +/** Returns the API version at compile time of the library. */ +RBDL_DLLAPI int rbdl_get_api_version(); + +/** Ensures whether the RBDL library we are linking against is compatible + * with the the version we have from rbdl.h. + * + * To perform the check run: + * \code + * rbdl_check_api_version(API_VERSION); + * \endcode + * + * This function will abort if compatibility is not met or warn if you run + * a version that might not be entirely compatible. + * + * In most cases you want to specify a specific version to ensure you are + * using a compatible version. To do so replace API_VERSION by a + * value of the form 0xAABBCC where AA is the major, BB the minor, and CC + * the patch version in hex-format, e.g: + * + * \code + * rbdl_check_api_version(0x020A0C); + * \endcode + * + * Would abort if the API major version is not 2 (= 0x02), warn if the + * linked minor version is not 10 (= 0x0A). The patch version 12 (= 0x12) + * does not have an influence on compatibility. + */ +RBDL_DLLAPI void rbdl_check_api_version(int version); + +/** Prints version information to standard output */ +RBDL_DLLAPI void rbdl_print_version(); + +/* RBDL_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/rbdl_config.h.cmake b/3rdparty/rbdl/include/rbdl/rbdl_config.h.cmake new file mode 100644 index 0000000..2256727 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/rbdl_config.h.cmake @@ -0,0 +1,74 @@ +/* +* RBDL - Rigid Body Dynamics Library +* Copyright (c) 2011-2012 Martin Felis +* +* Licensed under the zlib license. See LICENSE for more details. +*/ + +#ifndef RBDL_CONFIG_H +#define RBDL_CONFIG_H + +#define RBDL_API_VERSION (@RBDL_VERSION_MAJOR@ << 16) + (@RBDL_VERSION_MINOR@ << 8) + @RBDL_VERSION_PATCH@ + +#cmakedefine RBDL_USE_SIMPLE_MATH +#cmakedefine RBDL_ENABLE_LOGGING +#cmakedefine RBDL_BUILD_REVISION "@RBDL_BUILD_REVISION@" +#cmakedefine RBDL_BUILD_TYPE "@RBDL_BUILD_TYPE@" +#cmakedefine RBDL_BUILD_BRANCH "@RBDL_BUILD_BRANCH@" +#cmakedefine RBDL_BUILD_ADDON_LUAMODEL +#cmakedefine RBDL_BUILD_ADDON_URDFREADER +#cmakedefine RBDL_BUILD_STATIC +#cmakedefine RBDL_USE_ROS_URDF_LIBRARY + +/* compatibility defines */ +#ifdef _WIN32 +#define __func__ __FUNCTION__ +#define M_PI 3.1415926535897932384 +#endif + +// Handle portable symbol export. +// Defining manually which symbol should be exported is required +// under Windows whether MinGW or MSVC is used. +// +// The headers then have to be able to work in two different modes: +// - dllexport when one is building the library, +// - dllimport for clients using the library. +// +// On Linux, set the visibility accordingly. If C++ symbol visibility +// is handled by the compiler, see: http://gcc.gnu.org/wiki/Visibility +# if defined _WIN32 || defined __CYGWIN__ +// On Microsoft Windows, use dllimport and dllexport to tag symbols. +# define RBDL_DLLIMPORT __declspec(dllimport) +# define RBDL_DLLEXPORT __declspec(dllexport) +# define RBDL_DLLLOCAL +# else +// On Linux, for GCC >= 4, tag symbols using GCC extension. +# if __GNUC__ >= 4 +# define RBDL_DLLIMPORT __attribute__ ((visibility("default"))) +# define RBDL_DLLEXPORT __attribute__ ((visibility("default"))) +# define RBDL_DLLLOCAL __attribute__ ((visibility("hidden"))) +# else +// Otherwise (GCC < 4 or another compiler is used), export everything. +# define RBDL_DLLIMPORT +# define RBDL_DLLEXPORT +# define RBDL_DLLLOCAL +# endif // __GNUC__ >= 4 +# endif // defined _WIN32 || defined __CYGWIN__ + +# ifdef RBDL_BUILD_STATIC +// If one is using the library statically, get rid of +// extra information. +# define RBDL_DLLAPI +# define RBDL_LOCAL +# else +// Depending on whether one is building or using the +// library define DLLAPI to import or export. +# ifdef rbdl_EXPORTS +# define RBDL_DLLAPI RBDL_DLLEXPORT +# else +# define RBDL_DLLAPI RBDL_DLLIMPORT +# endif // RBDL_EXPORTS +# define RBDL_LOCAL RBDL_DLLLOCAL +# endif // RBDL_BUILD_STATIC + +#endif diff --git a/3rdparty/rbdl/include/rbdl/rbdl_eigenmath.h b/3rdparty/rbdl/include/rbdl/rbdl_eigenmath.h new file mode 100644 index 0000000..5d7c83d --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/rbdl_eigenmath.h @@ -0,0 +1,236 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_EIGENMATH_H +#define RBDL_EIGENMATH_H + +/* Exporting templated symbols is tricky when using MSVC. The following lines + * causes the classes in this file not to be explicitly exported. Instead + * they are already implicitly exported. + */ +#if defined(WIN32) && defined(rbdl_EXPORTS) +#define RBDL_TEMPLATE_DLLAPI +#else +#define RBDL_TEMPLATE_DLLAPI RBDL_DLLAPI +#endif + +class RBDL_TEMPLATE_DLLAPI Vector3_t : public Eigen::Vector3d +{ + public: + typedef Eigen::Vector3d Base; + + template + Vector3_t(const Eigen::MatrixBase& other) + : Eigen::Vector3d(other) + {} + + template + Vector3_t& operator=(const Eigen::MatrixBase& other) + { + this->Base::operator=(other); + return *this; + } + + EIGEN_STRONG_INLINE Vector3_t() + {} + + EIGEN_STRONG_INLINE Vector3_t( + const double& v0, const double& v1, const double& v2 + ) + { + Base::_check_template_params(); + + (*this) << v0, v1, v2; + } + + void set(const double& v0, const double& v1, const double& v2) + { + Base::_check_template_params(); + + (*this) << v0, v1, v2; + } +}; + +class RBDL_TEMPLATE_DLLAPI Matrix3_t : public Eigen::Matrix3d +{ + public: + typedef Eigen::Matrix3d Base; + + template + Matrix3_t(const Eigen::MatrixBase& other) + : Eigen::Matrix3d(other) + {} + + template + Matrix3_t& operator=(const Eigen::MatrixBase& other) + { + this->Base::operator=(other); + return *this; + } + + EIGEN_STRONG_INLINE Matrix3_t() + {} + + EIGEN_STRONG_INLINE Matrix3_t( + const double& m00, const double& m01, const double& m02, + const double& m10, const double& m11, const double& m12, + const double& m20, const double& m21, const double& m22 + ) + { + Base::_check_template_params(); + + (*this) + << m00, m01, m02, + m10, m11, m12, + m20, m21, m22 + ; + } +}; + +class RBDL_TEMPLATE_DLLAPI Vector4_t : public Eigen::Vector4d +{ + public: + typedef Eigen::Vector4d Base; + + template + Vector4_t(const Eigen::MatrixBase& other) + : Eigen::Vector4d(other) + {} + + template + Vector4_t& operator=(const Eigen::MatrixBase& other) + { + this->Base::operator=(other); + return *this; + } + + EIGEN_STRONG_INLINE Vector4_t() + {} + + EIGEN_STRONG_INLINE Vector4_t( + const double& v0, const double& v1, const double& v2, const double& v3 + ) + { + Base::_check_template_params(); + + (*this) << v0, v1, v2, v3; + } + + void set(const double& v0, const double& v1, const double& v2, const double& v3) + { + Base::_check_template_params(); + + (*this) << v0, v1, v2, v3; + } +}; + +class RBDL_TEMPLATE_DLLAPI SpatialVector_t : public Eigen::Matrix +{ + public: + typedef Eigen::Matrix Base; + + template + SpatialVector_t(const Eigen::MatrixBase& other) + : Eigen::Matrix(other) + {} + + template + SpatialVector_t& operator=(const Eigen::MatrixBase& other) + { + this->Base::operator=(other); + return *this; + } + + EIGEN_STRONG_INLINE SpatialVector_t() + {} + + EIGEN_STRONG_INLINE SpatialVector_t( + const double& v0, const double& v1, const double& v2, + const double& v3, const double& v4, const double& v5 + ) + { + Base::_check_template_params(); + + (*this) << v0, v1, v2, v3, v4, v5; + } + + void set( + const double& v0, const double& v1, const double& v2, + const double& v3, const double& v4, const double& v5 + ) + { + Base::_check_template_params(); + + (*this) << v0, v1, v2, v3, v4, v5; + } +}; + +class RBDL_TEMPLATE_DLLAPI SpatialMatrix_t : public Eigen::Matrix +{ + public: + typedef Eigen::Matrix Base; + + template + SpatialMatrix_t(const Eigen::MatrixBase& other) + : Eigen::Matrix(other) + {} + + template + SpatialMatrix_t& operator=(const Eigen::MatrixBase& other) + { + this->Base::operator=(other); + return *this; + } + + EIGEN_STRONG_INLINE SpatialMatrix_t() + {} + + EIGEN_STRONG_INLINE SpatialMatrix_t( + const Scalar& m00, const Scalar& m01, const Scalar& m02, const Scalar& m03, const Scalar& m04, const Scalar& m05, + const Scalar& m10, const Scalar& m11, const Scalar& m12, const Scalar& m13, const Scalar& m14, const Scalar& m15, + const Scalar& m20, const Scalar& m21, const Scalar& m22, const Scalar& m23, const Scalar& m24, const Scalar& m25, + const Scalar& m30, const Scalar& m31, const Scalar& m32, const Scalar& m33, const Scalar& m34, const Scalar& m35, + const Scalar& m40, const Scalar& m41, const Scalar& m42, const Scalar& m43, const Scalar& m44, const Scalar& m45, + const Scalar& m50, const Scalar& m51, const Scalar& m52, const Scalar& m53, const Scalar& m54, const Scalar& m55 + ) + { + Base::_check_template_params(); + + (*this) + << m00, m01, m02, m03, m04, m05 + , m10, m11, m12, m13, m14, m15 + , m20, m21, m22, m23, m24, m25 + , m30, m31, m32, m33, m34, m35 + , m40, m41, m42, m43, m44, m45 + , m50, m51, m52, m53, m54, m55 + ; + } + + void set( + const Scalar& m00, const Scalar& m01, const Scalar& m02, const Scalar& m03, const Scalar& m04, const Scalar& m05, + const Scalar& m10, const Scalar& m11, const Scalar& m12, const Scalar& m13, const Scalar& m14, const Scalar& m15, + const Scalar& m20, const Scalar& m21, const Scalar& m22, const Scalar& m23, const Scalar& m24, const Scalar& m25, + const Scalar& m30, const Scalar& m31, const Scalar& m32, const Scalar& m33, const Scalar& m34, const Scalar& m35, + const Scalar& m40, const Scalar& m41, const Scalar& m42, const Scalar& m43, const Scalar& m44, const Scalar& m45, + const Scalar& m50, const Scalar& m51, const Scalar& m52, const Scalar& m53, const Scalar& m54, const Scalar& m55 + ) + { + Base::_check_template_params(); + + (*this) + << m00, m01, m02, m03, m04, m05 + , m10, m11, m12, m13, m14, m15 + , m20, m21, m22, m23, m24, m25 + , m30, m31, m32, m33, m34, m35 + , m40, m41, m42, m43, m44, m45 + , m50, m51, m52, m53, m54, m55 + ; + } +}; + +/* _RBDL_EIGENMATH_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/rbdl_math.h b/3rdparty/rbdl/include/rbdl/rbdl_math.h new file mode 100644 index 0000000..9b72736 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/rbdl_math.h @@ -0,0 +1,82 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_MATH_H +#define RBDL_MATH_H + +#include "rbdl/rbdl_config.h" + +#ifdef RBDL_USE_SIMPLE_MATH +#include "rbdl/SimpleMath/SimpleMathFixed.h" +#include "rbdl/SimpleMath/SimpleMathDynamic.h" +#include "rbdl/SimpleMath/SimpleMathMixed.h" +#include "rbdl/SimpleMath/SimpleMathQR.h" +#include "rbdl/SimpleMath/SimpleMathCholesky.h" +#include "rbdl/SimpleMath/SimpleMathCommaInitializer.h" +#include "rbdl/SimpleMath/SimpleMathMap.h" +#include + +typedef SimpleMath::Fixed::Matrix Vector3_t; +typedef SimpleMath::Fixed::Matrix Matrix3_t; +typedef SimpleMath::Fixed::Matrix Vector4_t; + +typedef SimpleMath::Fixed::Matrix SpatialVector_t; +typedef SimpleMath::Fixed::Matrix SpatialMatrix_t; + +typedef SimpleMath::Fixed::Matrix Matrix63_t; +typedef SimpleMath::Fixed::Matrix Matrix43_t; + +typedef SimpleMath::Dynamic::Matrix MatrixN_t; +typedef SimpleMath::Dynamic::Matrix VectorN_t; + +#else +#include +#include +#include + +#include "rbdl/rbdl_eigenmath.h" + +typedef Eigen::Matrix Matrix63_t; +typedef Eigen::Matrix Matrix43_t; + +typedef Eigen::VectorXd VectorN_t; +typedef Eigen::MatrixXd MatrixN_t; +#endif + +namespace RigidBodyDynamics { + +/** \brief Math types such as vectors and matrices and utility functions. */ +namespace Math { +typedef Vector3_t Vector3d; +typedef Vector4_t Vector4d; +typedef Matrix3_t Matrix3d; +typedef SpatialVector_t SpatialVector; +typedef SpatialMatrix_t SpatialMatrix; +typedef Matrix63_t Matrix63; +typedef Matrix43_t Matrix43; +typedef VectorN_t VectorNd; +typedef MatrixN_t MatrixNd; +} /* Math */ + +} /* RigidBodyDynamics */ + +#include "rbdl/Quaternion.h" +#include "rbdl/SpatialAlgebraOperators.h" + +// If we use Eigen3 we have to create specializations of the STL +// std::vector such that the alignment is done properly. +#ifndef RBDL_USE_SIMPLE_MATH + EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Math::SpatialVector) + EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Math::SpatialMatrix) + EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Math::Matrix63) + EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Math::Matrix43) + EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Math::SpatialTransform) + EIGEN_DEFINE_STL_VECTOR_SPECIALIZATION(RigidBodyDynamics::Math::SpatialRigidBodyInertia) +#endif + + /* RBDL_MATH_H_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/rbdl_mathutils.h b/3rdparty/rbdl/include/rbdl/rbdl_mathutils.h new file mode 100644 index 0000000..c11398d --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/rbdl_mathutils.h @@ -0,0 +1,265 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_MATHUTILS_H +#define RBDL_MATHUTILS_H + +#include +#include + +#include "rbdl/rbdl_math.h" + +namespace RigidBodyDynamics { +struct Model; + +namespace Math { + +/** \brief Available solver methods for the linear systems. + * + * Please note that these methods are only available when Eigen3 is used. + * When the math library SimpleMath is used it will always use a slow + * column pivoting gauss elimination. + */ +enum RBDL_DLLAPI LinearSolver { + LinearSolverUnknown = 0, + LinearSolverPartialPivLU, + LinearSolverColPivHouseholderQR, + LinearSolverHouseholderQR, + LinearSolverLLT, + LinearSolverLast, +}; + +extern RBDL_DLLAPI Vector3d Vector3dZero; +extern RBDL_DLLAPI Matrix3d Matrix3dIdentity; +extern RBDL_DLLAPI Matrix3d Matrix3dZero; + +RBDL_DLLAPI inline VectorNd VectorFromPtr (unsigned int n, double *ptr) { + // TODO: use memory mapping operators for Eigen + VectorNd result (n); + + for (unsigned int i = 0; i < n; i++) { + result[i] = ptr[i]; + } + + return result; +} + +RBDL_DLLAPI inline MatrixNd MatrixFromPtr (unsigned int rows, unsigned int cols, double *ptr, bool row_major = true) { + MatrixNd result (rows, cols); + + if (row_major) { + for (unsigned int i = 0; i < rows; i++) { + for (unsigned int j = 0; j < cols; j++) { + result(i,j) = ptr[i * cols + j]; + } + } + } else { + for (unsigned int i = 0; i < rows; i++) { + for (unsigned int j = 0; j < cols; j++) { + result(i,j) = ptr[i + j * rows]; + } + } + } + + return result; +} + +/// \brief Solves a linear system using gaussian elimination with pivoting +RBDL_DLLAPI bool LinSolveGaussElimPivot (MatrixNd A, VectorNd b, VectorNd &x); + +// \todo write test +RBDL_DLLAPI void SpatialMatrixSetSubmatrix(SpatialMatrix &dest, unsigned int row, unsigned int col, const Matrix3d &matrix); + +RBDL_DLLAPI bool SpatialMatrixCompareEpsilon (const SpatialMatrix &matrix_a, + const SpatialMatrix &matrix_b, double epsilon); +RBDL_DLLAPI bool SpatialVectorCompareEpsilon (const SpatialVector &vector_a, + const SpatialVector &vector_b, double epsilon); + +/** \brief Translates the inertia matrix to a new center. */ +RBDL_DLLAPI Matrix3d parallel_axis (const Matrix3d &inertia, double mass, const Vector3d &com); + +/** \brief Creates a transformation of a linear displacement + * + * This can be used to specify the translation to the joint center when + * adding a body to a model. See also section 2.8 in RBDA. + * + * \note The transformation returned is for motions. For a transformation for forces + * \note one has to conjugate the matrix. + * + * \param displacement The displacement as a 3D vector + */ +RBDL_DLLAPI SpatialMatrix Xtrans_mat (const Vector3d &displacement); + +/** \brief Creates a rotational transformation around the Z-axis + * + * Creates a rotation around the current Z-axis by the given angle + * (specified in radians). + * + * \param zrot Rotation angle in radians. + */ +RBDL_DLLAPI SpatialMatrix Xrotz_mat (const double &zrot); + +/** \brief Creates a rotational transformation around the Y-axis + * + * Creates a rotation around the current Y-axis by the given angle + * (specified in radians). + * + * \param yrot Rotation angle in radians. + */ +RBDL_DLLAPI SpatialMatrix Xroty_mat (const double &yrot); + +/** \brief Creates a rotational transformation around the X-axis + * + * Creates a rotation around the current X-axis by the given angle + * (specified in radians). + * + * \param xrot Rotation angle in radians. + */ +RBDL_DLLAPI SpatialMatrix Xrotx_mat (const double &xrot); + +/** \brief Creates a spatial transformation for given parameters + * + * Creates a transformation to a coordinate system that is first rotated + * and then translated. + * + * \param displacement The displacement to the new origin + * \param zyx_euler The orientation of the new coordinate system, specifyed + * by ZYX-Euler angles. + */ +RBDL_DLLAPI SpatialMatrix XtransRotZYXEuler (const Vector3d &displacement, const Vector3d &zyx_euler); + +RBDL_DLLAPI inline Matrix3d rotx (const double &xrot) { + double s, c; + s = sin (xrot); + c = cos (xrot); + return Matrix3d ( + 1., 0., 0., + 0., c, s, + 0., -s, c + ); +} + +RBDL_DLLAPI inline Matrix3d roty (const double &yrot) { + double s, c; + s = sin (yrot); + c = cos (yrot); + return Matrix3d ( + c, 0., -s, + 0., 1., 0., + s, 0., c + ); +} + +RBDL_DLLAPI inline Matrix3d rotz (const double &zrot) { + double s, c; + s = sin (zrot); + c = cos (zrot); + return Matrix3d ( + c, s, 0., + -s, c, 0., + 0., 0., 1. + ); +} + +RBDL_DLLAPI inline Matrix3d rotxdot (const double &x, const double &xdot) { + double s, c; + s = sin (x); + c = cos (x); + return Matrix3d ( + 0., 0., 0., + 0., -s * xdot, c * xdot, + 0., -c * xdot,-s * xdot + ); +} + +RBDL_DLLAPI inline Matrix3d rotydot (const double &y, const double &ydot) { + double s, c; + s = sin (y); + c = cos (y); + return Matrix3d ( + -s * ydot, 0., - c * ydot, + 0., 0., 0., + c * ydot, 0., - s * ydot + ); +} + +RBDL_DLLAPI inline Matrix3d rotzdot (const double &z, const double &zdot) { + double s, c; + s = sin (z); + c = cos (z); + return Matrix3d ( + -s * zdot, c * zdot, 0., + -c * zdot, -s * zdot, 0., + 0., 0., 0. + ); +} + +RBDL_DLLAPI inline Vector3d angular_velocity_from_angle_rates (const Vector3d &zyx_angles, const Vector3d &zyx_angle_rates) { + double sy = sin(zyx_angles[1]); + double cy = cos(zyx_angles[1]); + double sx = sin(zyx_angles[2]); + double cx = cos(zyx_angles[2]); + + return Vector3d ( + zyx_angle_rates[2] - sy * zyx_angle_rates[0], + cx * zyx_angle_rates[1] + sx * cy * zyx_angle_rates[0], + -sx * zyx_angle_rates[1] + cx * cy * zyx_angle_rates[0] + ); +} + +RBDL_DLLAPI inline Vector3d global_angular_velocity_from_rates (const Vector3d &zyx_angles, const Vector3d &zyx_rates) { + Matrix3d RzT = rotz(zyx_angles[0]).transpose(); + Matrix3d RyT = roty(zyx_angles[1]).transpose(); + + return Vector3d ( + Vector3d (0., 0., zyx_rates[0]) + + RzT * Vector3d (0., zyx_rates[1], 0.) + + RzT * RyT * Vector3d (zyx_rates[2], 0., 0.) + ); +} + +RBDL_DLLAPI inline Vector3d angular_acceleration_from_angle_rates (const Vector3d &zyx_angles, const Vector3d &zyx_angle_rates, const Vector3d &zyx_angle_rates_dot) { + double sy = sin(zyx_angles[1]); + double cy = cos(zyx_angles[1]); + double sx = sin(zyx_angles[2]); + double cx = cos(zyx_angles[2]); + double xdot = zyx_angle_rates[2]; + double ydot = zyx_angle_rates[1]; + double zdot = zyx_angle_rates[0]; + double xddot = zyx_angle_rates_dot[2]; + double yddot = zyx_angle_rates_dot[1]; + double zddot = zyx_angle_rates_dot[0]; + + return Vector3d ( + xddot - (cy * ydot * zdot + sy * zddot), + -sx * xdot * ydot + cx * yddot + cx * xdot * cy * zdot + sx * ( - sy * ydot * zdot + cy * zddot), + -cx * xdot * ydot - sx * yddot - sx * xdot * cy * zdot + cx * ( - sy * ydot * zdot + cy * zddot) + ); +} + +RBDL_DLLAPI +void SparseFactorizeLTL (Model &model, Math::MatrixNd &H); + +RBDL_DLLAPI +void SparseMultiplyHx (Model &model, Math::MatrixNd &L); + +RBDL_DLLAPI +void SparseMultiplyLx (Model &model, Math::MatrixNd &L); +RBDL_DLLAPI +void SparseMultiplyLTx (Model &model, Math::MatrixNd &L); + +RBDL_DLLAPI +void SparseSolveLx (Model &model, Math::MatrixNd &L, Math::VectorNd &x); +RBDL_DLLAPI +void SparseSolveLTx (Model &model, Math::MatrixNd &L, Math::VectorNd &x); + +} /* Math */ + +} /* RigidBodyDynamics */ + +/* RBDL_MATHUTILS_H */ +#endif diff --git a/3rdparty/rbdl/include/rbdl/rbdl_utils.h b/3rdparty/rbdl/include/rbdl/rbdl_utils.h new file mode 100644 index 0000000..6743fa2 --- /dev/null +++ b/3rdparty/rbdl/include/rbdl/rbdl_utils.h @@ -0,0 +1,54 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#ifndef RBDL_UTILS_H +#define RBDL_UTILS_H + +#include +#include +#include + +namespace RigidBodyDynamics { + +struct Model; + +/** \brief Namespace that contains optional helper functions */ +namespace Utils { +/** \brief Creates a human readable overview of the model. */ +RBDL_DLLAPI std::string GetModelHierarchy (const Model &model); +/** \brief Creates a human readable overview of the Degrees of Freedom. */ +RBDL_DLLAPI std::string GetModelDOFOverview (const Model &model); +/** \brief Creates a human readable overview of the locations of all bodies that have names. */ +RBDL_DLLAPI std::string GetNamedBodyOriginsOverview (Model &model); + +/** \brief Computes the Center of Mass (COM) and optionally its linear velocity. + * + * When only interested in computing the location of the COM you can use + * NULL as value for com_velocity. + * + * \param model The model for which we want to compute the COM + * \param q The current joint positions + * \param qdot The current joint velocities + * \param mass (output) total mass of the model + * \param com (output) location of the Center of Mass of the model in base coordinates + * \param com_velocity (optional output) linear velocity of the COM in base coordinates + * \param angular_momentum (optional output) angular momentum of the model at the COM in base coordinates + * \param update_kinematics (optional input) whether the kinematics should be updated (defaults to true) + */ +RBDL_DLLAPI void CalcCenterOfMass (Model &model, const Math::VectorNd &q, const Math::VectorNd &qdot, double &mass, Math::Vector3d &com, Math::Vector3d *com_velocity = NULL, Math::Vector3d *angular_momentum = NULL, bool update_kinematics = true); + +/** \brief Computes the potential energy of the full model. */ +RBDL_DLLAPI double CalcPotentialEnergy (Model &model, const Math::VectorNd &q, bool update_kinematics = true); + +/** \brief Computes the kinetic energy of the full model. */ +RBDL_DLLAPI double CalcKineticEnergy (Model &model, const Math::VectorNd &q, const Math::VectorNd &qdot, bool update_kinematics = true); +} + +} + +/* RBDL_UTILS_H */ +#endif diff --git a/3rdparty/rbdl/python/CMakeLists.txt b/3rdparty/rbdl/python/CMakeLists.txt new file mode 100644 index 0000000..7d34106 --- /dev/null +++ b/3rdparty/rbdl/python/CMakeLists.txt @@ -0,0 +1,66 @@ +FIND_PROGRAM ( PYTHON "python" ) + +CMAKE_POLICY(SET CMP0048 NEW) + +SET (Python_ADDITIONAL_VERSIONS 2.7) + +INCLUDE ( UseCython ) + +FILE( COPY "rbdl.pyx" "crbdl.pxd" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + +CONFIGURE_FILE ( + ${CMAKE_CURRENT_SOURCE_DIR}/setup.py.cmake + ${CMAKE_CURRENT_BINARY_DIR}/setup.py + ) + +# Process the rbdl-wrapper.pyx to generate rbdl.pyx +ADD_CUSTOM_COMMAND ( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/rbdl.pyx + COMMAND ${PYTHON} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/wrappergen.py + ${CMAKE_CURRENT_SOURCE_DIR}/rbdl-wrapper.pyx + ${CMAKE_CURRENT_SOURCE_DIR}/rbdl.pyx + COMMENT "Generating rbdl.pyx from rbdl-wrapper.pyx" + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/rbdl-wrapper.pyx ${CMAKE_CURRENT_SOURCE_DIR}/wrappergen.py + ) + +# Enable C++11 (or C++0x for older compilers) +include(CheckCXXCompilerFlag) +CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) +CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +if(COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") +else() + message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") +endif() + +# If the pyx file is a C++ file, we should specify that here. +set_source_files_properties( ${CMAKE_CURRENT_SOURCE_DIR}/rbdl.pyx + PROPERTIES CYTHON_IS_CXX TRUE ) + +# Multi-file cython modules do not appear to be working at the moment. +cython_add_module( rbdl-python ${CMAKE_CURRENT_SOURCE_DIR}/rbdl.pyx ) + +#SET_TARGET_PROPERTIES ( rbdl-python PROPERTIES PREFIX "") +SET_TARGET_PROPERTIES ( rbdl-python PROPERTIES OUTPUT_NAME "rbdl") + +INCLUDE_DIRECTORIES ( + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/python + ${PROJECT_SOURCE_DIR} + ) + +TARGET_LINK_LIBRARIES (rbdl-python rbdl ) + +IF (RBDL_BUILD_ADDON_LUAMODEL) + TARGET_LINK_LIBRARIES (rbdl-python rbdl_luamodel ) +ENDIF() + +IF (RBDL_BUILD_ADDON_URDFREADER) + TARGET_LINK_LIBRARIES (rbdl-python rbdl_urdfreader ) +ENDIF() + +INSTALL ( TARGETS rbdl-python + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/python2.7/site-packages/ +) diff --git a/3rdparty/rbdl/python/README.md b/3rdparty/rbdl/python/README.md new file mode 100644 index 0000000..06c2d39 --- /dev/null +++ b/3rdparty/rbdl/python/README.md @@ -0,0 +1,53 @@ +# Python wrapper for RBDL + +This wrapper uses Cython to wrap RBDL in a Python module. The code requires +C++11 features and must be compiled with the flags ```-std=c++11``` (or on +older compilers: ```-std=c++0x```). It closely follows the C++ API. All +functions are found in the module ```rbdl```, e.g. + + rbdl.ForwardDynamics (model, q, qdot, tau, qddot) + +computes the accelerations qddot of the forward dynamics for given model, +q, qdot, tau. + +Arguments are all passed as reference and where possible, the wrapper +avoids copying values, especially for larger matrices such as Jacobians, +and joint-space inertia matrix. + +All functions have embedded signatures to ease the use from within IPython, +e.g. ```rbdl.ForwardDynamics?``` shows required arguments for the function. + +# Highlights + +Wrappers for the following features are already implemented: + +* supports model creation from Python code +* supports model loading from LuaModel and URDF +* operates directly on raw numpy data for Jacobians and joint-space inertia + matrix - no copying required! +* direct access to almost all values of the Model structure +* all functions of Dynamics.h are wrapped: + - O(n) inverse dynamics via RNEA + - O(n) forward dynamics via ABA + - Coriolis term computation via simplified RNEA + - computation of joint-space inertia matrix via CRBA +* kinematic computations: + - body <-> world transformations + - body point positions, velocities, and accelerations, including their + 6-D counterparts + - point Jacobians (translations) + - 6-D Jacobians (angular and linear movement) + - Spatial 6-D body Jacobians +* model mass, Center of Mass (CoM), CoM velocity, centroidal angular momentum + +# Differences to the C++ API + +The wrapper function ```rbdl.CalcCenterOfMass``` has a scalar return value +which is the mass of the model. Therefore the function does not use the +mass parameter when calling it. + +# ToDo + +* wrapping of constraint sets, and contact dynamics +* inverse kinematics +* documentation diff --git a/3rdparty/rbdl/python/crbdl.pxd b/3rdparty/rbdl/python/crbdl.pxd new file mode 100644 index 0000000..15c1f93 --- /dev/null +++ b/3rdparty/rbdl/python/crbdl.pxd @@ -0,0 +1,412 @@ +#cython: boundscheck=False + +from libcpp.string cimport string +from libcpp cimport bool +from libcpp.vector cimport vector + +cdef extern from "" namespace "RigidBodyDynamics::Math": + cdef cppclass VectorNd: + VectorNd () + VectorNd (int dim) + int rows() + int cols() + void resize (int) + double& operator[](int) + double* data() + + cdef cppclass Vector3d: + Vector3d () + int rows() + int cols() + double& operator[](int) + double* data() + + cdef cppclass Quaternion: + Quaternion () + int rows() + int cols() + double& operator[](int) + double* data() + Matrix3d toMatrix() +# Quaternion fromMatrix (Matrix3d &mat) + + cdef cppclass SpatialVector: + SpatialVector () + int rows() + int cols() + double& operator[](int) + double* data() + + cdef cppclass Matrix3d: + Matrix3d () + int rows() + int cols() + double& coeff "operator()"(int,int) + double* data() + + cdef cppclass MatrixNd: + MatrixNd () + MatrixNd (int rows, int cols) + int rows() + int cols() + void resize (int,int) + double& coeff "operator()"(int,int) + double* data() + void setZero() + + cdef cppclass SpatialMatrix: + SpatialMatrix () + int rows() + int cols() + double& coeff "operator()"(int,int) + double* data() + + cdef cppclass Matrix63: + Matrix63 () + int rows() + int cols() + double& coeff "operator()"(int,int) + double* data() + +cdef extern from "" namespace "RigidBodyDynamics::Math::Quaternion": + Quaternion fromMatrix(const Matrix3d &mat) + +cdef extern from "" namespace "RigidBodyDynamics::Math": + cdef cppclass SpatialTransform: + SpatialTransform() + SpatialMatrix toMatrix() + SpatialTransform inverse() + SpatialTransform operator*(const SpatialTransform&) + Matrix3d E + Vector3d r + + cdef cppclass SpatialRigidBodyInertia: + SpatialRigidBodyInertia() + SpatialMatrix toMatrix() + + double m + Vector3d h + double Ixx, Iyx, Iyy, Izx, Izy, Izz + +cdef extern from "" namespace "RigidBodyDynamics": + cdef cppclass Body: + Body() + Body(const double mass, const Vector3d &com, const Matrix3d &inertia) + double mMass + Vector3d mCenterOfMass + Matrix3d mInertia + bool mIsVirtual + + cdef cppclass FixedBody: + FixedBody() + double mMass + Vector3d mCenterOfMass + Matrix3d mInertia + unsigned int mMovableParent + SpatialTransform mParentTransform + SpatialTransform mBaseTransform + bool mIsVirtual + + +cdef extern from "" namespace "RigidBodyDynamics": + cdef enum JointType: + JointTypeUndefined = 0 + JointTypeRevolute + JointTypePrismatic + JointTypeRevoluteX + JointTypeRevoluteY + JointTypeRevoluteZ + JointTypeSpherical + JointTypeEulerZYX + JointTypeEulerXYZ + JointTypeEulerYXZ + JointTypeTranslationXYZ + JointTypeFloatingBase + JointTypeFixed + JointType1DoF + JointType2DoF + JointType3DoF + JointType4DoF + JointType5DoF + JointType6DoF + JointTypeCustom + +cdef extern from "" namespace "RigidBodyDynamics": + cdef cppclass Joint: + Joint() + Joint(JointType joint_type) + SpatialVector* mJointAxes + JointType mJointType + unsigned int mDoFCount + unsigned int q_index + +cdef extern from "" namespace "RigidBodyDynamics": + cdef cppclass Model: + Model() + unsigned int AddBody (const unsigned int parent_id, + const SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + string body_name + ) + unsigned int AppendBody (const SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + string body_name + ) + unsigned int GetParentBodyId( + unsigned int body_id) + unsigned int GetBodyId( + const char *body_name) + string GetBodyName ( + unsigned int body_id) + bool IsBodyId ( + unsigned int body_id) + bool IsFixedBodyId ( + unsigned int body_id) + Quaternion GetQuaternion ( + unsigned int body_id, + const VectorNd &q) + void SetQuaternion ( + unsigned int body_id, + const Quaternion &quat, + VectorNd &q) + + vector[unsigned int] _lambda + vector[unsigned int] lambda_q +# vector[vector[unsigned int]] mu + + unsigned int dof_count + unsigned int q_size + unsigned int qdot_size + unsigned int previously_added_body_id + + Vector3d gravity + vector[SpatialVector] v + vector[SpatialVector] a + + vector[Joint] mJoints + vector[SpatialVector] S + vector[SpatialTransform] X_J + vector[SpatialVector] v_J + vector[SpatialVector] c_J + + vector[unsigned int] mJointUpdateOrder + + vector[SpatialTransform] X_T + + vector[unsigned int] mFixedJointCount + + vector[Matrix63] multdof3_S + vector[Matrix63] multdof3_U + vector[Matrix63] multdof3_Dinv + vector[Matrix63] multdof3_u + vector[unsigned int] multdof3_w_index + + vector[SpatialVector] c + vector[SpatialMatrix] IA + vector[SpatialVector] pA + vector[SpatialVector] U + VectorNd d + VectorNd u + vector[SpatialVector] f + vector[SpatialRigidBodyInertia] I + vector[SpatialRigidBodyInertia] Ic + vector[SpatialVector] hc + + vector[SpatialTransform] X_lambda + vector[SpatialTransform] X_base + + vector[FixedBody] mFixedBodies + unsigned int fixed_body_discriminator + + vector[Body] mBodies + +cdef extern from "" namespace "RigidBodyDynamics": + cdef void UpdateKinematics (Model& model, + const VectorNd &q, + const VectorNd &qdot, + const VectorNd &qddot) + + cdef Vector3d CalcBodyToBaseCoordinates (Model& model, + const VectorNd &q, + const unsigned int body_id, + const Vector3d &body_point_coordinates, + bool update_kinematics) + + cdef Vector3d CalcBaseToBodyCoordinates (Model& model, + const VectorNd &q, + const unsigned int body_id, + const Vector3d &body_point_coordinates, + bool update_kinematics) + + cdef Vector3d CalcPointVelocity (Model& model, + const VectorNd &q, + const VectorNd &qdot, + const unsigned int body_id, + const Vector3d &body_point_coordinates, + bool update_kinematics) + + cdef Vector3d CalcPointAcceleration (Model& model, + const VectorNd &q, + const VectorNd &qdot, + const VectorNd &qddot, + const unsigned int body_id, + const Vector3d &body_point_coordinates, + bool update_kinematics) + + cdef SpatialVector CalcPointVelocity6D (Model& model, + const VectorNd &q, + const VectorNd &qdot, + const unsigned int body_id, + const Vector3d &body_point_coordinates, + bool update_kinematics) + + cdef SpatialVector CalcPointAcceleration6D (Model& model, + const VectorNd &q, + const VectorNd &qdot, + const VectorNd &qddot, + const unsigned int body_id, + const Vector3d &body_point_coordinates, + bool update_kinematics) + +cdef extern from "" namespace "RigidBodyDynamics::Utils": + cdef void CalcCenterOfMass (Model& model, + const VectorNd &q, + const VectorNd &qdot, + double &mass, + Vector3d &com, + Vector3d *com_velocity, + Vector3d *angular_momentum, + bool update_kinematics) + +cdef extern from "" namespace "RigidBodyDynamics": + cdef cppclass ConstraintSet: + ConstraintSet() + unsigned int AddContactConstraint ( + unsigned int body_id, + const Vector3d &body_point, + const Vector3d &world_normal, + const char* constraint_name, + double normal_acceleration) + + unsigned int AddLoopConstraint ( + unsigned int id_predecessor, + unsigned int id_successor, + const SpatialTransform &X_predecessor, + const SpatialTransform &X_successor, + const SpatialVector &axis, + double T_stab_inv, + const char *constraint_name) + + ConstraintSet Copy() + # void SetSolver (Math::LinearSolver solver) + bool Bind (const Model &model) + + size_t size() + void clear() + # Math::LinearSolver + bool bound + + vector[string] name + vector[unsigned int] body + vector[Vector3d] point + vector[Vector3d] normal + + VectorNd acceleration + VectorNd force + VectorNd impulse + VectorNd v_plus + + MatrixNd H + VectorNd C + VectorNd gamma + VectorNd G + + MatrixNd A + VectorNd b + VectorNd x + + MatrixNd GT_qr_Q + MatrixNd Y + MatrixNd Z + VectorNd qddot_y + VectorNd qddot_z + + MatrixNd K + VectorNd a + VectorNd QDDot_t + VectorNd QDDot_0 + + vector[SpatialVector] f_t + vector[SpatialVector] f_ext_constraints + vector[Vector3d] point_accel_0 + + vector[SpatialVector] d_pA + vector[SpatialVector] d_a + VectorNd d_u + + vector[SpatialMatrix] d_IA + vector[SpatialVector] d_U + + VectorNd d_d + vector[Vector3d] d_multdof3_u + +cdef extern from "rbdl_ptr_functions.h" namespace "RigidBodyDynamics": + cdef void CalcPointJacobianPtr (Model& model, + const double *q_ptr, + unsigned int body_id, + const Vector3d &point_position, + double *G, + bool update_kinematics) + + cdef void CalcPointJacobian6DPtr (Model &model, + const double *q_ptr, + unsigned int body_id, + const Vector3d &point_position, + double *G, + bool update_kinematics) + + cdef void CalcBodySpatialJacobianPtr ( + Model &model, + const double *q_ptr, + unsigned int body_id, + double *G, + bool update_kinematics) + + cdef void InverseDynamicsPtr ( + Model &model, + const double* q_ptr, + const double* qdot_ptr, + const double* qddot_ptr, + double* tau_ptr, + vector[SpatialVector] *f_ext + ) + + cdef void NonlinearEffectsPtr ( + Model &model, + const double* q_ptr, + const double* qdot_ptr, + double* tau_ptr + ) + + cdef void CompositeRigidBodyAlgorithmPtr (Model& model, + const double *q, + double *H, + bool update_kinematics) + + cdef void ForwardDynamicsPtr ( + Model &model, + const double* q_ptr, + const double* qdot_ptr, + double* tau_ptr, + const double* qddot_ptr, + vector[SpatialVector] *f_ext + ) + +cdef extern from "rbdl_loadmodel.cc": + cdef bool rbdl_loadmodel ( + const char* filename, + Model* model, + bool floating_base, + bool verbose) diff --git a/3rdparty/rbdl/python/rbdl-wrapper.pyx b/3rdparty/rbdl/python/rbdl-wrapper.pyx new file mode 100644 index 0000000..879336d --- /dev/null +++ b/3rdparty/rbdl/python/rbdl-wrapper.pyx @@ -0,0 +1,1393 @@ +#cython: boundscheck=False, embedsignature=True + +import numpy as np +cimport numpy as np +from libc.stdint cimport uintptr_t +from libcpp.string cimport string + +cimport crbdl + +############################## +# +# Linear Algebra Types +# +############################## + +cdef class Vector3d: + cdef crbdl.Vector3d *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.Vector3d() + + if pyvalues != None: + for i in range (3): + self.thisptr.data()[i] = pyvalues[i] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "Vector3d [{:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2]) + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 3 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Vector3d (address) + + @classmethod + def fromPythonArray (cls, python_values): + return Vector3d (0, python_values) + +cdef class Matrix3d: + cdef crbdl.Matrix3d *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.Matrix3d() + + if pyvalues != None: + for i in range (3): + for j in range (3): + (&(self.thisptr.coeff(i,j)))[0] = pyvalues[i,j] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "Matrix3d [{:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2]) + + def __getitem__(self, key): + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 3 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Matrix3d (address) + + @classmethod + def fromPythonArray (cls, python_values): + return Matrix3d (0, python_values) + + +cdef class VectorNd: + cdef crbdl.VectorNd *thisptr + cdef free_on_dealloc + + def __cinit__(self, ndim, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.VectorNd(ndim) + + if pyvalues != None: + for i in range (ndim): + self.thisptr.data()[i] = pyvalues[i] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return self.thisptr.rows() + + def toNumpy (self): + result = np.ndarray (self.thisptr.rows()) + for i in range (0, self.thisptr.rows()): + result[i] = self.thisptr[0][i] + return result + + # Constructors + @classmethod + def fromPythonArray (cls, python_values): + return VectorNd (len(python_values), 0, python_values) + + @classmethod + def fromPointer(cls, uintptr_t address): + cdef crbdl.VectorNd* vector_ptr = address + return VectorNd (vector_ptr.rows(), address) + +cdef class Quaternion: + cdef crbdl.Quaternion *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None, pymatvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.Quaternion() + + if pyvalues != None: + for i in range (4): + self.thisptr.data()[i] = pyvalues[i] + elif pymatvalues != None: + mat = Matrix3d() + for i in range (3): + for j in range (3): + (&(mat.thisptr.coeff(i,j)))[0] = pymatvalues[i,j] + self.thisptr[0] = crbdl.fromMatrix (mat.thisptr[0]) + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "Quaternion [{:3.4f}, {:3.4f}, {:3.4f}, {:3.4}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], + self.thisptr.data()[2], self.thisptr.data()[3]) + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 4 + + def toMatrix(self): + cdef crbdl.Matrix3d mat + mat = self.thisptr.toMatrix() + result = np.array ([3,3]) + for i in range (3): + for j in range (3): + result[i,j] = mat.coeff(i,j) + + return result + + def toNumpy(self): + result = np.ndarray (self.thisptr.rows()) + for i in range (0, self.thisptr.rows()): + result[i] = self.thisptr[0][i] + return result + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Quaternion (address) + + @classmethod + def fromPythonArray (cls, python_values): + return Quaternion (0, python_values) + + @classmethod + def fromPythonMatrix (cls, python_matrix_values): + return Quaternion (0, None, python_matrix_values) + +cdef class SpatialVector: + cdef crbdl.SpatialVector *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialVector() + + if pyvalues != None: + for i in range (6): + self.thisptr.data()[i] = pyvalues[i] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "SpatialVector [{:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2], + self.thisptr.data()[3], self.thisptr.data()[4], self.thisptr.data()[5]) + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 6 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialVector (address) + + @classmethod + def fromPythonArray (cls, python_values): + return SpatialVector (0, python_values) + +cdef class SpatialMatrix: + cdef crbdl.SpatialMatrix *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialMatrix() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "SpatialMatrix [{:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2], + self.thisptr.data()[3], self.thisptr.data()[4], self.thisptr.data()[5]) + + def __getitem__(self, key): + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 6 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialMatrix (address) + +############################## +# +# Conversion Numpy <-> Eigen +# +############################## + +# Vector3d +cdef crbdl.Vector3d NumpyToVector3d (np.ndarray[double, ndim=1, mode="c"] x): + cdef crbdl.Vector3d cx = crbdl.Vector3d() + for i in range (3): + cx[i] = x[i] + + return cx + +cdef np.ndarray Vector3dToNumpy (crbdl.Vector3d cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +# VectorNd +cdef crbdl.VectorNd NumpyToVectorNd (np.ndarray[double, ndim=1, mode="c"] x): + cdef crbdl.VectorNd cx = crbdl.VectorNd(x.shape[0]) + for i in range (x.shape[0]): + cx[i] = x[i] + + return cx + +cdef np.ndarray VectorNdToNumpy (crbdl.VectorNd cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +# MatrixNd +cdef crbdl.MatrixNd NumpyToMatrixNd (np.ndarray[double, ndim=2, mode="c"] M): + cdef crbdl.MatrixNd cM = crbdl.MatrixNd(M.shape[0], M.shape[1]) + for i in range (M.shape[0]): + for j in range (M.shape[1]): + (&(cM.coeff(i,j)))[0] = M[i,j] + + return cM + +cdef np.ndarray MatrixNdToNumpy (crbdl.MatrixNd cM): + result = np.ndarray ([cM.rows(), cM.cols()]) + for i in range (cM.rows()): + for j in range (cM.cols()): + result[i,j] = cM.coeff(i,j) + + return result + +# SpatialVector +cdef np.ndarray SpatialVectorToNumpy (crbdl.SpatialVector cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +cdef crbdl.Quaternion NumpyToQuaternion (np.ndarray[double, ndim=1, mode="c"] x): + cdef crbdl.Quaternion cx = crbdl.Quaternion() + for i in range (3): + cx[i] = x[i] + + return cx + +cdef np.ndarray QuaternionToNumpy (crbdl.Quaternion cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +############################## +# +# Spatial Algebra Types +# +############################## + +cdef class SpatialTransform: + cdef crbdl.SpatialTransform *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialTransform() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "SpatialTransform E = [ [{:3.4f}, {:3.4f}, {:3.4f}], [{:3.4f}, {:3.4f}, {:3.4f}], [{:3.4f}, {:3.4f}, {:3.4f}] ], r = [{:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.E.coeff(0,0), self.thisptr.E.coeff(0,1), self.thisptr.E.coeff(0,2), + self.thisptr.E.coeff(1,0), self.thisptr.E.coeff(1,1), self.thisptr.E.coeff(1,2), + self.thisptr.E.coeff(2,0), self.thisptr.E.coeff(2,1), self.thisptr.E.coeff(2,2), + self.thisptr.r[0], self.thisptr.r[1], self.thisptr.r[2]) + + property E: + """ Rotational part of the SpatialTransform. """ + def __get__ (self): + result = np.ndarray ((3,3)) + for i in range (3): + for j in range (3): + result[i,j] = self.thisptr.E.coeff(i,j) + + return result + + def __set__ (self, value): + for i in range (3): + for j in range (3): + (&(self.thisptr.E.coeff(i,j)))[0] = value[i,j] + + property r: + """ Translational part of the SpatialTransform. """ + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.r[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.r[i]))[0] = value[i] + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialTransform (address) + +cdef class SpatialRigidBodyInertia: + cdef crbdl.SpatialRigidBodyInertia *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialRigidBodyInertia() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "rbdl.SpatialRigidBodyInertia (0x{:0x})".format( self.thisptr) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialRigidBodyInertia (address) + + property m: + def __get__ (self): + return self.thisptr.m + + def __set__ (self, value): + self.thisptr.m = value + + property h: + """ Translational part of the SpatialRigidBodyInertia. """ + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.h[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.h[i]))[0] = value[i] + + property Ixx: + def __get__ (self): + return self.thisptr.Ixx + + def __set__ (self, value): + self.thisptr.Ixx = value + + property Iyx: + def __get__ (self): + return self.thisptr.Iyx + + def __set__ (self, value): + self.thisptr.Iyx = value + + property Iyy: + def __get__ (self): + return self.thisptr.Iyy + + def __set__ (self, value): + self.thisptr.Iyy = value + + property Izx: + def __get__ (self): + return self.thisptr.Izx + + def __set__ (self, value): + self.thisptr.Izx = value + + property Izy: + def __get__ (self): + return self.thisptr.Izy + + def __set__ (self, value): + self.thisptr.Izy = value + + property Izz: + def __get__ (self): + return self.thisptr.Izz + + def __set__ (self, value): + self.thisptr.Izz = value + +############################## +# +# Rigid Multibody Types +# +############################## + +cdef class Body: + cdef crbdl.Body *thisptr + cdef free_on_dealloc + + def __cinit__(self, **kwargs): + cdef double c_mass + cdef crbdl.Vector3d c_com + cdef crbdl.Matrix3d c_inertia + cdef uintptr_t address=0 + + if "address" in kwargs.keys(): + address=kwargs["address"] + mass = None + if "mass" in kwargs.keys(): + mass=kwargs["mass"] + com = None + if "com" in kwargs.keys(): + com=kwargs["com"] + inertia = None + if "inertia" in kwargs.keys(): + inertia=kwargs["inertia"] + + if address == 0: + self.free_on_dealloc = True + if (mass != None) and (com != None) and (inertia != None): + c_mass = mass + + for i in range (3): + c_com[i] = com[i] + + for i in range (3): + for j in range (3): + (&(c_inertia.coeff(i,j)))[0] = inertia[i,j] + + self.thisptr = new crbdl.Body(c_mass, c_com, c_inertia) + else: + self.thisptr = new crbdl.Body() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "rbdl.Body (0x{:0x})".format( self.thisptr) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Body (address=address) + + @classmethod + def fromMassComInertia(cls, double mass, + np.ndarray[double, ndim=1] com, + np.ndarray[double, ndim=2] inertia): + + return Body (address=0, mass=mass, com=com, inertia=inertia) + + # Properties + property mMass: + def __get__ (self): + return self.thisptr.mMass + + def __set__ (self, value): + self.thisptr.mMass = value + + property mCenterOfMass: + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.mCenterOfMass[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.mCenterOfMass[i]))[0] = value[i] + + property mInertia: + def __get__ (self): + result = np.ndarray ((3,3)) + for i in range (3): + for j in range (3): + result[i,j] = self.thisptr.mInertia.coeff(i,j) + + return result + + def __set__ (self, value): + for i in range (3): + for j in range (3): + (&(self.thisptr.mInertia.coeff(i,j)))[0] = value[i,j] + + property mIsVirtual: + def __get__ (self): + return self.thisptr.mIsVirtual + + def __set__ (self, value): + self.thisptr.mIsVirtual = value + +cdef class FixedBody: + cdef crbdl.FixedBody *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.FixedBody() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "rbdl.FixedBody (0x{:0x})".format( self.thisptr) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return FixedBody (address) + + # Properties + property mMass: + def __get__ (self): + return self.thisptr.mMass + + def __set__ (self, value): + self.thisptr.mMass = value + + property mCenterOfMass: + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.mCenterOfMass[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.mCenterOfMass[i]))[0] = value[i] + + property mInertia: + def __get__ (self): + result = np.ndarray ((3,3)) + for i in range (3): + for j in range (3): + result[i,j] = self.thisptr.mInertia.coeff(i,j) + + return result + + def __set__ (self, value): + for i in range (3): + for j in range (3): + (&(self.thisptr.mInertia.coeff(i,j)))[0] = value[i,j] + +cdef enum JointType: + JointTypeUndefined = 0 + JointTypeRevolute + JointTypePrismatic + JointTypeRevoluteX + JointTypeRevoluteY + JointTypeRevoluteZ + JointTypeSpherical + JointTypeEulerZYX + JointTypeEulerXYZ + JointTypeEulerYXZ + JointTypeTranslationXYZ + JointTypeFloatingBase + JointTypeFixed + JointType1DoF + JointType2DoF + JointType3DoF + JointType4DoF + JointType5DoF + JointType6DoF + JointTypeCustom + +cdef class Joint: + cdef crbdl.Joint *thisptr + cdef free_on_dealloc + + joint_type_map = { + JointTypeUndefined: "JointTypeUndefined", + JointTypeRevolute: "JointTypeRevolute", + JointTypePrismatic: "JointTypePrismatic", + JointTypeRevoluteX: "JointTypeRevoluteX", + JointTypeRevoluteY: "JointTypeRevoluteY", + JointTypeRevoluteZ: "JointTypeRevoluteZ", + JointTypeSpherical: "JointTypeSpherical", + JointTypeEulerZYX: "JointTypeEulerZYX", + JointTypeEulerXYZ: "JointTypeEulerXYZ", + JointTypeEulerYXZ: "JointTypeEulerYXZ", + JointTypeTranslationXYZ: "JointTypeTranslationXYZ", + JointTypeFloatingBase: "JointTypeFloatingBase", + JointTypeFixed: "JointTypeFixed", + JointType1DoF: "JointType1DoF", + JointType2DoF: "JointType2DoF", + JointType3DoF: "JointType3DoF", + JointType4DoF: "JointType4DoF", + JointType5DoF: "JointType5DoF", + JointType6DoF: "JointType6DoF", + JointTypeCustom: "JointTypeCustom", + } + + def _joint_type_from_str (self, joint_type_str): + if joint_type_str not in self.joint_type_map.values(): + raise ValueError("Invalid JointType '" + str(joint_type_str) + "'!") + else: + for joint_type, joint_str in self.joint_type_map.iteritems(): + if joint_str == joint_type_str: + return joint_type + + def __cinit__(self, uintptr_t address=0, joint_type=-1): + if address == 0: + self.free_on_dealloc = True + if joint_type == -1: + self.thisptr = new crbdl.Joint() + else: + self.thisptr = new crbdl.Joint(self._joint_type_from_str(joint_type)) + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + joint_type_str = "JointTypeUndefined" + + if self.thisptr.mJointType in self.joint_type_map.keys(): + joint_type_str = self.joint_type_map[self.thisptr.mJointType] + + return "rbdl.Joint (0x{:0x}), JointType: {:s}".format( self.thisptr, joint_type_str) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Joint (address) + + @classmethod + def fromJointType(cls, joint_type): + return Joint (0, joint_type) + + @classmethod + def fromJointAxes(cls, axes): + assert (len(axes) > 0) + assert (len(axes[0]) == 6) + axes_count = len(axes) + joint_type = JointType1DoF + axes_count - 1 + + result = Joint (0, cls.joint_type_map[joint_type]) + + for i in range (axes_count): + result.setJointAxis(i, axes[i]) + + return result + + property mDoFCount: + def __get__ (self): + return self.thisptr.mDoFCount + + def __set__ (self, value): + self.thisptr.mDoFCount = value + + property mJointType: + def __get__ (self): + return self.joint_type_map[self.thisptr.mJointType] + + property q_index: + def __get__ (self): + return self.thisptr.q_index + + def getJointAxis (self, index): + assert index >= 0 and index < self.thisptr.mDoFCount, "Invalid joint axis index!" + return SpatialVectorToNumpy (self.thisptr.mJointAxes[index]) + + def setJointAxis (self, index, value): + assert index >= 0 and index < self.thisptr.mDoFCount, "Invalid joint axis index!" + for i in range (6): + (&(self.thisptr.mJointAxes[index][i]))[0] = value[i] + self.thisptr.mJointAxes[index][i] + +cdef class Model + +%VectorWrapperClassDefinitions(PARENT=Model)% + +cdef class Model: + cdef crbdl.Model *thisptr + %VectorWrapperMemberDefinitions (PARENT=Model)% + + def __cinit__(self): + self.thisptr = new crbdl.Model() + %VectorWrapperCInitCode (PARENT=Model)% + + def __dealloc__(self): + del self.thisptr + + def __repr__(self): + return "rbdl.Model (0x{:0x})".format( self.thisptr) + + def AddBody (self, + parent_id, + SpatialTransform joint_frame not None, + Joint joint not None, + Body body not None, + string body_name = ""): + return self.thisptr.AddBody ( + parent_id, + joint_frame.thisptr[0], + joint.thisptr[0], + body.thisptr[0], + body_name + ) + + def AppendBody (self, + SpatialTransform joint_frame not None, + Joint joint not None, + Body body not None, + string body_name = ""): + return self.thisptr.AppendBody ( + joint_frame.thisptr[0], + joint.thisptr[0], + body.thisptr[0], + body_name + ) + + def SetQuaternion (self, + int body_id, + np.ndarray[double, ndim=1, mode="c"] quat, + np.ndarray[double, ndim=1, mode="c"] q): + quat_wrap = Quaternion.fromPythonArray (quat) + q_wrap = VectorNd.fromPythonArray (q) + self.thisptr.SetQuaternion (body_id, + (quat_wrap).thisptr[0], + (q_wrap).thisptr[0]) + for i in range(len(q)): + q[i] = q_wrap[i] + + def GetQuaternion (self, + int body_id, + np.ndarray[double, ndim=1, mode="c"] q): + return QuaternionToNumpy (self.thisptr.GetQuaternion(body_id, NumpyToVectorNd (q))) + + def GetBody (self, index): + return Body (address= &(self.thisptr.mBodies[index])) + + def GetParentBodyId (self, index): + return self.thisptr.GetParentBodyId(index) + + def GetBodyId (self, name): + return self.thisptr.GetBodyId(name) + + def GetBodyName (self, index): + return self.thisptr.GetBodyName(index) + + def IsBodyId (self, index): + return self.thisptr.IsBodyId(index) + + def IsFixedBodyId (self, index): + return self.thisptr.IsFixedBodyId(index) + + property dof_count: + def __get__ (self): + return self.thisptr.dof_count + + property q_size: + def __get__ (self): + return self.thisptr.q_size + + property qdot_size: + def __get__ (self): + return self.thisptr.qdot_size + + property previously_added_body_id: + def __get__ (self): + return self.thisptr.previously_added_body_id + + property gravity: + def __get__ (self): + return np.array ([ + self.thisptr.gravity[0], + self.thisptr.gravity[1], + self.thisptr.gravity[2] + ] + ) + def __set__ (self, values): + for i in range (0,3): + self.thisptr.gravity[i] = values[i] + + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=v, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=a, PARENT=Model)% + + %VectorWrapperAddProperty (TYPE=Joint, MEMBER=mJoints, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=S, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialTransform, MEMBER=X_J, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=v_J, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=c_J, PARENT=Model)% + + property mJointUpdateOrder: + def __get__ (self): + return self.thisptr.mJointUpdateOrder + + %VectorWrapperAddProperty (TYPE=SpatialTransform, MEMBER=X_T, PARENT=Model)% + + property mFixedJointCount: + def __get__ (self): + return self.thisptr.mFixedJointCount + + # TODO + # multdof3_S + # multdof3_U + # multdof3_Dinv + # multdof3_u + + property multdof3_w_index: + def __get__ (self): + return self.thisptr.multdof3_w_index + + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=c, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialMatrix, MEMBER=IA, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=pA, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=U, PARENT=Model)% + + # TODO + # d + # u + + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=f, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialRigidBodyInertia, MEMBER=I, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialRigidBodyInertia, MEMBER=Ic, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialVector, MEMBER=hc, PARENT=Model)% + + %VectorWrapperAddProperty (TYPE=SpatialTransform, MEMBER=X_lambda, PARENT=Model)% + %VectorWrapperAddProperty (TYPE=SpatialTransform, MEMBER=X_base, PARENT=Model)% + + %VectorWrapperAddProperty (TYPE=FixedBody, MEMBER=mFixedBodies, PARENT=Model)% + + property fixed_body_discriminator: + def __get__ (self): + return self.thisptr.fixed_body_discriminator + + %VectorWrapperAddProperty (TYPE=Body, MEMBER=mBodies, PARENT=Model)% + +############################## +# +# Constraint Types +# +############################## + +cdef class ConstraintSet + +%VectorWrapperClassDefinitions(PARENT=ConstraintSet)% + +cdef class ConstraintSet: + cdef crbdl.ConstraintSet *thisptr + %VectorWrapperMemberDefinitions (PARENT=ConstraintSet)% + + def __cinit__(self): + self.thisptr = new crbdl.ConstraintSet() + %VectorWrapperCInitCode (PARENT=ConstraintSet)% + + def __dealloc__(self): + del self.thisptr + + def __repr__(self): + return "rbdl.ConstraintSet (0x{:0x})".format( self.thisptr) + + def AddContactConstraint (self, + body_id not None, + body_point not None, + world_normal not None, + constraint_name = None, + normal_acceleration = 0.): + cdef crbdl.Vector3d c_body_point + cdef crbdl.Vector3d c_world_normal + cdef char* constraint_name_ptr + + for i in range (3): + c_body_point[i] = body_point[i] + c_world_normal[i] = world_normal[i] + + if constraint_name == None: + constraint_name_ptr = NULL + else: + constraint_name_ptr = constraint_name + + return self.thisptr.AddContactConstraint ( + body_id, + c_body_point, + c_world_normal, + constraint_name_ptr, + normal_acceleration + ) + + def AddLoopConstraint (self, + id_predecessor not None, + id_successor not None, + SpatialTransform X_predecessor not None, + SpatialTransform X_successor not None, + SpatialVector axis not None, + double T_stab_inv, + constraint_name = None): + cdef char* constraint_name_ptr + + if constraint_name == None: + constraint_name_ptr = NULL + else: + constraint_name_ptr = constraint_name + + return self.thisptr.AddLoopConstraint ( + id_predecessor, + id_successor, + X_predecessor.thisptr[0], + X_successor.thisptr[0], + axis.thisptr[0], + T_stab_inv, + constraint_name_ptr) + + def Bind (self, model): + return self.thisptr.Bind ((model).thisptr[0]) + + def size (self): + return self.thisptr.size() + + def clear (self): + self.thisptr.clear() + + property bound: + def __get__ (self): + return self.thisptr.bound + +# %VectorWrapperAddProperty (TYPE=string, MEMBER=name, PARENT=ConstraintSet)% + + %VectorWrapperAddProperty (TYPE=Vector3d, MEMBER=point, PARENT=ConstraintSet)% + %VectorWrapperAddProperty (TYPE=Vector3d, MEMBER=normal, PARENT=ConstraintSet)% + +# property acceleration: +# def __get__(self): +# return VectorNd.fromPointer ( &(self.thisptr.acceleration)).toNumpy() +# def __set__(self, values): +# vec = VectorNd.fromPythonArray (values) +# self.thisptr.acceleration = (vec.thisptr[0]) + +############################## +# +# Kinematics.h +# +############################## + +def CalcBodyToBaseCoordinates (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcBodyToBaseCoordinates ( + model.thisptr[0], + NumpyToVectorNd (q), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcBaseToBodyCoordinates (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcBaseToBodyCoordinates ( + model.thisptr[0], + NumpyToVectorNd (q), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointVelocity (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcPointVelocity ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointAcceleration (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] qddot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcPointAcceleration ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + NumpyToVectorNd (qddot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointVelocity6D (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return SpatialVectorToNumpy (crbdl.CalcPointVelocity6D ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointAcceleration6D (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] qddot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return SpatialVectorToNumpy (crbdl.CalcPointAcceleration6D ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + NumpyToVectorNd (qddot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointJacobian (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + np.ndarray[double, ndim=2, mode="c"] G, + update_kinematics=True): + crbdl.CalcPointJacobianPtr ( + model.thisptr[0], + q.data, + body_id, + NumpyToVector3d (body_point_position), + G.data, + update_kinematics + ) + +def CalcPointJacobian6D (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + np.ndarray[double, ndim=2, mode="c"] G, + update_kinematics=True): + crbdl.CalcPointJacobian6DPtr ( + model.thisptr[0], + q.data, + body_id, + NumpyToVector3d (body_point_position), + G.data, + update_kinematics + ) + +def CalcBodySpatialJacobian(Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + np.ndarray[double, ndim=2, mode="c"] G, + update_kinematics=True): + crbdl.CalcBodySpatialJacobianPtr( + model.thisptr[0], + q.data, + body_id, + G.data, + update_kinematics + ) + +############################## +# +# rbdl_utils.h +# +############################## + +def CalcCenterOfMass (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] com, + np.ndarray[double, ndim=1, mode="c"] com_velocity=None, + np.ndarray[double, ndim=1, mode="c"] angular_momentum=None, + update_kinematics=True): + cdef double cmass + cdef crbdl.Vector3d c_com = crbdl.Vector3d() + cdef crbdl.Vector3d* c_com_vel_ptr # = crbdl.Vector3d() + cdef crbdl.Vector3d* c_ang_momentum_ptr # = crbdl.Vector3d() + + c_com_vel_ptr = NULL + c_ang_momentum_ptr = NULL + + if com_velocity != None: + c_com_vel_ptr = new crbdl.Vector3d() + + if angular_momentum != None: + c_ang_momentum_ptr = new crbdl.Vector3d() + + cmass = 0.0 + crbdl.CalcCenterOfMass ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + cmass, + c_com, + c_com_vel_ptr, + c_ang_momentum_ptr, + update_kinematics) + + com[0] = c_com[0] + com[1] = c_com[1] + com[2] = c_com[2] + + if com_velocity != None: + com_velocity[0] = c_com_vel_ptr.data()[0] + com_velocity[1] = c_com_vel_ptr.data()[1] + com_velocity[2] = c_com_vel_ptr.data()[2] + del c_com_vel_ptr + + if angular_momentum != None: + angular_momentum[0] = c_ang_momentum_ptr.data()[0] + angular_momentum[1] = c_ang_momentum_ptr.data()[1] + angular_momentum[2] = c_ang_momentum_ptr.data()[2] + del c_ang_momentum_ptr + + return cmass + +############################## +# +# Dynamics.h +# +############################## + +def InverseDynamics (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] qddot, + np.ndarray[double, ndim=1, mode="c"] tau): + crbdl.InverseDynamicsPtr (model.thisptr[0], + q.data, + qdot.data, + qddot.data, + tau.data, + NULL + ) + +def NonlinearEffects (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] tau): + crbdl.NonlinearEffectsPtr (model.thisptr[0], + q.data, + qdot.data, + tau.data + ) + +def CompositeRigidBodyAlgorithm (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=2, mode="c"] H, + update_kinematics=True): + crbdl.CompositeRigidBodyAlgorithmPtr (model.thisptr[0], + q.data, + H.data, + update_kinematics); + +def ForwardDynamics (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] tau, + np.ndarray[double, ndim=1, mode="c"] qddot): + crbdl.ForwardDynamicsPtr (model.thisptr[0], + q.data, + qdot.data, + tau.data, + qddot.data, + NULL + ) + +def loadModel ( + filename, + **kwargs + ): + verbose = False + if "verbose" in kwargs.keys(): + verbose=kwargs["verbose"] + + floating_base = False + if "floating_base" in kwargs.keys(): + floating_base=kwargs["floating_base"] + + result = Model() + if crbdl.rbdl_loadmodel (filename, result.thisptr, floating_base, verbose): + return result + + print ("Error loading model {}!".format (filename)) + return None diff --git a/3rdparty/rbdl/python/rbdl.pyx b/3rdparty/rbdl/python/rbdl.pyx new file mode 100644 index 0000000..16332d1 --- /dev/null +++ b/3rdparty/rbdl/python/rbdl.pyx @@ -0,0 +1,2119 @@ +# WARNING! +# +# This file was automatically created from rbdl-wrapper.pyx using wrappergen.py. +# Do not modify this file directly. Edit original source instead!! + +#cython: boundscheck=False, embedsignature=True + +import numpy as np +cimport numpy as np +from libc.stdint cimport uintptr_t +from libcpp.string cimport string + +cimport crbdl + +############################## +# +# Linear Algebra Types +# +############################## + +cdef class Vector3d: + cdef crbdl.Vector3d *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.Vector3d() + + if pyvalues != None: + for i in range (3): + self.thisptr.data()[i] = pyvalues[i] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "Vector3d [{:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2]) + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 3 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Vector3d (address) + + @classmethod + def fromPythonArray (cls, python_values): + return Vector3d (0, python_values) + +cdef class Matrix3d: + cdef crbdl.Matrix3d *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.Matrix3d() + + if pyvalues != None: + for i in range (3): + for j in range (3): + (&(self.thisptr.coeff(i,j)))[0] = pyvalues[i,j] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "Matrix3d [{:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2]) + + def __getitem__(self, key): + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 3 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Matrix3d (address) + + @classmethod + def fromPythonArray (cls, python_values): + return Matrix3d (0, python_values) + + +cdef class VectorNd: + cdef crbdl.VectorNd *thisptr + cdef free_on_dealloc + + def __cinit__(self, ndim, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.VectorNd(ndim) + + if pyvalues != None: + for i in range (ndim): + self.thisptr.data()[i] = pyvalues[i] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return self.thisptr.rows() + + def toNumpy (self): + result = np.ndarray (self.thisptr.rows()) + for i in range (0, self.thisptr.rows()): + result[i] = self.thisptr[0][i] + return result + + # Constructors + @classmethod + def fromPythonArray (cls, python_values): + return VectorNd (len(python_values), 0, python_values) + + @classmethod + def fromPointer(cls, uintptr_t address): + cdef crbdl.VectorNd* vector_ptr = address + return VectorNd (vector_ptr.rows(), address) + +cdef class Quaternion: + cdef crbdl.Quaternion *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None, pymatvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.Quaternion() + + if pyvalues != None: + for i in range (4): + self.thisptr.data()[i] = pyvalues[i] + elif pymatvalues != None: + mat = Matrix3d() + for i in range (3): + for j in range (3): + (&(mat.thisptr.coeff(i,j)))[0] = pymatvalues[i,j] + self.thisptr[0] = crbdl.fromMatrix (mat.thisptr[0]) + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "Quaternion [{:3.4f}, {:3.4f}, {:3.4f}, {:3.4}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], + self.thisptr.data()[2], self.thisptr.data()[3]) + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 4 + + def toMatrix(self): + cdef crbdl.Matrix3d mat + mat = self.thisptr.toMatrix() + result = np.array ([3,3]) + for i in range (3): + for j in range (3): + result[i,j] = mat.coeff(i,j) + + return result + + def toNumpy(self): + result = np.ndarray (self.thisptr.rows()) + for i in range (0, self.thisptr.rows()): + result[i] = self.thisptr[0][i] + return result + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Quaternion (address) + + @classmethod + def fromPythonArray (cls, python_values): + return Quaternion (0, python_values) + + @classmethod + def fromPythonMatrix (cls, python_matrix_values): + return Quaternion (0, None, python_matrix_values) + +cdef class SpatialVector: + cdef crbdl.SpatialVector *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0, pyvalues=None): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialVector() + + if pyvalues != None: + for i in range (6): + self.thisptr.data()[i] = pyvalues[i] + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "SpatialVector [{:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2], + self.thisptr.data()[3], self.thisptr.data()[4], self.thisptr.data()[5]) + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [self.thisptr.data()[i] for i in xrange(*key.indices(len(self)))] + else: + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 6 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialVector (address) + + @classmethod + def fromPythonArray (cls, python_values): + return SpatialVector (0, python_values) + +cdef class SpatialMatrix: + cdef crbdl.SpatialMatrix *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialMatrix() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "SpatialMatrix [{:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.data()[0], self.thisptr.data()[1], self.thisptr.data()[2], + self.thisptr.data()[3], self.thisptr.data()[4], self.thisptr.data()[5]) + + def __getitem__(self, key): + return self.thisptr.data()[key] + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + self.thisptr.data()[i] = value[src_index] + src_index = src_index + 1 + else: + self.thisptr.data()[key] = value + + def __len__ (self): + return 6 + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialMatrix (address) + +############################## +# +# Conversion Numpy <-> Eigen +# +############################## + +# Vector3d +cdef crbdl.Vector3d NumpyToVector3d (np.ndarray[double, ndim=1, mode="c"] x): + cdef crbdl.Vector3d cx = crbdl.Vector3d() + for i in range (3): + cx[i] = x[i] + + return cx + +cdef np.ndarray Vector3dToNumpy (crbdl.Vector3d cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +# VectorNd +cdef crbdl.VectorNd NumpyToVectorNd (np.ndarray[double, ndim=1, mode="c"] x): + cdef crbdl.VectorNd cx = crbdl.VectorNd(x.shape[0]) + for i in range (x.shape[0]): + cx[i] = x[i] + + return cx + +cdef np.ndarray VectorNdToNumpy (crbdl.VectorNd cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +# MatrixNd +cdef crbdl.MatrixNd NumpyToMatrixNd (np.ndarray[double, ndim=2, mode="c"] M): + cdef crbdl.MatrixNd cM = crbdl.MatrixNd(M.shape[0], M.shape[1]) + for i in range (M.shape[0]): + for j in range (M.shape[1]): + (&(cM.coeff(i,j)))[0] = M[i,j] + + return cM + +cdef np.ndarray MatrixNdToNumpy (crbdl.MatrixNd cM): + result = np.ndarray ([cM.rows(), cM.cols()]) + for i in range (cM.rows()): + for j in range (cM.cols()): + result[i,j] = cM.coeff(i,j) + + return result + +# SpatialVector +cdef np.ndarray SpatialVectorToNumpy (crbdl.SpatialVector cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +cdef crbdl.Quaternion NumpyToQuaternion (np.ndarray[double, ndim=1, mode="c"] x): + cdef crbdl.Quaternion cx = crbdl.Quaternion() + for i in range (3): + cx[i] = x[i] + + return cx + +cdef np.ndarray QuaternionToNumpy (crbdl.Quaternion cx): + result = np.ndarray ((cx.rows())) + for i in range (cx.rows()): + result[i] = cx[i] + + return result + +############################## +# +# Spatial Algebra Types +# +############################## + +cdef class SpatialTransform: + cdef crbdl.SpatialTransform *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialTransform() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "SpatialTransform E = [ [{:3.4f}, {:3.4f}, {:3.4f}], [{:3.4f}, {:3.4f}, {:3.4f}], [{:3.4f}, {:3.4f}, {:3.4f}] ], r = [{:3.4f}, {:3.4f}, {:3.4f}]".format ( + self.thisptr.E.coeff(0,0), self.thisptr.E.coeff(0,1), self.thisptr.E.coeff(0,2), + self.thisptr.E.coeff(1,0), self.thisptr.E.coeff(1,1), self.thisptr.E.coeff(1,2), + self.thisptr.E.coeff(2,0), self.thisptr.E.coeff(2,1), self.thisptr.E.coeff(2,2), + self.thisptr.r[0], self.thisptr.r[1], self.thisptr.r[2]) + + property E: + """ Rotational part of the SpatialTransform. """ + def __get__ (self): + result = np.ndarray ((3,3)) + for i in range (3): + for j in range (3): + result[i,j] = self.thisptr.E.coeff(i,j) + + return result + + def __set__ (self, value): + for i in range (3): + for j in range (3): + (&(self.thisptr.E.coeff(i,j)))[0] = value[i,j] + + property r: + """ Translational part of the SpatialTransform. """ + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.r[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.r[i]))[0] = value[i] + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialTransform (address) + +cdef class SpatialRigidBodyInertia: + cdef crbdl.SpatialRigidBodyInertia *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.SpatialRigidBodyInertia() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "rbdl.SpatialRigidBodyInertia (0x{:0x})".format( self.thisptr) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return SpatialRigidBodyInertia (address) + + property m: + def __get__ (self): + return self.thisptr.m + + def __set__ (self, value): + self.thisptr.m = value + + property h: + """ Translational part of the SpatialRigidBodyInertia. """ + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.h[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.h[i]))[0] = value[i] + + property Ixx: + def __get__ (self): + return self.thisptr.Ixx + + def __set__ (self, value): + self.thisptr.Ixx = value + + property Iyx: + def __get__ (self): + return self.thisptr.Iyx + + def __set__ (self, value): + self.thisptr.Iyx = value + + property Iyy: + def __get__ (self): + return self.thisptr.Iyy + + def __set__ (self, value): + self.thisptr.Iyy = value + + property Izx: + def __get__ (self): + return self.thisptr.Izx + + def __set__ (self, value): + self.thisptr.Izx = value + + property Izy: + def __get__ (self): + return self.thisptr.Izy + + def __set__ (self, value): + self.thisptr.Izy = value + + property Izz: + def __get__ (self): + return self.thisptr.Izz + + def __set__ (self, value): + self.thisptr.Izz = value + +############################## +# +# Rigid Multibody Types +# +############################## + +cdef class Body: + cdef crbdl.Body *thisptr + cdef free_on_dealloc + + def __cinit__(self, **kwargs): + cdef double c_mass + cdef crbdl.Vector3d c_com + cdef crbdl.Matrix3d c_inertia + cdef uintptr_t address=0 + + if "address" in kwargs.keys(): + address=kwargs["address"] + mass = None + if "mass" in kwargs.keys(): + mass=kwargs["mass"] + com = None + if "com" in kwargs.keys(): + com=kwargs["com"] + inertia = None + if "inertia" in kwargs.keys(): + inertia=kwargs["inertia"] + + if address == 0: + self.free_on_dealloc = True + if (mass != None) and (com != None) and (inertia != None): + c_mass = mass + + for i in range (3): + c_com[i] = com[i] + + for i in range (3): + for j in range (3): + (&(c_inertia.coeff(i,j)))[0] = inertia[i,j] + + self.thisptr = new crbdl.Body(c_mass, c_com, c_inertia) + else: + self.thisptr = new crbdl.Body() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "rbdl.Body (0x{:0x})".format( self.thisptr) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Body (address=address) + + @classmethod + def fromMassComInertia(cls, double mass, + np.ndarray[double, ndim=1] com, + np.ndarray[double, ndim=2] inertia): + + return Body (address=0, mass=mass, com=com, inertia=inertia) + + # Properties + property mMass: + def __get__ (self): + return self.thisptr.mMass + + def __set__ (self, value): + self.thisptr.mMass = value + + property mCenterOfMass: + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.mCenterOfMass[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.mCenterOfMass[i]))[0] = value[i] + + property mInertia: + def __get__ (self): + result = np.ndarray ((3,3)) + for i in range (3): + for j in range (3): + result[i,j] = self.thisptr.mInertia.coeff(i,j) + + return result + + def __set__ (self, value): + for i in range (3): + for j in range (3): + (&(self.thisptr.mInertia.coeff(i,j)))[0] = value[i,j] + + property mIsVirtual: + def __get__ (self): + return self.thisptr.mIsVirtual + + def __set__ (self, value): + self.thisptr.mIsVirtual = value + +cdef class FixedBody: + cdef crbdl.FixedBody *thisptr + cdef free_on_dealloc + + def __cinit__(self, uintptr_t address=0): + if address == 0: + self.free_on_dealloc = True + self.thisptr = new crbdl.FixedBody() + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + return "rbdl.FixedBody (0x{:0x})".format( self.thisptr) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return FixedBody (address) + + # Properties + property mMass: + def __get__ (self): + return self.thisptr.mMass + + def __set__ (self, value): + self.thisptr.mMass = value + + property mCenterOfMass: + def __get__ (self): + result = np.ndarray ((3)) + for i in range (3): + result[i] = self.thisptr.mCenterOfMass[i] + + return result + + def __set__ (self, value): + for i in range (3): + (&(self.thisptr.mCenterOfMass[i]))[0] = value[i] + + property mInertia: + def __get__ (self): + result = np.ndarray ((3,3)) + for i in range (3): + for j in range (3): + result[i,j] = self.thisptr.mInertia.coeff(i,j) + + return result + + def __set__ (self, value): + for i in range (3): + for j in range (3): + (&(self.thisptr.mInertia.coeff(i,j)))[0] = value[i,j] + +cdef enum JointType: + JointTypeUndefined = 0 + JointTypeRevolute + JointTypePrismatic + JointTypeRevoluteX + JointTypeRevoluteY + JointTypeRevoluteZ + JointTypeSpherical + JointTypeEulerZYX + JointTypeEulerXYZ + JointTypeEulerYXZ + JointTypeTranslationXYZ + JointTypeFloatingBase + JointTypeFixed + JointType1DoF + JointType2DoF + JointType3DoF + JointType4DoF + JointType5DoF + JointType6DoF + JointTypeCustom + +cdef class Joint: + cdef crbdl.Joint *thisptr + cdef free_on_dealloc + + joint_type_map = { + JointTypeUndefined: "JointTypeUndefined", + JointTypeRevolute: "JointTypeRevolute", + JointTypePrismatic: "JointTypePrismatic", + JointTypeRevoluteX: "JointTypeRevoluteX", + JointTypeRevoluteY: "JointTypeRevoluteY", + JointTypeRevoluteZ: "JointTypeRevoluteZ", + JointTypeSpherical: "JointTypeSpherical", + JointTypeEulerZYX: "JointTypeEulerZYX", + JointTypeEulerXYZ: "JointTypeEulerXYZ", + JointTypeEulerYXZ: "JointTypeEulerYXZ", + JointTypeTranslationXYZ: "JointTypeTranslationXYZ", + JointTypeFloatingBase: "JointTypeFloatingBase", + JointTypeFixed: "JointTypeFixed", + JointType1DoF: "JointType1DoF", + JointType2DoF: "JointType2DoF", + JointType3DoF: "JointType3DoF", + JointType4DoF: "JointType4DoF", + JointType5DoF: "JointType5DoF", + JointType6DoF: "JointType6DoF", + JointTypeCustom: "JointTypeCustom", + } + + def _joint_type_from_str (self, joint_type_str): + if joint_type_str not in self.joint_type_map.values(): + raise ValueError("Invalid JointType '" + str(joint_type_str) + "'!") + else: + for joint_type, joint_str in self.joint_type_map.iteritems(): + if joint_str == joint_type_str: + return joint_type + + def __cinit__(self, uintptr_t address=0, joint_type=-1): + if address == 0: + self.free_on_dealloc = True + if joint_type == -1: + self.thisptr = new crbdl.Joint() + else: + self.thisptr = new crbdl.Joint(self._joint_type_from_str(joint_type)) + else: + self.free_on_dealloc = False + self.thisptr = address + + def __dealloc__(self): + if self.free_on_dealloc: + del self.thisptr + + def __repr__(self): + joint_type_str = "JointTypeUndefined" + + if self.thisptr.mJointType in self.joint_type_map.keys(): + joint_type_str = self.joint_type_map[self.thisptr.mJointType] + + return "rbdl.Joint (0x{:0x}), JointType: {:s}".format( self.thisptr, joint_type_str) + + # Constructors + @classmethod + def fromPointer(cls, uintptr_t address): + return Joint (address) + + @classmethod + def fromJointType(cls, joint_type): + return Joint (0, joint_type) + + @classmethod + def fromJointAxes(cls, axes): + assert (len(axes) > 0) + assert (len(axes[0]) == 6) + axes_count = len(axes) + joint_type = JointType1DoF + axes_count - 1 + + result = Joint (0, cls.joint_type_map[joint_type]) + + for i in range (axes_count): + result.setJointAxis(i, axes[i]) + + return result + + property mDoFCount: + def __get__ (self): + return self.thisptr.mDoFCount + + def __set__ (self, value): + self.thisptr.mDoFCount = value + + property mJointType: + def __get__ (self): + return self.joint_type_map[self.thisptr.mJointType] + + property q_index: + def __get__ (self): + return self.thisptr.q_index + + def getJointAxis (self, index): + assert index >= 0 and index < self.thisptr.mDoFCount, "Invalid joint axis index!" + return SpatialVectorToNumpy (self.thisptr.mJointAxes[index]) + + def setJointAxis (self, index, value): + assert index >= 0 and index < self.thisptr.mDoFCount, "Invalid joint axis index!" + for i in range (6): + (&(self.thisptr.mJointAxes[index][i]))[0] = value[i] + self.thisptr.mJointAxes[index][i] + +cdef class Model + +cdef class _Model_v_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.v[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.v[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.v[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.v[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.v.size() + +cdef class _Model_a_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.a[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.a[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.a[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.a[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.a.size() + +cdef class _Model_mJoints_Joint_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [Joint.fromPointer ( &(self.parent.mJoints[i])) for i in xrange (*key.indices(len(self)))] + else: + return Joint.fromPointer ( &(self.parent.mJoints[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], Joint), "Invalid type! Expected Joint, but got " + str(type(value[src_index])) + "." + self.parent.mJoints[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, Joint), "Invalid type! Expected Joint, but got " + str(type(value)) + "." + self.parent.mJoints[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.mJoints.size() + +cdef class _Model_S_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.S[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.S[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.S[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.S[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.S.size() + +cdef class _Model_X_J_SpatialTransform_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialTransform.fromPointer ( &(self.parent.X_J[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialTransform.fromPointer ( &(self.parent.X_J[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value[src_index])) + "." + self.parent.X_J[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value)) + "." + self.parent.X_J[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.X_J.size() + +cdef class _Model_v_J_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.v_J[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.v_J[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.v_J[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.v_J[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.v_J.size() + +cdef class _Model_c_J_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.c_J[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.c_J[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.c_J[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.c_J[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.c_J.size() + +cdef class _Model_X_T_SpatialTransform_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialTransform.fromPointer ( &(self.parent.X_T[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialTransform.fromPointer ( &(self.parent.X_T[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value[src_index])) + "." + self.parent.X_T[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value)) + "." + self.parent.X_T[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.X_T.size() + +cdef class _Model_c_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.c[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.c[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.c[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.c[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.c.size() + +cdef class _Model_IA_SpatialMatrix_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialMatrix.fromPointer ( &(self.parent.IA[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialMatrix.fromPointer ( &(self.parent.IA[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialMatrix), "Invalid type! Expected SpatialMatrix, but got " + str(type(value[src_index])) + "." + self.parent.IA[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialMatrix), "Invalid type! Expected SpatialMatrix, but got " + str(type(value)) + "." + self.parent.IA[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.IA.size() + +cdef class _Model_pA_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.pA[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.pA[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.pA[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.pA[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.pA.size() + +cdef class _Model_U_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.U[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.U[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.U[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.U[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.U.size() + +cdef class _Model_f_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.f[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.f[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.f[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.f[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.f.size() + +cdef class _Model_I_SpatialRigidBodyInertia_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialRigidBodyInertia.fromPointer ( &(self.parent.I[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialRigidBodyInertia.fromPointer ( &(self.parent.I[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialRigidBodyInertia), "Invalid type! Expected SpatialRigidBodyInertia, but got " + str(type(value[src_index])) + "." + self.parent.I[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialRigidBodyInertia), "Invalid type! Expected SpatialRigidBodyInertia, but got " + str(type(value)) + "." + self.parent.I[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.I.size() + +cdef class _Model_Ic_SpatialRigidBodyInertia_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialRigidBodyInertia.fromPointer ( &(self.parent.Ic[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialRigidBodyInertia.fromPointer ( &(self.parent.Ic[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialRigidBodyInertia), "Invalid type! Expected SpatialRigidBodyInertia, but got " + str(type(value[src_index])) + "." + self.parent.Ic[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialRigidBodyInertia), "Invalid type! Expected SpatialRigidBodyInertia, but got " + str(type(value)) + "." + self.parent.Ic[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.Ic.size() + +cdef class _Model_hc_SpatialVector_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialVector.fromPointer ( &(self.parent.hc[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialVector.fromPointer ( &(self.parent.hc[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value[src_index])) + "." + self.parent.hc[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialVector), "Invalid type! Expected SpatialVector, but got " + str(type(value)) + "." + self.parent.hc[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.hc.size() + +cdef class _Model_X_lambda_SpatialTransform_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialTransform.fromPointer ( &(self.parent.X_lambda[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialTransform.fromPointer ( &(self.parent.X_lambda[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value[src_index])) + "." + self.parent.X_lambda[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value)) + "." + self.parent.X_lambda[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.X_lambda.size() + +cdef class _Model_X_base_SpatialTransform_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [SpatialTransform.fromPointer ( &(self.parent.X_base[i])) for i in xrange (*key.indices(len(self)))] + else: + return SpatialTransform.fromPointer ( &(self.parent.X_base[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value[src_index])) + "." + self.parent.X_base[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, SpatialTransform), "Invalid type! Expected SpatialTransform, but got " + str(type(value)) + "." + self.parent.X_base[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.X_base.size() + +cdef class _Model_mFixedBodies_FixedBody_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [FixedBody.fromPointer ( &(self.parent.mFixedBodies[i])) for i in xrange (*key.indices(len(self)))] + else: + return FixedBody.fromPointer ( &(self.parent.mFixedBodies[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], FixedBody), "Invalid type! Expected FixedBody, but got " + str(type(value[src_index])) + "." + self.parent.mFixedBodies[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, FixedBody), "Invalid type! Expected FixedBody, but got " + str(type(value)) + "." + self.parent.mFixedBodies[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.mFixedBodies.size() + +cdef class _Model_mBodies_Body_VectorWrapper: + cdef crbdl.Model *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [Body.fromPointer ( &(self.parent.mBodies[i])) for i in xrange (*key.indices(len(self)))] + else: + return Body.fromPointer ( &(self.parent.mBodies[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], Body), "Invalid type! Expected Body, but got " + str(type(value[src_index])) + "." + self.parent.mBodies[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, Body), "Invalid type! Expected Body, but got " + str(type(value)) + "." + self.parent.mBodies[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.mBodies.size() + + +cdef class Model: + cdef crbdl.Model *thisptr + cdef _Model_v_SpatialVector_VectorWrapper v + cdef _Model_a_SpatialVector_VectorWrapper a + cdef _Model_mJoints_Joint_VectorWrapper mJoints + cdef _Model_S_SpatialVector_VectorWrapper S + cdef _Model_X_J_SpatialTransform_VectorWrapper X_J + cdef _Model_v_J_SpatialVector_VectorWrapper v_J + cdef _Model_c_J_SpatialVector_VectorWrapper c_J + cdef _Model_X_T_SpatialTransform_VectorWrapper X_T + cdef _Model_c_SpatialVector_VectorWrapper c + cdef _Model_IA_SpatialMatrix_VectorWrapper IA + cdef _Model_pA_SpatialVector_VectorWrapper pA + cdef _Model_U_SpatialVector_VectorWrapper U + cdef _Model_f_SpatialVector_VectorWrapper f + cdef _Model_I_SpatialRigidBodyInertia_VectorWrapper I + cdef _Model_Ic_SpatialRigidBodyInertia_VectorWrapper Ic + cdef _Model_hc_SpatialVector_VectorWrapper hc + cdef _Model_X_lambda_SpatialTransform_VectorWrapper X_lambda + cdef _Model_X_base_SpatialTransform_VectorWrapper X_base + cdef _Model_mFixedBodies_FixedBody_VectorWrapper mFixedBodies + cdef _Model_mBodies_Body_VectorWrapper mBodies + + def __cinit__(self): + self.thisptr = new crbdl.Model() + self.v = _Model_v_SpatialVector_VectorWrapper ( self.thisptr) + self.a = _Model_a_SpatialVector_VectorWrapper ( self.thisptr) + self.mJoints = _Model_mJoints_Joint_VectorWrapper ( self.thisptr) + self.S = _Model_S_SpatialVector_VectorWrapper ( self.thisptr) + self.X_J = _Model_X_J_SpatialTransform_VectorWrapper ( self.thisptr) + self.v_J = _Model_v_J_SpatialVector_VectorWrapper ( self.thisptr) + self.c_J = _Model_c_J_SpatialVector_VectorWrapper ( self.thisptr) + self.X_T = _Model_X_T_SpatialTransform_VectorWrapper ( self.thisptr) + self.c = _Model_c_SpatialVector_VectorWrapper ( self.thisptr) + self.IA = _Model_IA_SpatialMatrix_VectorWrapper ( self.thisptr) + self.pA = _Model_pA_SpatialVector_VectorWrapper ( self.thisptr) + self.U = _Model_U_SpatialVector_VectorWrapper ( self.thisptr) + self.f = _Model_f_SpatialVector_VectorWrapper ( self.thisptr) + self.I = _Model_I_SpatialRigidBodyInertia_VectorWrapper ( self.thisptr) + self.Ic = _Model_Ic_SpatialRigidBodyInertia_VectorWrapper ( self.thisptr) + self.hc = _Model_hc_SpatialVector_VectorWrapper ( self.thisptr) + self.X_lambda = _Model_X_lambda_SpatialTransform_VectorWrapper ( self.thisptr) + self.X_base = _Model_X_base_SpatialTransform_VectorWrapper ( self.thisptr) + self.mFixedBodies = _Model_mFixedBodies_FixedBody_VectorWrapper ( self.thisptr) + self.mBodies = _Model_mBodies_Body_VectorWrapper ( self.thisptr) + + def __dealloc__(self): + del self.thisptr + + def __repr__(self): + return "rbdl.Model (0x{:0x})".format( self.thisptr) + + def AddBody (self, + parent_id, + SpatialTransform joint_frame not None, + Joint joint not None, + Body body not None, + string body_name = ""): + return self.thisptr.AddBody ( + parent_id, + joint_frame.thisptr[0], + joint.thisptr[0], + body.thisptr[0], + body_name + ) + + def AppendBody (self, + SpatialTransform joint_frame not None, + Joint joint not None, + Body body not None, + string body_name = ""): + return self.thisptr.AppendBody ( + joint_frame.thisptr[0], + joint.thisptr[0], + body.thisptr[0], + body_name + ) + + def SetQuaternion (self, + int body_id, + np.ndarray[double, ndim=1, mode="c"] quat, + np.ndarray[double, ndim=1, mode="c"] q): + quat_wrap = Quaternion.fromPythonArray (quat) + q_wrap = VectorNd.fromPythonArray (q) + self.thisptr.SetQuaternion (body_id, + (quat_wrap).thisptr[0], + (q_wrap).thisptr[0]) + for i in range(len(q)): + q[i] = q_wrap[i] + + def GetQuaternion (self, + int body_id, + np.ndarray[double, ndim=1, mode="c"] q): + return QuaternionToNumpy (self.thisptr.GetQuaternion(body_id, NumpyToVectorNd (q))) + + def GetBody (self, index): + return Body (address= &(self.thisptr.mBodies[index])) + + def GetParentBodyId (self, index): + return self.thisptr.GetParentBodyId(index) + + def GetBodyId (self, name): + return self.thisptr.GetBodyId(name) + + def GetBodyName (self, index): + return self.thisptr.GetBodyName(index) + + def IsBodyId (self, index): + return self.thisptr.IsBodyId(index) + + def IsFixedBodyId (self, index): + return self.thisptr.IsFixedBodyId(index) + + property dof_count: + def __get__ (self): + return self.thisptr.dof_count + + property q_size: + def __get__ (self): + return self.thisptr.q_size + + property qdot_size: + def __get__ (self): + return self.thisptr.qdot_size + + property previously_added_body_id: + def __get__ (self): + return self.thisptr.previously_added_body_id + + property gravity: + def __get__ (self): + return np.array ([ + self.thisptr.gravity[0], + self.thisptr.gravity[1], + self.thisptr.gravity[2] + ] + ) + def __set__ (self, values): + for i in range (0,3): + self.thisptr.gravity[i] = values[i] + + property v: + def __get__ (self): + return self.v + + property a: + def __get__ (self): + return self.a + + + property mJoints: + def __get__ (self): + return self.mJoints + + property S: + def __get__ (self): + return self.S + + property X_J: + def __get__ (self): + return self.X_J + + property v_J: + def __get__ (self): + return self.v_J + + property c_J: + def __get__ (self): + return self.c_J + + + property mJointUpdateOrder: + def __get__ (self): + return self.thisptr.mJointUpdateOrder + + property X_T: + def __get__ (self): + return self.X_T + + + property mFixedJointCount: + def __get__ (self): + return self.thisptr.mFixedJointCount + + # TODO + # multdof3_S + # multdof3_U + # multdof3_Dinv + # multdof3_u + + property multdof3_w_index: + def __get__ (self): + return self.thisptr.multdof3_w_index + + property c: + def __get__ (self): + return self.c + + property IA: + def __get__ (self): + return self.IA + + property pA: + def __get__ (self): + return self.pA + + property U: + def __get__ (self): + return self.U + + + # TODO + # d + # u + + property f: + def __get__ (self): + return self.f + + property I: + def __get__ (self): + return self.I + + property Ic: + def __get__ (self): + return self.Ic + + property hc: + def __get__ (self): + return self.hc + + + property X_lambda: + def __get__ (self): + return self.X_lambda + + property X_base: + def __get__ (self): + return self.X_base + + + property mFixedBodies: + def __get__ (self): + return self.mFixedBodies + + + property fixed_body_discriminator: + def __get__ (self): + return self.thisptr.fixed_body_discriminator + + property mBodies: + def __get__ (self): + return self.mBodies + + +############################## +# +# Constraint Types +# +############################## + +cdef class ConstraintSet + +cdef class _ConstraintSet_point_Vector3d_VectorWrapper: + cdef crbdl.ConstraintSet *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [Vector3d.fromPointer ( &(self.parent.point[i])) for i in xrange (*key.indices(len(self)))] + else: + return Vector3d.fromPointer ( &(self.parent.point[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], Vector3d), "Invalid type! Expected Vector3d, but got " + str(type(value[src_index])) + "." + self.parent.point[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, Vector3d), "Invalid type! Expected Vector3d, but got " + str(type(value)) + "." + self.parent.point[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.point.size() + +cdef class _ConstraintSet_normal_Vector3d_VectorWrapper: + cdef crbdl.ConstraintSet *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [Vector3d.fromPointer ( &(self.parent.normal[i])) for i in xrange (*key.indices(len(self)))] + else: + return Vector3d.fromPointer ( &(self.parent.normal[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], Vector3d), "Invalid type! Expected Vector3d, but got " + str(type(value[src_index])) + "." + self.parent.normal[i] = ( value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, Vector3d), "Invalid type! Expected Vector3d, but got " + str(type(value)) + "." + self.parent.normal[key] = ( value).thisptr[0] + + def __len__(self): + return self.parent.normal.size() + + +cdef class ConstraintSet: + cdef crbdl.ConstraintSet *thisptr + cdef _ConstraintSet_point_Vector3d_VectorWrapper point + cdef _ConstraintSet_normal_Vector3d_VectorWrapper normal + + def __cinit__(self): + self.thisptr = new crbdl.ConstraintSet() + self.point = _ConstraintSet_point_Vector3d_VectorWrapper ( self.thisptr) + self.normal = _ConstraintSet_normal_Vector3d_VectorWrapper ( self.thisptr) + + def __dealloc__(self): + del self.thisptr + + def __repr__(self): + return "rbdl.ConstraintSet (0x{:0x})".format( self.thisptr) + + def AddContactConstraint (self, + body_id not None, + body_point not None, + world_normal not None, + constraint_name = None, + normal_acceleration = 0.): + cdef crbdl.Vector3d c_body_point + cdef crbdl.Vector3d c_world_normal + cdef char* constraint_name_ptr + + for i in range (3): + c_body_point[i] = body_point[i] + c_world_normal[i] = world_normal[i] + + if constraint_name == None: + constraint_name_ptr = NULL + else: + constraint_name_ptr = constraint_name + + return self.thisptr.AddContactConstraint ( + body_id, + c_body_point, + c_world_normal, + constraint_name_ptr, + normal_acceleration + ) + + def AddLoopConstraint (self, + id_predecessor not None, + id_successor not None, + SpatialTransform X_predecessor not None, + SpatialTransform X_successor not None, + SpatialVector axis not None, + double T_stab_inv, + constraint_name = None): + cdef char* constraint_name_ptr + + if constraint_name == None: + constraint_name_ptr = NULL + else: + constraint_name_ptr = constraint_name + + return self.thisptr.AddLoopConstraint ( + id_predecessor, + id_successor, + X_predecessor.thisptr[0], + X_successor.thisptr[0], + axis.thisptr[0], + T_stab_inv, + constraint_name_ptr) + + def Bind (self, model): + return self.thisptr.Bind ((model).thisptr[0]) + + def size (self): + return self.thisptr.size() + + def clear (self): + self.thisptr.clear() + + property bound: + def __get__ (self): + return self.thisptr.bound + +# %VectorWrapperAddProperty (TYPE=string, MEMBER=name, PARENT=ConstraintSet)% + + property point: + def __get__ (self): + return self.point + + property normal: + def __get__ (self): + return self.normal + + +# property acceleration: +# def __get__(self): +# return VectorNd.fromPointer ( &(self.thisptr.acceleration)).toNumpy() +# def __set__(self, values): +# vec = VectorNd.fromPythonArray (values) +# self.thisptr.acceleration = (vec.thisptr[0]) + +############################## +# +# Kinematics.h +# +############################## + +def CalcBodyToBaseCoordinates (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcBodyToBaseCoordinates ( + model.thisptr[0], + NumpyToVectorNd (q), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcBaseToBodyCoordinates (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcBaseToBodyCoordinates ( + model.thisptr[0], + NumpyToVectorNd (q), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointVelocity (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcPointVelocity ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointAcceleration (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] qddot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return Vector3dToNumpy (crbdl.CalcPointAcceleration ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + NumpyToVectorNd (qddot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointVelocity6D (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return SpatialVectorToNumpy (crbdl.CalcPointVelocity6D ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointAcceleration6D (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] qddot, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + update_kinematics=True): + return SpatialVectorToNumpy (crbdl.CalcPointAcceleration6D ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + NumpyToVectorNd (qddot), + body_id, + NumpyToVector3d (body_point_position), + update_kinematics + )) + +def CalcPointJacobian (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + np.ndarray[double, ndim=2, mode="c"] G, + update_kinematics=True): + crbdl.CalcPointJacobianPtr ( + model.thisptr[0], + q.data, + body_id, + NumpyToVector3d (body_point_position), + G.data, + update_kinematics + ) + +def CalcPointJacobian6D (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + np.ndarray[double, ndim=2, mode="c"] G, + update_kinematics=True): + crbdl.CalcPointJacobian6DPtr ( + model.thisptr[0], + q.data, + body_id, + NumpyToVector3d (body_point_position), + G.data, + update_kinematics + ) + +def CalcBodySpatialJacobian(Model model, + np.ndarray[double, ndim=1, mode="c"] q, + int body_id, + np.ndarray[double, ndim=1, mode="c"] body_point_position, + np.ndarray[double, ndim=2, mode="c"] G, + update_kinematics=True): + crbdl.CalcBodySpatialJacobianPtr( + model.thisptr[0], + q.data, + body_id, + G.data, + update_kinematics + ) + +############################## +# +# rbdl_utils.h +# +############################## + +def CalcCenterOfMass (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] com, + np.ndarray[double, ndim=1, mode="c"] com_velocity=None, + np.ndarray[double, ndim=1, mode="c"] angular_momentum=None, + update_kinematics=True): + cdef double cmass + cdef crbdl.Vector3d c_com = crbdl.Vector3d() + cdef crbdl.Vector3d* c_com_vel_ptr # = crbdl.Vector3d() + cdef crbdl.Vector3d* c_ang_momentum_ptr # = crbdl.Vector3d() + + c_com_vel_ptr = NULL + c_ang_momentum_ptr = NULL + + if com_velocity != None: + c_com_vel_ptr = new crbdl.Vector3d() + + if angular_momentum != None: + c_ang_momentum_ptr = new crbdl.Vector3d() + + cmass = 0.0 + crbdl.CalcCenterOfMass ( + model.thisptr[0], + NumpyToVectorNd (q), + NumpyToVectorNd (qdot), + cmass, + c_com, + c_com_vel_ptr, + c_ang_momentum_ptr, + update_kinematics) + + com[0] = c_com[0] + com[1] = c_com[1] + com[2] = c_com[2] + + if com_velocity != None: + com_velocity[0] = c_com_vel_ptr.data()[0] + com_velocity[1] = c_com_vel_ptr.data()[1] + com_velocity[2] = c_com_vel_ptr.data()[2] + del c_com_vel_ptr + + if angular_momentum != None: + angular_momentum[0] = c_ang_momentum_ptr.data()[0] + angular_momentum[1] = c_ang_momentum_ptr.data()[1] + angular_momentum[2] = c_ang_momentum_ptr.data()[2] + del c_ang_momentum_ptr + + return cmass + +############################## +# +# Dynamics.h +# +############################## + +def InverseDynamics (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] qddot, + np.ndarray[double, ndim=1, mode="c"] tau): + crbdl.InverseDynamicsPtr (model.thisptr[0], + q.data, + qdot.data, + qddot.data, + tau.data, + NULL + ) + +def NonlinearEffects (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] tau): + crbdl.NonlinearEffectsPtr (model.thisptr[0], + q.data, + qdot.data, + tau.data + ) + +def CompositeRigidBodyAlgorithm (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=2, mode="c"] H, + update_kinematics=True): + crbdl.CompositeRigidBodyAlgorithmPtr (model.thisptr[0], + q.data, + H.data, + update_kinematics); + +def ForwardDynamics (Model model, + np.ndarray[double, ndim=1, mode="c"] q, + np.ndarray[double, ndim=1, mode="c"] qdot, + np.ndarray[double, ndim=1, mode="c"] tau, + np.ndarray[double, ndim=1, mode="c"] qddot): + crbdl.ForwardDynamicsPtr (model.thisptr[0], + q.data, + qdot.data, + tau.data, + qddot.data, + NULL + ) + +def loadModel ( + filename, + **kwargs + ): + verbose = False + if "verbose" in kwargs.keys(): + verbose=kwargs["verbose"] + + floating_base = False + if "floating_base" in kwargs.keys(): + floating_base=kwargs["floating_base"] + + result = Model() + if crbdl.rbdl_loadmodel (filename, result.thisptr, floating_base, verbose): + return result + + print ("Error loading model {}!".format (filename)) + return None + diff --git a/3rdparty/rbdl/python/rbdl_loadmodel.cc b/3rdparty/rbdl/python/rbdl_loadmodel.cc new file mode 100644 index 0000000..04f0941 --- /dev/null +++ b/3rdparty/rbdl/python/rbdl_loadmodel.cc @@ -0,0 +1,42 @@ +#include + +#ifdef RBDL_BUILD_ADDON_LUAMODEL +#include +#endif + +#ifdef RBDL_BUILD_ADDON_URDFREADER +#include +#endif + +#include +#include + +using namespace RigidBodyDynamics; +using namespace std; + +bool rbdl_loadmodel (const char* filename, Model* model, bool floating_base=false, bool verbose=false) { + string fname (filename); + + for (size_t i = 0; i < fname.size(); i++) { + fname[i] = tolower(fname[i]); + } + + bool result = false; + if (fname.substr (fname.size() - 4, 4) == ".lua") { +#ifdef RBDL_BUILD_ADDON_LUAMODEL + result = Addons::LuaModelReadFromFile (filename, model, verbose); +#else + cerr << "Error: RBDL Addon LuaModel not enabled!" << endl; +#endif + } else if (fname.substr (fname.size() - 5, 5) == ".urdf") { +#ifdef RBDL_BUILD_ADDON_URDFREADER + result = Addons::URDFReadFromFile (filename, model, floating_base, verbose); +#else + cerr << "Error: RBDL Addon URDFReader not enabled!" << endl; +#endif + } else { + cerr << "Error: Cannot identify model type from filename '" << filename << "'!" << endl; + } + + return result; +} diff --git a/3rdparty/rbdl/python/rbdl_ptr_functions.h b/3rdparty/rbdl/python/rbdl_ptr_functions.h new file mode 100644 index 0000000..d6fbb82 --- /dev/null +++ b/3rdparty/rbdl/python/rbdl_ptr_functions.h @@ -0,0 +1,637 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2015 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + * + * This file defines functions that allows calling of the RBDL algorithms + * by providing input and output as raw double arrays. It eliminates the + * need of copying from Numpy values into temporary RBDL (C++) vectors and + * matrices. However it requires C++11 and must be compiled with -std=c++11 + * (or -std=c++0x on older compilers). + */ + +#include +#include + +namespace RigidBodyDynamics { + +namespace Math { + +// PTR_DATA_ROW_MAJOR : +// Specifies whether the data that is provided via raw double pointers is +// stored as row major. Eigen uses column major by default and therefore +// this has to be properly mapped. +#define PTR_DATA_ROW_MAJOR 1 + +#ifdef RBDL_USE_SIMPLE_MATH + typedef VectorNd VectorNdRef; + typedef MatrixNd MatrixNdRef; +#else + typedef Eigen::Ref VectorNdRef; + +#ifdef PTR_DATA_ROW_MAJOR + typedef Eigen::Matrix MatrixNdRowMaj; + typedef Eigen::Ref MatrixNdRef; +#else + typedef Eigen::Ref MatrixNdRef; +#endif + +#endif + +RBDL_DLLAPI inline VectorNdRef VectorFromPtr (double *ptr, unsigned int n) { +#ifdef RBDL_USE_SIMPLE_MATH + return SimpleMath::Map (ptr, n, 1); +#elif defined EIGEN_CORE_H + return Eigen::Map (ptr, n, 1); +#else + std::cerr << __func__ << " not defined for used math library!" << std::endl; + abort(); + return VectorNd::Constant (1,1./0.); +#endif +} + +RBDL_DLLAPI inline MatrixNdRef MatrixFromPtr (double *ptr, unsigned int rows, unsigned int cols, bool row_major = true) { +#ifdef RBDL_USE_SIMPLE_MATH + return SimpleMath::Map (ptr, rows, cols); +#elif defined EIGEN_CORE_H +#ifdef PTR_DATA_ROW_MAJOR + return Eigen::Map (ptr, rows, cols); +#else + return Eigen::Map (ptr, rows, cols); +#endif +#else + std::cerr << __func__ << " not defined for used math library!" << std::endl; + abort(); + return MatrixNd::Constant (1,1, 1./0.); +#endif +} + +} + +RBDL_DLLAPI +void UpdateKinematicsCustomPtr (Model &model, + const double *q_ptr, + const double *qdot_ptr, + const double *qddot_ptr + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + unsigned int i; + + if (q_ptr) { + VectorNdRef Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + + VectorNd QDot_zero (VectorNd::Zero (model.q_size)); + + jcalc (model, i, (Q), QDot_zero); + + model.X_lambda[i] = model.X_J[i] * model.X_T[i]; + + if (lambda != 0) { + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + } else { + model.X_base[i] = model.X_lambda[i]; + } + } + } + + if (qdot_ptr) { + VectorNdRef Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + VectorNdRef QDot = VectorFromPtr(const_cast(qdot_ptr), model.q_size); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, Q, QDot); + + if (lambda != 0) { + model.v[i] = model.X_lambda[i].apply(model.v[lambda]) + model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + } else { + model.v[i] = model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + } + // LOG << "v[" << i << "] = " << model.v[i].transpose() << std::endl; + } + } + + if (qddot_ptr) { + VectorNdRef QDDot = VectorFromPtr(const_cast(qddot_ptr), model.q_size); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + model.c[i]; + } else { + model.a[i] = model.c[i]; + } + + if (model.mJoints[i].mDoFCount == 3) { + Vector3d omegadot_temp ((QDDot)[q_index], (QDDot)[q_index + 1], (QDDot)[q_index + 2]); + model.a[i] = model.a[i] + model.multdof3_S[i] * omegadot_temp; + } else { + model.a[i] = model.a[i] + model.S[i] * (QDDot)[q_index]; + } + } + } +} + +RBDL_DLLAPI +void CalcPointJacobianPtr ( + Model &model, + const double *q_ptr, + unsigned int body_id, + const Math::Vector3d &point_position, + double * G_ptr, + bool update_kinematics + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustomPtr (model, q_ptr, NULL, NULL); + } + + VectorNdRef Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + MatrixNdRef G = MatrixFromPtr(const_cast(G_ptr), 3, model.qdot_size); + + SpatialTransform point_trans = SpatialTransform (Matrix3d::Identity(), CalcBodyToBaseCoordinates (model, Q, body_id, point_position, false)); + + assert (G.rows() == 3 && G.cols() == model.qdot_size ); + + unsigned int reference_body_id = body_id; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + } + + unsigned int j = reference_body_id; + + // e[j] is set to 1 if joint j contributes to the jacobian that we are + // computing. For all other joints the column will be zero. + while (j != 0) { + unsigned int q_index = model.mJoints[j].q_index; + + if (model.mJoints[j].mDoFCount == 3) { + G.block(0, q_index, 3, 3) = ((point_trans * model.X_base[j].inverse()).toMatrix() * model.multdof3_S[j]).block(3,0,3,3); + } else { + G.block(0,q_index, 3, 1) = point_trans.apply(model.X_base[j].inverse().apply(model.S[j])).block(3,0,3,1); + } + + j = model.lambda[j]; + } +} + +RBDL_DLLAPI +void CalcPointJacobian6DPtr ( + Model &model, + const double *q_ptr, + unsigned int body_id, + const Math::Vector3d &point_position, + double *G_ptr, + bool update_kinematics + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustomPtr (model, q_ptr, NULL, NULL); + } + + VectorNdRef Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + MatrixNdRef G = MatrixFromPtr(const_cast(G_ptr), 6, model.qdot_size); + + SpatialTransform point_trans = SpatialTransform (Matrix3d::Identity(), CalcBodyToBaseCoordinates (model, Q, body_id, point_position, false)); + + assert (G.rows() == 6 && G.cols() == model.qdot_size ); + + unsigned int reference_body_id = body_id; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + } + + unsigned int j = reference_body_id; + + while (j != 0) { + unsigned int q_index = model.mJoints[j].q_index; + + if (model.mJoints[j].mDoFCount == 3) { + G.block(0, q_index, 6, 3) = ((point_trans * model.X_base[j].inverse()).toMatrix() * model.multdof3_S[j]).block(0,0,6,3); + } else { + G.block(0,q_index, 6, 1) = point_trans.apply(model.X_base[j].inverse().apply(model.S[j])).block(0,0,6,1); + } + + j = model.lambda[j]; + } +} + +RBDL_DLLAPI +void CalcBodySpatialJacobianPtr ( + Model &model, + const double *q_ptr, + unsigned int body_id, + double *G_ptr, + bool update_kinematics + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustomPtr (model, q_ptr, NULL, NULL); + } + + MatrixNdRef G = MatrixFromPtr(const_cast(G_ptr), 6, model.q_size); + + assert (G.rows() == 6 && G.cols() == model.qdot_size ); + + unsigned int reference_body_id = body_id; + + SpatialTransform base_to_body; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + base_to_body = model.mFixedBodies[fbody_id].mParentTransform * model.X_base[reference_body_id]; + } else { + base_to_body = model.X_base[reference_body_id]; + } + + unsigned int j = reference_body_id; + + while (j != 0) { + unsigned int q_index = model.mJoints[j].q_index; + + if (model.mJoints[j].mDoFCount == 3) { + G.block(0,q_index,6,3) = (base_to_body * model.X_base[j].inverse()).toMatrix() * model.multdof3_S[j]; + } else { + G.block(0,q_index,6,1) = base_to_body.apply(model.X_base[j].inverse().apply(model.S[j])); + } + + j = model.lambda[j]; + } +} + +RBDL_DLLAPI +void InverseDynamicsPtr ( + Model &model, + const double *q_ptr, + const double *qdot_ptr, + const double *qddot_ptr, + const double *tau_ptr, + std::vector *f_ext + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + VectorNdRef Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + VectorNdRef QDot = VectorFromPtr(const_cast(qdot_ptr), model.q_size); + VectorNdRef QDDot = VectorFromPtr(const_cast(qddot_ptr), model.q_size); + VectorNdRef Tau = VectorFromPtr(const_cast(tau_ptr), model.q_size); + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0].set (0., 0., 0., -model.gravity[0], -model.gravity[1], -model.gravity[2]); + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, Q, QDot); + + if (lambda != 0) { + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + } else { + model.X_base[i] = model.X_lambda[i]; + } + + model.v[i] = model.X_lambda[i].apply(model.v[lambda]) + model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + + if (model.mJoints[i].mDoFCount == 3) { + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + model.c[i] + model.multdof3_S[i] * Vector3d (QDDot[q_index], QDDot[q_index + 1], QDDot[q_index + 2]); + } else { + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + model.c[i] + model.S[i] * QDDot[q_index]; + } + + if (!model.mBodies[i].mIsVirtual) { + model.f[i] = model.I[i] * model.a[i] + crossf(model.v[i],model.I[i] * model.v[i]); + } else { + model.f[i].setZero(); + } + + if (f_ext != NULL && (*f_ext)[i] != SpatialVector::Zero()) + model.f[i] -= model.X_base[i].toMatrixAdjoint() * (*f_ext)[i]; + } + + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + if (model.mJoints[i].mDoFCount == 3) { + Tau.block<3,1>(model.mJoints[i].q_index, 0) = model.multdof3_S[i].transpose() * model.f[i]; + } else { + Tau[model.mJoints[i].q_index] = model.S[i].dot(model.f[i]); + } + + if (model.lambda[i] != 0) { + model.f[model.lambda[i]] = model.f[model.lambda[i]] + model.X_lambda[i].applyTranspose(model.f[i]); + } + } +} + +RBDL_DLLAPI +void NonlinearEffectsPtr ( + Model &model, + const double *q_ptr, + const double *qdot_ptr, + const double *tau_ptr + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + VectorNdRef Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + VectorNdRef QDot = VectorFromPtr(const_cast(qdot_ptr), model.q_size); + VectorNdRef Tau = VectorFromPtr(const_cast(tau_ptr), model.q_size); + + SpatialVector spatial_gravity (0., 0., 0., -model.gravity[0], -model.gravity[1], -model.gravity[2]); + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0] = spatial_gravity; + + for (unsigned int i = 1; i < model.mJointUpdateOrder.size(); i++) { + jcalc (model, model.mJointUpdateOrder[i], Q, QDot); + } + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + if (model.lambda[i] == 0) { + model.v[i] = model.v_J[i]; + model.a[i] = model.X_lambda[i].apply(spatial_gravity); + } else { + model.v[i] = model.X_lambda[i].apply(model.v[model.lambda[i]]) + model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + model.a[i] = model.X_lambda[i].apply(model.a[model.lambda[i]]) + model.c[i]; + } + + if (!model.mBodies[i].mIsVirtual) { + model.f[i] = model.I[i] * model.a[i] + crossf(model.v[i],model.I[i] * model.v[i]); + } else { + model.f[i].setZero(); + } + } + + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + if (model.mJoints[i].mDoFCount == 3) { + Tau.block<3,1>(model.mJoints[i].q_index, 0) = model.multdof3_S[i].transpose() * model.f[i]; + } else { + Tau[model.mJoints[i].q_index] = model.S[i].dot(model.f[i]); + } + + if (model.lambda[i] != 0) { + model.f[model.lambda[i]] = model.f[model.lambda[i]] + model.X_lambda[i].applyTranspose(model.f[i]); + } + } +} + +RBDL_DLLAPI +inline void CompositeRigidBodyAlgorithmPtr ( + Model& model, + const double *q_ptr, + double *H_ptr, + bool update_kinematics = true + ) { + using namespace RigidBodyDynamics::Math; + + VectorNdRef&& Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + MatrixNdRef&& H = MatrixFromPtr(H_ptr, model.qdot_size, model.qdot_size); + + assert (H.rows() == model.dof_count && H.cols() == model.dof_count); + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + if (update_kinematics) { + jcalc_X_lambda_S (model, i, Q); + } + model.Ic[i] = model.I[i]; + } + + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + if (model.lambda[i] != 0) { + model.Ic[model.lambda[i]] = model.Ic[model.lambda[i]] + model.X_lambda[i].applyTranspose(model.Ic[i]); + } + + unsigned int dof_index_i = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 3) { + Matrix63 F_63 = model.Ic[i].toMatrix() * model.multdof3_S[i]; + H.block<3,3>(dof_index_i, dof_index_i) = model.multdof3_S[i].transpose() * F_63; + + unsigned int j = i; + unsigned int dof_index_j = dof_index_i; + + while (model.lambda[j] != 0) { + F_63 = model.X_lambda[j].toMatrixTranspose() * (F_63); + j = model.lambda[j]; + dof_index_j = model.mJoints[j].q_index; + + if (model.mJoints[j].mDoFCount == 3) { + Matrix3d H_temp2 = F_63.transpose() * (model.multdof3_S[j]); + + H.block<3,3>(dof_index_i,dof_index_j) = H_temp2; + H.block<3,3>(dof_index_j,dof_index_i) = H_temp2.transpose(); + } else { + Vector3d H_temp2 = F_63.transpose() * (model.S[j]); + + H.block<3,1>(dof_index_i,dof_index_j) = H_temp2; + H.block<1,3>(dof_index_j,dof_index_i) = H_temp2.transpose(); + } + } + } else { + SpatialVector F = model.Ic[i] * model.S[i]; + H(dof_index_i, dof_index_i) = model.S[i].dot(F); + + unsigned int j = i; + unsigned int dof_index_j = dof_index_i; + + while (model.lambda[j] != 0) { + F = model.X_lambda[j].applyTranspose(F); + j = model.lambda[j]; + dof_index_j = model.mJoints[j].q_index; + + if (model.mJoints[j].mDoFCount == 3) { + Vector3d H_temp2 = (F.transpose() * model.multdof3_S[j]).transpose(); + + LOG << F.transpose() << std::endl << model.multdof3_S[j] << std::endl; + LOG << H_temp2.transpose() << std::endl; + + H.block<1,3>(dof_index_i,dof_index_j) = H_temp2.transpose(); + H.block<3,1>(dof_index_j,dof_index_i) = H_temp2; + } else { + H(dof_index_i,dof_index_j) = F.dot(model.S[j]); + H(dof_index_j,dof_index_i) = H(dof_index_i,dof_index_j); + } + } + } + } +} + +RBDL_DLLAPI +void ForwardDynamicsPtr ( + Model &model, + const double *q_ptr, + const double *qdot_ptr, + const double *tau_ptr, + const double *qddot_ptr, + std::vector *f_ext + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + using namespace RigidBodyDynamics::Math; + + VectorNdRef&& Q = VectorFromPtr(const_cast(q_ptr), model.q_size); + VectorNdRef&& QDot = VectorFromPtr(const_cast(qdot_ptr), model.q_size); + VectorNdRef&& QDDot = VectorFromPtr(const_cast(qddot_ptr), model.q_size); + VectorNdRef&& Tau = VectorFromPtr(const_cast(tau_ptr), model.q_size); + + SpatialVector spatial_gravity (0., 0., 0., model.gravity[0], model.gravity[1], model.gravity[2]); + + unsigned int i = 0; + + LOG << "Q = " << Q.transpose() << std::endl; + LOG << "QDot = " << QDot.transpose() << std::endl; + LOG << "Tau = " << Tau.transpose() << std::endl; + LOG << "---" << std::endl; + + // Reset the velocity of the root body + model.v[0].setZero(); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, Q, QDot); + + if (lambda != 0) + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + else + model.X_base[i] = model.X_lambda[i]; + + model.v[i] = model.X_lambda[i].apply( model.v[lambda]) + model.v_J[i]; + + /* + LOG << "X_J (" << i << "):" << std::endl << X_J << std::endl; + LOG << "v_J (" << i << "):" << std::endl << v_J << std::endl; + LOG << "v_lambda" << i << ":" << std::endl << model.v.at(lambda) << std::endl; + LOG << "X_base (" << i << "):" << std::endl << model.X_base[i] << std::endl; + LOG << "X_lambda (" << i << "):" << std::endl << model.X_lambda[i] << std::endl; + LOG << "SpatialVelocity (" << i << "): " << model.v[i] << std::endl; + */ + + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + model.I[i].setSpatialMatrix (model.IA[i]); + + model.pA[i] = crossf(model.v[i],model.I[i] * model.v[i]); + + if (f_ext != NULL && (*f_ext)[i] != SpatialVector::Zero()) { + LOG << "External force (" << i << ") = " << model.X_base[i].toMatrixAdjoint() * (*f_ext)[i] << std::endl; + model.pA[i] -= model.X_base[i].toMatrixAdjoint() * (*f_ext)[i]; + } + } + +// ClearLogOutput(); + + LOG << "--- first loop ---" << std::endl; + + for (i = model.mBodies.size() - 1; i > 0; i--) { + unsigned int q_index = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 3) { + model.multdof3_U[i] = model.IA[i] * model.multdof3_S[i]; +#ifdef EIGEN_CORE_H + model.multdof3_Dinv[i] = (model.multdof3_S[i].transpose() * model.multdof3_U[i]).inverse().eval(); +#else + model.multdof3_Dinv[i] = (model.multdof3_S[i].transpose() * model.multdof3_U[i]).inverse(); +#endif + Vector3d tau_temp (Tau[q_index], Tau[q_index + 1], Tau[q_index + 2]); + + model.multdof3_u[i] = tau_temp - model.multdof3_S[i].transpose() * model.pA[i]; + +// LOG << "multdof3_u[" << i << "] = " << model.multdof3_u[i].transpose() << std::endl; + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] - model.multdof3_U[i] * model.multdof3_Dinv[i] * model.multdof3_U[i].transpose(); + SpatialVector pa = model.pA[i] + Ia * model.c[i] + model.multdof3_U[i] * model.multdof3_Dinv[i] * model.multdof3_u[i]; +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += model.X_lambda[i].toMatrixTranspose() * Ia * model.X_lambda[i].toMatrix(); + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] += model.X_lambda[i].toMatrixTranspose() * Ia * model.X_lambda[i].toMatrix(); + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " << model.pA[lambda].transpose() << std::endl; + } + } else { + model.U[i] = model.IA[i] * model.S[i]; + model.d[i] = model.S[i].dot(model.U[i]); + model.u[i] = Tau[q_index] - model.S[i].dot(model.pA[i]); +// LOG << "u[" << i << "] = " << model.u[i] << std::endl; + + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] - model.U[i] * (model.U[i] / model.d[i]).transpose(); + SpatialVector pa = model.pA[i] + Ia * model.c[i] + model.U[i] * model.u[i] / model.d[i]; +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += model.X_lambda[i].toMatrixTranspose() * Ia * model.X_lambda[i].toMatrix(); + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] += model.X_lambda[i].toMatrixTranspose() * Ia * model.X_lambda[i].toMatrix(); + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " << model.pA[lambda].transpose() << std::endl; + } + } + } + +// ClearLogOutput(); + + model.a[0] = spatial_gravity * -1.; + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + SpatialTransform X_lambda = model.X_lambda[i]; + + model.a[i] = X_lambda.apply(model.a[lambda]) + model.c[i]; + LOG << "a'[" << i << "] = " << model.a[i].transpose() << std::endl; + + if (model.mJoints[i].mDoFCount == 3) { + Vector3d qdd_temp = model.multdof3_Dinv[i] * (model.multdof3_u[i] - model.multdof3_U[i].transpose() * model.a[i]); + QDDot[q_index] = qdd_temp[0]; + QDDot[q_index + 1] = qdd_temp[1]; + QDDot[q_index + 2] = qdd_temp[2]; + model.a[i] = model.a[i] + model.multdof3_S[i] * qdd_temp; + } else { + QDDot[q_index] = (1./model.d[i]) * (model.u[i] - model.U[i].dot(model.a[i])); + model.a[i] = model.a[i] + model.S[i] * QDDot[q_index]; + } + } + + LOG << "QDDot = " << QDDot.transpose() << std::endl; +} + +} diff --git a/3rdparty/rbdl/python/setup.py.cmake b/3rdparty/rbdl/python/setup.py.cmake new file mode 100755 index 0000000..1b7fe68 --- /dev/null +++ b/3rdparty/rbdl/python/setup.py.cmake @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +from distutils.core import setup +from distutils.extension import Extension +from Cython.Distutils import build_ext +from Cython.Build import cythonize + +import os +import numpy as np + +BASEDIR = os.path.dirname(os.path.abspath(__file__)) + +extra_params = {} +extra_params['include_dirs'] = [ + '/usr/include', + BASEDIR, + np.get_include(), + '@RBDL_INCLUDE_DIR@', + '@EIGEN3_INCLUDE_DIR@', + '@CMAKE_CURRENT_SOURCE_DIR@', + '@RBDL_SOURCE_DIR@/include', + '@RBDL_BINARY_DIR@/include', + '/usr/include/eigen3/' +] + +extra_params['language'] = 'c++' +extra_params['extra_compile_args'] = ["-O3", "-Wno-unused-variable", "-std=c++11"] +extra_params['libraries'] = ['rbdl'] +extra_params['library_dirs'] = [ + '${CMAKE_CURRENT_BINARY_DIR}/../', + '${CMAKE_INSTALL_PREFIX}/lib/', + '/usr/lib', + BASEDIR + ] +extra_params['extra_link_args'] = [ + "-Wl,-O1", + "-Wl,--as-needed", + ] + +if os.name == 'posix': + extra_params['runtime_library_dirs'] = extra_params['library_dirs'] + +ext_modules = [ + Extension("rbdl", ["crbdl.pxd", "rbdl.pyx"], **extra_params), +] + +setup( + name='rbdl', + author='Martin Felis', + author_email='martin@fysx.org', + description='Python wrapper for RBDL - the Rigid Body Dynamics Library', + license='zlib', + version='${RBDL_VERSION_MAJOR}.${RBDL_VERSION_MINOR}.${RBDL_VERSION_PATCH}', + url='http://rbdl.bitbucket.org/', + cmdclass={'build_ext': build_ext}, + ext_modules=cythonize(ext_modules), +) diff --git a/3rdparty/rbdl/python/test_wrapper.py b/3rdparty/rbdl/python/test_wrapper.py new file mode 100755 index 0000000..18796c9 --- /dev/null +++ b/3rdparty/rbdl/python/test_wrapper.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# +# RBDL - Rigid Body Dynamics Library +# Copyright (c) 2011-2015 Martin Felis +# +# Licensed under the zlib license. See LICENSE for more details. + +import unittest + +import math +import numpy as np +from numpy.testing import * +import rbdl + +class JointTests (unittest.TestCase): + def test_JointConstructorAxesSimple(self): + axis = np.asarray([[1., 0., 0., 0., 0., 0.]]) + joint_rot_x = rbdl.Joint.fromJointAxes (axis) + + assert_equal (joint_rot_x.getJointAxis(0), axis[0]) + + def test_JointConstructorAxes6DoF(self): + axis = np.asarray([ + [1., 0., 0., 0., 0., 0.], + [0., 1., 0., 0., 0., 0.], + [0., 0., 1., 0., 0., 0.], + [0., 0., 0., 1., 0., 0.], + [0., 0., 0., 0., 1., 0.], + [0., 0., 0., 0., 0., 1.], + ]) + joint = rbdl.Joint.fromJointAxes (axis) + + for i in range (axis.shape[0]): + assert_equal (joint.getJointAxis(i), axis[i]) + +class SampleModel3R (unittest.TestCase): + """ Example planar triple pendulum + + - All joints along the positive Z axis in rest position + - All joints revolute y joints + - all "links" are 1 unit length + """ + def setUp(self): + self.model = rbdl.Model() + joint_rot_y = rbdl.Joint.fromJointType ("JointTypeRevoluteY") + self.body = rbdl.Body.fromMassComInertia (1., np.array([0., 0.0, 0.5]), np.eye(3) * + 0.05) + self.xtrans = rbdl.SpatialTransform() + self.xtrans.r = np.array([0., 0., 1.]) + + self.body_1 = self.model.AppendBody (rbdl.SpatialTransform(), joint_rot_y, self.body) + self.body_2 = self.model.AppendBody (self.xtrans, joint_rot_y, self.body) + self.body_3 = self.model.AppendBody (self.xtrans, joint_rot_y, self.body) + + self.q = np.zeros (self.model.q_size) + self.qdot = np.zeros (self.model.qdot_size) + self.qddot = np.zeros (self.model.qdot_size) + self.tau = np.zeros (self.model.qdot_size) + + def test_CoordinateTransformBodyBase (self): + """ + Checks whether CalcBodyToBaseCoordinates and CalcBaseToBodyCoordinates + give the right results. + """ + q = np.random.rand(self.model.q_size) + point_local = np.array ([1., 2., 3.]) + point_base = rbdl.CalcBodyToBaseCoordinates ( + self.model, + q, + self.body_3, + point_local) + point_local_2 = rbdl.CalcBaseToBodyCoordinates ( + self.model, + q, + self.body_3, + point_base) + + assert_almost_equal (point_local, point_local_2) + + def test_CalcPointVelocity (self): + """ + Checks whether CalcBodyToBaseCoordinates and CalcBaseToBodyCoordinates + give the right results. + """ + q = np.zeros(self.model.q_size) + qdot = np.zeros(self.model.q_size) + qdot[0] = 1. + point_local = np.array ([0., 0., 0.]) + point_vel = rbdl.CalcPointVelocity ( + self.model, + q, + qdot, + self.body_3, + point_local + ) + + assert_almost_equal (np.array([2., 0., 0.]), point_vel) + + def test_CalcCenterOfMass (self): + """ Tests calculation of center of mass + TODO: add checks for angular momentum + """ + + com = np.array ([-1., -1., -1.]) + com_vel = np.array([-2., -2., -2.]) + ang_mom = np.array([-3., -3., -3.]) + self.qdot[0] = 1. + + mass = rbdl.CalcCenterOfMass ( + self.model, + self.q, + self.qdot, + com, + None, + None + ) + self.assertEqual (3, mass) + assert_almost_equal (np.array([0., 0., 1.5]), com) + assert_almost_equal (np.array([0., 0., 1.5]), com) + + mass = rbdl.CalcCenterOfMass ( + self.model, + self.q, + self.qdot, + com, + com_vel, + None + ) + self.assertEqual (3, mass) + assert_almost_equal (np.array([0., 0., 1.5]), com) + assert_almost_equal (np.array([1.5, 0., 0.0]), com_vel) + + mass = rbdl.CalcCenterOfMass ( + self.model, + self.q, + self.qdot, + com, + com_vel, + ang_mom + ) + self.assertEqual (3, mass) + assert_almost_equal (np.array([0., 0., 1.5]), com) + + def test_DynamicsConsistency (self): + """ Checks whether forward and inverse dynamics are consistent """ + q = np.random.rand (self.model.q_size) + qdot = np.random.rand (self.model.q_size) + qddot = np.random.rand (self.model.q_size) + + tau = np.random.rand (self.model.q_size) + + rbdl.ForwardDynamics ( + self.model, + q, + qdot, + tau, + qddot) + + tau_id = np.zeros ((self.model.q_size)) + rbdl.InverseDynamics ( + self.model, + q, + qdot, + qddot, + tau_id + ) + + assert_almost_equal (tau, tau_id) + + def test_NonlinearEffectsConsistency (self): + """ Checks whether NonlinearEffects is consistent with InverseDynamics """ + q = np.random.rand (self.model.q_size) + qdot = np.random.rand (self.model.q_size) + + nle_id = np.random.rand (self.model.q_size) + + rbdl.InverseDynamics( + self.model, + q, + qdot, + np.zeros (self.model.qdot_size), + nle_id) + + nle = np.zeros ((self.model.q_size)) + rbdl.NonlinearEffects ( + self.model, + q, + qdot, + nle + ) + + assert_almost_equal (nle_id, nle) + + def test_CalcPointJacobian (self): + """ Computes point Jacobian and checks whether G * qdot is consistent + with CalcPointVelocity. """ + q = np.zeros (self.model.q_size) + G = np.zeros ([3, self.model.q_size]) + point_coords = np.array ([0., 0., 1.]) + + rbdl.CalcPointJacobian ( + self.model, + q, + self.body_3, + point_coords, + G + ) + + qdot = np.ones(self.model.qdot_size) + point_vel = rbdl.CalcPointVelocity ( + self.model, + q, + qdot, + self.body_3, + point_coords + ) + + jac_point_vel = np.dot (G, qdot) + assert_almost_equal (jac_point_vel, point_vel) + + def test_CalcPointJacobianNonSquare (self): + """ Computes point Jacobian and checks whether G * qdot is consistent + with CalcPointVelocity. """ + + self.model = rbdl.Model() + joint_trans_xyz = rbdl.Joint.fromJointType ("JointTypeTranslationXYZ") + + self.body_1 = self.model.AppendBody (rbdl.SpatialTransform(), + joint_trans_xyz, self.body) + + self.body_4 = self.model.AppendBody (rbdl.SpatialTransform(), + joint_trans_xyz, self.body) + + point_coords = np.array ([0., 0., 1.]) + q = np.zeros (self.model.q_size) + G = np.zeros ([3, self.model.q_size]) + + rbdl.CalcPointJacobian ( + self.model, + q, + self.body_4, + point_coords, + G + ) + + qdot = np.ones(self.model.qdot_size) + jac_point_vel = np.dot (G, qdot) + + point_vel = rbdl.CalcPointVelocity ( + self.model, + q, + qdot, + self.body_4, + point_coords + ) + + assert_almost_equal (jac_point_vel, point_vel) + +class FloatingBaseModel (unittest.TestCase): + """ Model with a floating base + """ + def setUp(self): + self.model = rbdl.Model() + joint_rot_y = rbdl.Joint.fromJointType ("JointTypeFloatingBase") + self.body = rbdl.Body.fromMassComInertia (1., np.array([0., 0.0, 0.5]), np.eye(3) * + 0.05) + self.xtrans = rbdl.SpatialTransform() + self.xtrans.r = np.array([0., 0., 0.]) + + self.body_1 = self.model.AppendBody (rbdl.SpatialTransform(), joint_rot_y, self.body) + + self.q = np.zeros (self.model.q_size) + self.qdot = np.zeros (self.model.qdot_size) + self.qddot = np.zeros (self.model.qdot_size) + self.tau = np.zeros (self.model.qdot_size) + + def test_Dimensions (self): + """ + Checks whether the dimensions of q and qdot are correct + """ + + q = np.random.rand(self.model.q_size) + self.assertEqual (7, self.model.q_size) + self.assertEqual (6, self.model.qdot_size) + + def test_SetQuaternion (self): + mat = np.asarray ([[0., 1., 0.], [-1., 0., 0.], [0., 0., 1.]]) + rbdl_quat = rbdl.Quaternion.fromPythonMatrix (mat) + ref_q = self.q.copy() + + self.model.SetQuaternion (2, rbdl_quat.toNumpy(), self.q) + + ref_q[3:6] = rbdl_quat[0:3] + ref_q[-1] = rbdl_quat[3] + + assert_array_equal (ref_q, self.q) + + def test_GetQuaternion (self): + mat = np.asarray ([[0., 1., 0.], [-1., 0., 0.], [0., 0., 1.]]) + rbdl_quat = rbdl.Quaternion.fromPythonMatrix (mat) + + self.assertEqual (4, len(rbdl_quat)) + self.q[5] = math.sqrt(2.) * 0.5 + self.q[6] = math.sqrt(2.) * 0.5 + + ref_quat = [0., 0., math.sqrt(2.) * 0.5, math.sqrt(2.) * 0.5] + quat = self.model.GetQuaternion (2, self.q) + + assert_array_equal (np.asarray(ref_quat), quat) + +class ConstraintSetTests (unittest.TestCase): + def test_Simple (self): + # only tests whether the API seems to work. No functional + # tests yet. + + cs = rbdl.ConstraintSet() + idx = cs.AddContactConstraint (1, [1., 2., 3.], [4., 5., 6.]) + assert_equal (0, idx) + + X = rbdl.SpatialTransform() + sv = rbdl.SpatialVector.fromPythonArray ([1., 2., 3., 4., 5., 6.]) + idx2 = cs.AddLoopConstraint (1, 2, X, X, sv, 1.) + assert_equal (1, idx2) + +if __name__ == '__main__': + unittest.main() diff --git a/3rdparty/rbdl/python/wrappergen.py b/3rdparty/rbdl/python/wrappergen.py new file mode 100755 index 0000000..6eb597c --- /dev/null +++ b/3rdparty/rbdl/python/wrappergen.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +import sys, re, os + +def usage(arg0): + print ("Usage: {} ".format(arg0)) + sys.exit(-1) + +wrapper_command_strings = { + "ClassDefinitions" : """cdef class _%PARENT%_%MEMBER%_%TYPE%_VectorWrapper: + cdef crbdl.%PARENT% *parent + + def __cinit__ (self, uintptr_t ptr): + self.parent = ptr + + def __getitem__(self, key): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + return [%TYPE%.fromPointer ( &(self.parent.%MEMBER%[i])) for i in xrange (*key.indices(len(self)))] + else: + return %TYPE%.fromPointer ( &(self.parent.%MEMBER%[key])) + + def __setitem__(self, key, value): + if isinstance( key, slice ) : + #Get the start, stop, and step from the slice + src_index = 0 + for i in xrange (*key.indices(len(self))): + assert isinstance (value[src_index], %TYPE%), "Invalid type! Expected %TYPE%, but got " + str(type(value[src_index])) + "." + self.parent.%MEMBER%[i] = (<%TYPE%> value[src_index]).thisptr[0] + src_index = src_index + 1 + else: + assert isinstance (value, %TYPE%), "Invalid type! Expected %TYPE%, but got " + str(type(value)) + "." + self.parent.%MEMBER%[key] = (<%TYPE%> value).thisptr[0] + + def __len__(self): + return self.parent.%MEMBER%.size() +""", + + "MemberDefinitions" : """ cdef _%PARENT%_%MEMBER%_%TYPE%_VectorWrapper %MEMBER%""", + + "CInitCode" : """ self.%MEMBER% = _%PARENT%_%MEMBER%_%TYPE%_VectorWrapper ( self.thisptr)""", + + "AddProperty" : """ property %MEMBER%: + def __get__ (self): + return self.%MEMBER% +""" +} + +def parse_line (line_str): + command = "" + args = {} + + # remove comments + line_str = line_str.split("#")[0] + + wrapper_line_str_match = re.search ("%VectorWrapper(\S*)\s*\((.*)\).*%", line_str) + if (wrapper_line_str_match): + command = wrapper_line_str_match.group(1) + arg_str = wrapper_line_str_match.group(2) + arg_match = re.findall("(\s*(\S*)\s*=\s*(\w*)\s*,?)", arg_str) + if len(arg_match) > 0: + for arg in arg_match: + if len(arg) != 3: + print ("Invalid command args at line_str {}".format + (line_number)) + sys.exit(-1) + + args[arg[1]] = arg[2] + + return command, args + + return False, None + +if __name__ == "__main__": + if len(sys.argv) != 3: + usage (sys.argv[0]) + + infilename = sys.argv[1] + outfilename = sys.argv[2] + + print ("Processing {} to generate {}".format (infilename, outfilename)) + infile = open(infilename) + outfile = open(outfilename, "w") + outfile.write ("""# WARNING! +# +# This file was automatically created from {} using {}. +# Do not modify this file directly. Edit original source instead!! + +""".format (os.path.basename(infilename), os.path.basename(sys.argv[0]))) + + template = infile.read() + template_lines = template.split ("\n") + + # find the classes that will contain generated code + generated_parent_classes = [] + generated_parent_members = {} + for line_number, line_str in enumerate (template_lines): + command, args = parse_line (line_str) + if command: + if args["PARENT"] not in generated_parent_classes: + generated_parent_classes.append (args["PARENT"]) + generated_parent_members[args["PARENT"]] = [] + + if command=="AddProperty": + generated_parent_members[args["PARENT"]].append ({ + "TYPE": args["TYPE"], + "MEMBER": args["MEMBER"] + }) + + # generate code + for line_number, line_str in enumerate (template_lines): + command, args = parse_line (line_str) + if not command: + outfile.write (line_str + "\n") + else: +# print (command, wrapper_command_strings.keys()) + if command in wrapper_command_strings.keys(): + parent = args["PARENT"] + if command == "AddProperty": + content_type = args["TYPE"] + member_name = args["MEMBER"] + command_code = wrapper_command_strings[command][:] + command_code = command_code.replace ( + "%PARENT%", parent).replace ( + "%MEMBER%", member_name).replace ( + "%TYPE%", content_type) + outfile.write (command_code + "\n") + else: + for member in generated_parent_members[parent]: + content_type = member["TYPE"] + member_name = member["MEMBER"] + command_code = wrapper_command_strings[command][:] + command_code = command_code.replace ( + "%PARENT%", parent).replace ( + "%MEMBER%", member_name).replace ( + "%TYPE%", content_type) + outfile.write (command_code + "\n") diff --git a/3rdparty/rbdl/rbdl.pc.cmake b/3rdparty/rbdl/rbdl.pc.cmake new file mode 100644 index 0000000..2c4cdee --- /dev/null +++ b/3rdparty/rbdl/rbdl.pc.cmake @@ -0,0 +1,13 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: RBDL +Description: Rigid Body Dynamics Library +URL: http://rbdl.bitbucket.org/ +Version: @RBDL_VERSION@ +Requires: eigen3 +Conflicts: +Libs: -L${libdir} -lrbdl -Wl,-rpath ${libdir} +Libs.private: +Cflags: -I${includedir} diff --git a/3rdparty/rbdl/src/Constraints.cc b/3rdparty/rbdl/src/Constraints.cc new file mode 100644 index 0000000..f2d208b --- /dev/null +++ b/3rdparty/rbdl/src/Constraints.cc @@ -0,0 +1,1537 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2015 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Joint.h" +#include "rbdl/Body.h" +#include "rbdl/Constraints.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Kinematics.h" + +namespace RigidBodyDynamics { + +using namespace Math; + +void SolveLinearSystem ( + const MatrixNd& A, + const VectorNd& b, + VectorNd& x, + LinearSolver ls +); + +unsigned int GetMovableBodyId (Model& model, unsigned int id); + +unsigned int ConstraintSet::AddContactConstraint ( + unsigned int body_id, + const Vector3d &body_point, + const Vector3d &world_normal, + const char *constraint_name, + double normal_acceleration + ) { + assert (bound == false); + + unsigned int n_constr = size() + 1; + + std::string name_str; + if (constraint_name != NULL) { + name_str = constraint_name; + } + + constraintType.push_back (ContactConstraint); + name.push_back (name_str); + contactConstraintIndices.push_back(size()); + + // These variables will be used for this type of constraint. + body.push_back (body_id); + point.push_back (body_point); + normal.push_back (world_normal); + + // These variables will not be used. + body_p.push_back (0); + body_s.push_back (0); + X_p.push_back (SpatialTransform()); + X_s.push_back (SpatialTransform()); + constraintAxis.push_back (SpatialVector::Zero()); + T_stab_inv.push_back (0.); + err.conservativeResize(n_constr); + err[n_constr - 1] = 0.; + errd.conservativeResize(n_constr); + errd[n_constr - 1] = 0.; + + acceleration.conservativeResize (n_constr); + acceleration[n_constr - 1] = normal_acceleration; + + force.conservativeResize (n_constr); + force[n_constr - 1] = 0.; + + impulse.conservativeResize (n_constr); + impulse[n_constr - 1] = 0.; + + v_plus.conservativeResize (n_constr); + v_plus[n_constr - 1] = 0.; + + d_multdof3_u = std::vector(n_constr, Math::Vector3d::Zero()); + + return n_constr - 1; +} + +unsigned int ConstraintSet::AddLoopConstraint ( + unsigned int id_predecessor, + unsigned int id_successor, + const Math::SpatialTransform &X_predecessor, + const Math::SpatialTransform &X_successor, + const Math::SpatialVector& axis, + double T_stabilization, + const char *constraint_name + ) { + assert (bound == false); + + unsigned int n_constr = size() + 1; + + std::string name_str; + if (constraint_name != NULL) { + name_str = constraint_name; + } + + constraintType.push_back(LoopConstraint); + name.push_back (name_str); + loopConstraintIndices.push_back(size()); + + // These variables will be used for this kind of constraint. + body_p.push_back (id_predecessor); + body_s.push_back (id_successor); + X_p.push_back (X_predecessor); + X_s.push_back (X_successor); + constraintAxis.push_back (axis); + T_stab_inv.push_back (1. / T_stabilization); + err.conservativeResize(n_constr); + err[n_constr - 1] = 0.; + errd.conservativeResize(n_constr); + errd[n_constr - 1] = 0.; + + // These variables will not be used. + body.push_back (0); + point.push_back (Vector3d::Zero()); + normal.push_back (Vector3d::Zero()); + + acceleration.conservativeResize (n_constr); + acceleration[n_constr - 1] = 0.; + + force.conservativeResize (n_constr); + force[n_constr - 1] = 0.; + + impulse.conservativeResize (n_constr); + impulse[n_constr - 1] = 0.; + + v_plus.conservativeResize (n_constr); + v_plus[n_constr - 1] = 0.; + + d_multdof3_u = std::vector(n_constr, Math::Vector3d::Zero()); + + return n_constr - 1; +} + +bool ConstraintSet::Bind (const Model &model) { + assert (bound == false); + + if (bound) { + std::cerr << "Error: binding an already bound constraint set!" << std::endl; + abort(); + } + unsigned int n_constr = size(); + + H.conservativeResize (model.dof_count, model.dof_count); + H.setZero(); + C.conservativeResize (model.dof_count); + C.setZero(); + gamma.conservativeResize (n_constr); + gamma.setZero(); + G.conservativeResize (n_constr, model.dof_count); + G.setZero(); + A.conservativeResize (model.dof_count + n_constr, model.dof_count + n_constr); + A.setZero(); + b.conservativeResize (model.dof_count + n_constr); + b.setZero(); + x.conservativeResize (model.dof_count + n_constr); + x.setZero(); + + Gi.conservativeResize (3, model.qdot_size); + GSpi.conservativeResize (6, model.qdot_size); + GSsi.conservativeResize (6, model.qdot_size); + GSJ.conservativeResize (6, model.qdot_size); + + // HouseHolderQR crashes if matrix G has more rows than columns. +#ifdef RBDL_USE_SIMPLE_MATH + GT_qr = SimpleMath::HouseholderQR (G.transpose()); +#else + GT_qr = Eigen::HouseholderQR (G.transpose()); +#endif + GT_qr_Q = MatrixNd::Zero (model.dof_count, model.dof_count); + Y = MatrixNd::Zero (model.dof_count, G.rows()); + Z = MatrixNd::Zero (model.dof_count, model.dof_count - G.rows()); + qddot_y = VectorNd::Zero (model.dof_count); + qddot_z = VectorNd::Zero (model.dof_count); + + K.conservativeResize (n_constr, n_constr); + K.setZero(); + a.conservativeResize (n_constr); + a.setZero(); + QDDot_t.conservativeResize (model.dof_count); + QDDot_t.setZero(); + QDDot_0.conservativeResize (model.dof_count); + QDDot_0.setZero(); + f_t.resize (n_constr, SpatialVector::Zero()); + f_ext_constraints.resize (model.mBodies.size(), SpatialVector::Zero()); + point_accel_0.resize (n_constr, Vector3d::Zero()); + + d_pA = std::vector (model.mBodies.size(), SpatialVector::Zero()); + d_a = std::vector (model.mBodies.size(), SpatialVector::Zero()); + d_u = VectorNd::Zero (model.mBodies.size()); + + d_IA = std::vector (model.mBodies.size() + , SpatialMatrix::Identity()); + d_U = std::vector (model.mBodies.size(), SpatialVector::Zero()); + d_d = VectorNd::Zero (model.mBodies.size()); + + d_multdof3_u = std::vector (model.mBodies.size() + , Math::Vector3d::Zero()); + + bound = true; + + return bound; +} + +void ConstraintSet::clear() { + acceleration.setZero(); + force.setZero(); + impulse.setZero(); + + H.setZero(); + C.setZero(); + gamma.setZero(); + G.setZero(); + A.setZero(); + b.setZero(); + x.setZero(); + + K.setZero(); + a.setZero(); + QDDot_t.setZero(); + QDDot_0.setZero(); + + unsigned int i; + for (i = 0; i < f_t.size(); i++) + f_t[i].setZero(); + + for (i = 0; i < f_ext_constraints.size(); i++) + f_ext_constraints[i].setZero(); + + for (i = 0; i < point_accel_0.size(); i++) + point_accel_0[i].setZero(); + + for (i = 0; i < d_pA.size(); i++) + d_pA[i].setZero(); + + for (i = 0; i < d_a.size(); i++) + d_a[i].setZero(); + + d_u.setZero(); +} + +RBDL_DLLAPI +void SolveConstrainedSystemDirect ( + Math::MatrixNd &H, + const Math::MatrixNd &G, + const Math::VectorNd &c, + const Math::VectorNd &gamma, + Math::VectorNd &qddot, + Math::VectorNd &lambda, + Math::MatrixNd &A, + Math::VectorNd &b, + Math::VectorNd &x, + Math::LinearSolver &linear_solver + ) { + // Build the system: Copy H + A.block(0, 0, c.rows(), c.rows()) = H; + + // Copy G and G^T + A.block(0, c.rows(), c.rows(), gamma.rows()) = G.transpose(); + A.block(c.rows(), 0, gamma.rows(), c.rows()) = G; + + // Build the system: Copy -C + \tau + b.block(0, 0, c.rows(), 1) = c; + b.block(c.rows(), 0, gamma.rows(), 1) = gamma; + + LOG << "A = " << std::endl << A << std::endl; + LOG << "b = " << std::endl << b << std::endl; + + switch (linear_solver) { + case (LinearSolverPartialPivLU) : +#ifdef RBDL_USE_SIMPLE_MATH + // SimpleMath does not have a LU solver so just use its QR solver + x = A.householderQr().solve(b); +#else + x = A.partialPivLu().solve(b); +#endif + break; + case (LinearSolverColPivHouseholderQR) : + x = A.colPivHouseholderQr().solve(b); + break; + case (LinearSolverHouseholderQR) : + x = A.householderQr().solve(b); + break; + default: + LOG << "Error: Invalid linear solver: " << linear_solver << std::endl; + assert (0); + break; + } + + LOG << "x = " << std::endl << x << std::endl; +} + +RBDL_DLLAPI +void SolveConstrainedSystemRangeSpaceSparse ( + Model &model, + Math::MatrixNd &H, + const Math::MatrixNd &G, + const Math::VectorNd &c, + const Math::VectorNd &gamma, + Math::VectorNd &qddot, + Math::VectorNd &lambda, + Math::MatrixNd &K, + Math::VectorNd &a, + Math::LinearSolver linear_solver + ) { + SparseFactorizeLTL (model, H); + + MatrixNd Y (G.transpose()); + + for (unsigned int i = 0; i < Y.cols(); i++) { + VectorNd Y_col = Y.block(0,i,Y.rows(),1); + SparseSolveLTx (model, H, Y_col); + Y.block(0,i,Y.rows(),1) = Y_col; + } + + VectorNd z (c); + SparseSolveLTx (model, H, z); + + K = Y.transpose() * Y; + + a = gamma - Y.transpose() * z; + + lambda = K.llt().solve(a); + + qddot = c + G.transpose() * lambda; + SparseSolveLTx (model, H, qddot); + SparseSolveLx (model, H, qddot); +} + +RBDL_DLLAPI +void SolveConstrainedSystemNullSpace ( + Math::MatrixNd &H, + const Math::MatrixNd &G, + const Math::VectorNd &c, + const Math::VectorNd &gamma, + Math::VectorNd &qddot, + Math::VectorNd &lambda, + Math::MatrixNd &Y, + Math::MatrixNd &Z, + Math::VectorNd &qddot_y, + Math::VectorNd &qddot_z, + Math::LinearSolver &linear_solver + ) { + switch (linear_solver) { + case (LinearSolverPartialPivLU) : +#ifdef RBDL_USE_SIMPLE_MATH + // SimpleMath does not have a LU solver so just use its QR solver + qddot_y = (G * Y).householderQr().solve (gamma); +#else + qddot_y = (G * Y).partialPivLu().solve (gamma); +#endif + break; + case (LinearSolverColPivHouseholderQR) : + qddot_y = (G * Y).colPivHouseholderQr().solve (gamma); + break; + case (LinearSolverHouseholderQR) : + qddot_y = (G * Y).householderQr().solve (gamma); + break; + default: + LOG << "Error: Invalid linear solver: " << linear_solver << std::endl; + assert (0); + break; + } + + qddot_z = (Z.transpose() * H * Z).llt().solve(Z.transpose() * (c - H * Y * qddot_y)); + + qddot = Y * qddot_y + Z * qddot_z; + + switch (linear_solver) { + case (LinearSolverPartialPivLU) : +#ifdef RBDL_USE_SIMPLE_MATH + // SimpleMath does not have a LU solver so just use its QR solver + qddot_y = (G * Y).householderQr().solve (gamma); +#else + lambda = (G * Y).partialPivLu().solve (Y.transpose() * (H * qddot - c)); +#endif + break; + case (LinearSolverColPivHouseholderQR) : + lambda = (G * Y).colPivHouseholderQr().solve (Y.transpose() * (H * qddot - c)); + break; + case (LinearSolverHouseholderQR) : + lambda = (G * Y).householderQr().solve (Y.transpose() * (H * qddot - c)); + break; + default: + LOG << "Error: Invalid linear solver: " << linear_solver << std::endl; + assert (0); + break; + } +} + +RBDL_DLLAPI +void CalcConstraintsPositionError ( + Model& model, + const Math::VectorNd &Q, + ConstraintSet &CS, + Math::VectorNd& err, + bool update_kinematics + ) { + assert(err.size() == CS.size()); + + if(update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + for (unsigned int i = 0; i < CS.contactConstraintIndices.size(); i++) { + const unsigned int c = CS.contactConstraintIndices[i]; + err[c] = 0.; + } + + for (unsigned int i = 0; i < CS.loopConstraintIndices.size(); i++) { + const unsigned int lci = CS.loopConstraintIndices[i]; + + // Variables used for computations. + Vector3d pos_p; + Vector3d pos_s; + Matrix3d rot_p; + Matrix3d rot_s; + Matrix3d rot_ps; + SpatialVector d; + + // Constraints computed in the predecessor body frame. + + // Compute the orientation of the two constraint frames. + rot_p = CalcBodyWorldOrientation (model, Q, CS.body_p[lci], false).transpose() + * CS.X_p[lci].E; + rot_s = CalcBodyWorldOrientation (model, Q, CS.body_s[lci], false).transpose() + * CS.X_s[lci].E; + + // Compute the orientation from the predecessor to the successor frame. + rot_ps = rot_p.transpose() * rot_s; + + // Compute the position of the two contact points. + pos_p = CalcBodyToBaseCoordinates (model, Q, CS.body_p[lci], CS.X_p[lci].r + , false); + pos_s = CalcBodyToBaseCoordinates (model, Q, CS.body_s[lci], CS.X_s[lci].r + , false); + + // The first three elemenets represent the rotation error. + // This formulation is equivalent to u * sin(theta), where u and theta are + // the angle-axis of rotation from the predecessor to the successor frame. + // These quantities are expressed in the predecessor frame. + d[0] = -0.5 * (rot_ps(1,2) - rot_ps(2,1)); + d[1] = -0.5 * (rot_ps(2,0) - rot_ps(0,2)); + d[2] = -0.5 * (rot_ps(0,1) - rot_ps(1,0)); + + // The last three elements represent the position error. + // It is equivalent to the difference in the position of the two + // constraint points. + // The distance is projected on the predecessor frame to be consistent + // with the rotation. + d.block<3,1>(3,0) = rot_p.transpose() * (pos_s - pos_p); + + // Project the error on the constraint axis to find the actual error. + err[lci] = CS.constraintAxis[lci].transpose() * d; + } +} + +RBDL_DLLAPI +void CalcConstraintsJacobian ( + Model &model, + const Math::VectorNd &Q, + ConstraintSet &CS, + Math::MatrixNd &G, + bool update_kinematics + ) { + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + // variables to check whether we need to recompute G. + ConstraintSet::ConstraintType prev_constraint_type + = ConstraintSet::ConstraintTypeLast; + unsigned int prev_body_id_1 = 0; + unsigned int prev_body_id_2 = 0; + SpatialTransform prev_body_X_1; + SpatialTransform prev_body_X_2; + + for (unsigned int i = 0; i < CS.contactConstraintIndices.size(); i++) { + const unsigned int c = CS.contactConstraintIndices[i]; + + // only compute the matrix Gi if actually needed + if (prev_constraint_type != CS.constraintType[c] + || prev_body_id_1 != CS.body[c] + || prev_body_X_1.r != CS.point[c]) { + + // Compute the jacobian for the point. + CS.Gi.setZero(); + CalcPointJacobian (model, Q, CS.body[c], CS.point[c], CS.Gi, false); + prev_constraint_type = ConstraintSet::ContactConstraint; + + // Update variables for optimization check. + prev_body_id_1 = CS.body[c]; + prev_body_X_1 = Xtrans(CS.point[c]); + } + + for(unsigned int j = 0; j < model.dof_count; j++) { + Vector3d gaxis (CS.Gi(0,j), CS.Gi(1,j), CS.Gi(2,j)); + G(c,j) = gaxis.transpose() * CS.normal[c]; + } + } + + // Variables used for computations. + Vector3d normal; + SpatialVector axis; + Vector3d pos_p; + Matrix3d rot_p; + SpatialTransform X_0p; + + for (unsigned int i = 0; i < CS.loopConstraintIndices.size(); i++) { + const unsigned int c = CS.loopConstraintIndices[i]; + + // Only recompute variables if necessary. + if( prev_body_id_1 != CS.body_p[c] + || prev_body_id_2 != CS.body_s[c] + || prev_body_X_1.r != CS.X_p[c].r + || prev_body_X_2.r != CS.X_s[c].r + || prev_body_X_1.E != CS.X_p[c].E + || prev_body_X_2.E != CS.X_s[c].E) { + + // Compute the 6D jacobians of the two contact points. + CS.GSpi.setZero(); + CS.GSsi.setZero(); + CalcPointJacobian6D(model, Q, CS.body_p[c], CS.X_p[c].r, CS.GSpi, false); + CalcPointJacobian6D(model, Q, CS.body_s[c], CS.X_s[c].r, CS.GSsi, false); + CS.GSJ = CS.GSsi - CS.GSpi; + + // Compute position and rotation matrix from predecessor body to base. + pos_p = CalcBodyToBaseCoordinates (model, Q, CS.body_p[c], CS.X_p[c].r + , false); + rot_p = CalcBodyWorldOrientation (model, Q, CS.body_p[c] + , false).transpose()* CS.X_p[c].E; + X_0p = SpatialTransform (rot_p, pos_p); + + // Update variables for optimization check. + prev_constraint_type = ConstraintSet::LoopConstraint; + prev_body_id_1 = CS.body_p[c]; + prev_body_id_2 = CS.body_s[c]; + prev_body_X_1 = CS.X_p[c]; + prev_body_X_2 = CS.X_s[c]; + } + + // Express the constraint axis in the base frame. + axis = X_0p.apply(CS.constraintAxis[c]); + + // Compute the constraint Jacobian row. + G.block(c, 0, 1, model.dof_count) = axis.transpose() * CS.GSJ; + } +} + +RBDL_DLLAPI +void CalcConstraintsVelocityError ( + Model& model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + ConstraintSet &CS, + Math::VectorNd& err, + bool update_kinematics + ) { + MatrixNd G(MatrixNd::Zero(CS.size(), model.dof_count)); + CalcConstraintsJacobian (model, Q, CS, G, update_kinematics); + err = G * QDot; +} + +RBDL_DLLAPI +void CalcConstrainedSystemVariables ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS + ) { + // Compute C + NonlinearEffects(model, Q, QDot, CS.C); + assert(CS.H.cols() == model.dof_count && CS.H.rows() == model.dof_count); + + // Compute H + CS.H.setZero(); + CompositeRigidBodyAlgorithm(model, Q, CS.H, false); + + // Compute G + // We have to update model.X_base as they are not automatically computed + // by NonlinearEffects() + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + model.X_base[i] = model.X_lambda[i] * model.X_base[model.lambda[i]]; + } + CalcConstraintsJacobian (model, Q, CS, CS.G, false); + + // Compute position error for Baumgarte Stabilization. + CalcConstraintsPositionError (model, Q, CS, CS.err, false); + + // Compute velocity error for Baugarte stabilization. + CS.errd = CS.G * QDot; + + // Compute gamma + unsigned int prev_body_id = 0; + Vector3d prev_body_point = Vector3d::Zero(); + Vector3d gamma_i = Vector3d::Zero(); + + CS.QDDot_0.setZero(); + UpdateKinematicsCustom(model, NULL, NULL, &CS.QDDot_0); + + for (unsigned int i = 0; i < CS.contactConstraintIndices.size(); i++) { + const unsigned int c = CS.contactConstraintIndices[i]; + + // only compute point accelerations when necessary + if (prev_body_id != CS.body[c] || prev_body_point != CS.point[c]) { + gamma_i = CalcPointAcceleration (model, Q, QDot, CS.QDDot_0, CS.body[c] + , CS.point[c], false); + prev_body_id = CS.body[c]; + prev_body_point = CS.point[c]; + } + + // we also substract ContactData[c].acceleration such that the contact + // point will have the desired acceleration + CS.gamma[c] = CS.acceleration[c] - CS.normal[c].dot(gamma_i); + } + + for (unsigned int i = 0; i < CS.loopConstraintIndices.size(); i++) { + const unsigned int c = CS.loopConstraintIndices[i]; + + // Variables used for computations. + Vector3d pos_p; + Matrix3d rot_p; + SpatialVector vel_p; + SpatialVector vel_s; + SpatialVector axis; + unsigned int id_p; + unsigned int id_s; + + // Force recomputation. + prev_body_id = 0; + + // Express the constraint axis in the base frame. + pos_p = CalcBodyToBaseCoordinates (model, Q, CS.body_p[c], CS.X_p[c].r + , false); + rot_p = CalcBodyWorldOrientation (model, Q, CS.body_p[c], false).transpose() + * CS.X_p[c].E; + axis = SpatialTransform (rot_p, pos_p).apply(CS.constraintAxis[c]); + + // Compute the spatial velocities of the two constrained bodies. + vel_p = CalcPointVelocity6D (model, Q, QDot, CS.body_p[c], CS.X_p[c].r + , false); + vel_s = CalcPointVelocity6D (model, Q, QDot, CS.body_s[c], CS.X_s[c].r + , false); + + // Check if the bodies involved in the constraint are fixed. If yes, find + // their movable parent to access the right value in the a vector. + // This is needed because we access the model.a vector directly later. + id_p = GetMovableBodyId (model, CS.body_p[c]); + id_s = GetMovableBodyId (model, CS.body_s[c]); + + // Problem here if one of the bodies is fixed... + // Compute the value of gamma. + CS.gamma[c] + // Right hand side term. + = - axis.transpose() * (model.a[id_s] - model.a[id_p] + + crossm(vel_s, vel_p)) + // Baumgarte stabilization term. + - 2. * CS.T_stab_inv[c] * CS.errd[c] + - CS.T_stab_inv[c] * CS.T_stab_inv[c] * CS.err[c]; + } +} + +RBDL_DLLAPI +bool CalcAssemblyQ ( + Model &model, + Math::VectorNd QInit, // Note: passed by value intentionally + ConstraintSet &cs, + Math::VectorNd &Q, + const Math::VectorNd &weights, + double tolerance, + unsigned int max_iter + ) { + + if(Q.size() != model.q_size) { + std::cerr << "Incorrect Q vector size." << std::endl; + assert(false); + abort(); + } + if(QInit.size() != model.q_size) { + std::cerr << "Incorrect QInit vector size." << std::endl; + assert(false); + abort(); + } + if(weights.size() != model.dof_count) { + std::cerr << "Incorrect weights vector size." << std::endl; + assert(false); + abort(); + } + + // Initialize variables. + MatrixNd constraintJac (cs.size(), model.dof_count); + MatrixNd A = MatrixNd::Zero (cs.size() + model.dof_count, cs.size() + + model.dof_count); + VectorNd b = VectorNd::Zero (cs.size() + model.dof_count); + VectorNd x = VectorNd::Zero (cs.size() + model.dof_count); + VectorNd d = VectorNd::Zero (model.dof_count); + VectorNd e = VectorNd::Zero (cs.size()); + + // The top-left block is the weight matrix and is constant. + for(unsigned int i = 0; i < weights.size(); ++i) { + A(i,i) = weights[i]; + } + + // Check if the error is small enough already. If so, just return the initial + // guess as the solution. + CalcConstraintsPositionError (model, QInit, cs, e); + if (e.norm() < tolerance) { + Q = QInit; + return true; + } + + // We solve the linearized problem iteratively. + // Iterations are stopped if the maximum is reached. + for(unsigned int it = 0; it < max_iter; ++it) { + // Compute the constraint jacobian and build A and b. + constraintJac.setZero(); + CalcConstraintsJacobian (model, QInit, cs, constraintJac); + A.block (model.dof_count, 0, cs.size(), model.dof_count) = constraintJac; + A.block (0, model.dof_count, model.dof_count, cs.size()) + = constraintJac.transpose(); + b.block (model.dof_count, 0, cs.size(), 1) = -e; + + // Solve the sistem A*x = b. + SolveLinearSystem (A, b, x, cs.linear_solver); + + // Extract the d = (Q - QInit) vector from x. + d = x.block (0, 0, model.dof_count, 1); + + // Update solution. + for (size_t i = 0; i < model.mJoints.size(); ++i) { + // If the joint is spherical, translate the corresponding components + // of d into a modification in the joint quaternion. + if (model.mJoints[i].mJointType == JointTypeSpherical) { + Quaternion quat = model.GetQuaternion(i, QInit); + Vector3d omega = d.block<3,1>(model.mJoints[i].q_index,0); + // Convert the 3d representation of the displacement to 4d and sum it + // to the components of the quaternion. + quat += quat.omegaToQDot(omega); + // The quaternion needs to be normalized after the previous sum. + quat /= quat.norm(); + model.SetQuaternion(i, quat, QInit); + } + // If the current joint is not spherical, simply add the corresponding + // components of d to QInit. + else { + unsigned int qIdx = model.mJoints[i].q_index; + for(size_t j = 0; j < model.mJoints[i].mDoFCount; ++j) { + QInit[qIdx + j] += d[qIdx + j]; + } + // QInit.block(qIdx, 0, model.mJoints[i].mDoFCount, 1) + // += d.block(model.mJoints[i].q_index, 0, model.mJoints[i].mDoFCount, 1); + } + } + + // Update the errors. + CalcConstraintsPositionError (model, QInit, cs, e); + + // Check if the error and the step are small enough to end. + if (e.norm() < tolerance && d.norm() < tolerance){ + Q = QInit; + return true; + } + } + + // Return false if maximum number of iterations is exceeded. + Q = QInit; + return false; +} + +RBDL_DLLAPI +void CalcAssemblyQDot ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotInit, + ConstraintSet &cs, + Math::VectorNd &QDot, + const Math::VectorNd &weights + ) { + if(QDot.size() != model.dof_count) { + std::cerr << "Incorrect QDot vector size." << std::endl; + assert(false); + abort(); + } + if(Q.size() != model.q_size) { + std::cerr << "Incorrect Q vector size." << std::endl; + assert(false); + abort(); + } + if(QDotInit.size() != QDot.size()) { + std::cerr << "Incorrect QDotInit vector size." << std::endl; + assert(false); + abort(); + } + if(weights.size() != QDot.size()) { + std::cerr << "Incorrect weight vector size." << std::endl; + assert(false); + abort(); + } + + // Initialize variables. + MatrixNd constraintJac = MatrixNd::Zero(cs.size(), model.dof_count); + MatrixNd A = MatrixNd::Zero(cs.size() + model.dof_count, cs.size() + + model.dof_count); + VectorNd b = VectorNd::Zero(cs.size() + model.dof_count); + VectorNd x = VectorNd::Zero(cs.size() + model.dof_count); + + // The top-left block is the weight matrix and is constant. + for(unsigned int i = 0; i < weights.size(); ++i) { + A(i,i) = weights[i]; + b[i] = weights[i] * QDotInit[i]; + } + CalcConstraintsJacobian (model, Q, cs, constraintJac); + A.block (model.dof_count, 0, cs.size(), model.dof_count) = constraintJac; + A.block (0, model.dof_count, model.dof_count, cs.size()) + = constraintJac.transpose(); + + // Solve the sistem A*x = b. + SolveLinearSystem (A, b, x, cs.linear_solver); + + // Copy the result to the output variable. + QDot = x.block (0, 0, model.dof_count, 1); +} + +RBDL_DLLAPI +void ForwardDynamicsConstraintsDirect ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &Tau, + ConstraintSet &CS, + VectorNd &QDDot + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + CalcConstrainedSystemVariables (model, Q, QDot, Tau, CS); + + SolveConstrainedSystemDirect (CS.H, CS.G, Tau - CS.C, CS.gamma, QDDot + , CS.force, CS.A, CS.b, CS.x, CS.linear_solver); + + // Copy back QDDot + for (unsigned int i = 0; i < model.dof_count; i++) + QDDot[i] = CS.x[i]; + + // Copy back contact forces + for (unsigned int i = 0; i < CS.size(); i++) { + CS.force[i] = -CS.x[model.dof_count + i]; + } +} + +RBDL_DLLAPI +void ForwardDynamicsConstraintsRangeSpaceSparse ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + const Math::VectorNd &Tau, + ConstraintSet &CS, + Math::VectorNd &QDDot + ) { + CalcConstrainedSystemVariables (model, Q, QDot, Tau, CS); + + SolveConstrainedSystemRangeSpaceSparse (model, CS.H, CS.G, Tau - CS.C + , CS.gamma, QDDot, CS.force, CS.K, CS.a, CS.linear_solver); +} + +RBDL_DLLAPI +void ForwardDynamicsConstraintsNullSpace ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &Tau, + ConstraintSet &CS, + VectorNd &QDDot + ) { + + LOG << "-------- " << __func__ << " --------" << std::endl; + + CalcConstrainedSystemVariables (model, Q, QDot, Tau, CS); + + CS.GT_qr.compute (CS.G.transpose()); +#ifdef RBDL_USE_SIMPLE_MATH + CS.GT_qr_Q = CS.GT_qr.householderQ(); +#else + CS.GT_qr.householderQ().evalTo (CS.GT_qr_Q); +#endif + + CS.Y = CS.GT_qr_Q.block (0,0,QDot.rows(), CS.G.rows()); + CS.Z = CS.GT_qr_Q.block (0,CS.G.rows(),QDot.rows(), QDot.rows() - CS.G.rows()); + + SolveConstrainedSystemNullSpace (CS.H, CS.G, Tau - CS.C, CS.gamma, QDDot + , CS.force, CS.Y, CS.Z, CS.qddot_y, CS.qddot_z, CS.linear_solver); + +} + +RBDL_DLLAPI +void ComputeConstraintImpulsesDirect ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotMinus, + ConstraintSet &CS, + Math::VectorNd &QDotPlus + ) { + + // Compute H + UpdateKinematicsCustom (model, &Q, NULL, NULL); + CompositeRigidBodyAlgorithm (model, Q, CS.H, false); + + // Compute G + CalcConstraintsJacobian (model, Q, CS, CS.G, false); + + SolveConstrainedSystemDirect (CS.H, CS.G, CS.H * QDotMinus, CS.v_plus + , QDotPlus, CS.impulse, CS.A, CS.b, CS.x, CS.linear_solver); + + // Copy back QDotPlus + for (unsigned int i = 0; i < model.dof_count; i++) + QDotPlus[i] = CS.x[i]; + + // Copy back constraint impulses + for (unsigned int i = 0; i < CS.size(); i++) { + CS.impulse[i] = CS.x[model.dof_count + i]; + } + +} + +RBDL_DLLAPI +void ComputeConstraintImpulsesRangeSpaceSparse ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotMinus, + ConstraintSet &CS, + Math::VectorNd &QDotPlus + ) { + + // Compute H + UpdateKinematicsCustom (model, &Q, NULL, NULL); + CompositeRigidBodyAlgorithm (model, Q, CS.H, false); + + // Compute G + CalcConstraintsJacobian (model, Q, CS, CS.G, false); + + SolveConstrainedSystemRangeSpaceSparse (model, CS.H, CS.G, CS.H * QDotMinus + , CS.v_plus, QDotPlus, CS.impulse, CS.K, CS.a, CS.linear_solver); + +} + +RBDL_DLLAPI +void ComputeConstraintImpulsesNullSpace ( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDotMinus, + ConstraintSet &CS, + Math::VectorNd &QDotPlus + ) { + + // Compute H + UpdateKinematicsCustom (model, &Q, NULL, NULL); + CompositeRigidBodyAlgorithm (model, Q, CS.H, false); + + // Compute G + CalcConstraintsJacobian (model, Q, CS, CS.G, false); + + CS.GT_qr.compute(CS.G.transpose()); + CS.GT_qr_Q = CS.GT_qr.householderQ(); + + CS.Y = CS.GT_qr_Q.block (0,0,QDotMinus.rows(), CS.G.rows()); + CS.Z = CS.GT_qr_Q.block (0,CS.G.rows(),QDotMinus.rows(), QDotMinus.rows() + - CS.G.rows()); + + SolveConstrainedSystemNullSpace (CS.H, CS.G, CS.H * QDotMinus, CS.v_plus + , QDotPlus, CS.impulse, CS.Y, CS.Z, CS.qddot_y, CS.qddot_z + , CS.linear_solver); +} + +/** \brief Compute only the effects of external forces on the generalized accelerations + * + * This function is a reduced version of ForwardDynamics() which only + * computes the effects of the external forces on the generalized + * accelerations. + * + */ +RBDL_DLLAPI +void ForwardDynamicsApplyConstraintForces ( + Model &model, + const VectorNd &Tau, + ConstraintSet &CS, + VectorNd &QDDot + ) { + LOG << "-------- " << __func__ << " --------" << std::endl; + assert (QDDot.size() == model.dof_count); + + unsigned int i = 0; + + for (i = 1; i < model.mBodies.size(); i++) { + model.IA[i] = model.I[i].toMatrix();; + model.pA[i] = crossf(model.v[i],model.I[i] * model.v[i]); + + if (CS.f_ext_constraints[i] != SpatialVector::Zero()) { + LOG << "External force (" << i << ") = " << model.X_base[i].toMatrixAdjoint() * CS.f_ext_constraints[i] << std::endl; + model.pA[i] -= model.X_base[i].toMatrixAdjoint() * CS.f_ext_constraints[i]; + } + } + + // ClearLogOutput(); + + LOG << "--- first loop ---" << std::endl; + + for (i = model.mBodies.size() - 1; i > 0; i--) { + unsigned int q_index = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + unsigned int lambda = model.lambda[i]; + model.multdof3_u[i] = Vector3d (Tau[q_index], + Tau[q_index + 1], + Tau[q_index + 2]) + - model.multdof3_S[i].transpose() * model.pA[i]; + + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] - (model.multdof3_U[i] + * model.multdof3_Dinv[i] + * model.multdof3_U[i].transpose()); + + SpatialVector pa = model.pA[i] + Ia * model.c[i] + + model.multdof3_U[i] * model.multdof3_Dinv[i] * model.multdof3_u[i]; + +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += (model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix()); + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] += (model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix()); + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " << model.pA[lambda].transpose() + << std::endl; + } + } else if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + model.u[i] = Tau[q_index] - model.S[i].dot(model.pA[i]); + + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - model.U[i] * (model.U[i] / model.d[i]).transpose(); + SpatialVector pa = model.pA[i] + Ia * model.c[i] + + model.U[i] * model.u[i] / model.d[i]; +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += (model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix()); + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] += (model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix()); + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() << std::endl; + } + } else if(model.mJoints[i].mJointType == JointTypeCustom) { + + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + unsigned int lambda = model.lambda[i]; + VectorNd tau_temp = VectorNd::Zero(dofI); + + for(int z=0; zu = tau_temp + - (model.mCustomJoints[kI]->S.transpose() + * model.pA[i]); + + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - ( model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->U.transpose()); + + SpatialVector pa = model.pA[i] + Ia * model.c[i] + + ( model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->u); +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix(); + + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] += model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix(); + + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " << model.pA[lambda].transpose() + << std::endl; + } + } + } + + model.a[0] = SpatialVector (0., 0., 0., -model.gravity[0], -model.gravity[1], -model.gravity[2]); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + SpatialTransform X_lambda = model.X_lambda[i]; + + model.a[i] = X_lambda.apply(model.a[lambda]) + model.c[i]; + LOG << "a'[" << i << "] = " << model.a[i].transpose() << std::endl; + + if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + Vector3d qdd_temp = model.multdof3_Dinv[i] * + (model.multdof3_u[i] + - model.multdof3_U[i].transpose() * model.a[i]); + + QDDot[q_index] = qdd_temp[0]; + QDDot[q_index + 1] = qdd_temp[1]; + QDDot[q_index + 2] = qdd_temp[2]; + model.a[i] = model.a[i] + model.multdof3_S[i] * qdd_temp; + } else if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + QDDot[q_index] = (1./model.d[i]) * (model.u[i] - model.U[i].dot(model.a[i])); + model.a[i] = model.a[i] + model.S[i] * QDDot[q_index]; + } else if (model.mJoints[i].mJointType == JointTypeCustom){ + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + VectorNd qdd_temp = VectorNd::Zero(dofI); + + qdd_temp = model.mCustomJoints[kI]->Dinv + * (model.mCustomJoints[kI]->u + - model.mCustomJoints[kI]->U.transpose() + * model.a[i]); + + for(int z=0; zS * qdd_temp); + } + } + + LOG << "QDDot = " << QDDot.transpose() << std::endl; +} + +/** \brief Computes the effect of external forces on the generalized accelerations. + * + * This function is essentially similar to ForwardDynamics() except that it + * tries to only perform computations of variables that change due to + * external forces defined in f_t. + */ +RBDL_DLLAPI +void ForwardDynamicsAccelerationDeltas ( + Model &model, + ConstraintSet &CS, + VectorNd &QDDot_t, + const unsigned int body_id, + const std::vector &f_t + ) { + LOG << "-------- " << __func__ << " ------" << std::endl; + + assert (CS.d_pA.size() == model.mBodies.size()); + assert (CS.d_a.size() == model.mBodies.size()); + assert (CS.d_u.size() == model.mBodies.size()); + + // TODO reset all values (debug) + for (unsigned int i = 0; i < model.mBodies.size(); i++) { + CS.d_pA[i].setZero(); + CS.d_a[i].setZero(); + CS.d_u[i] = 0.; + CS.d_multdof3_u[i].setZero(); + } + for(unsigned int i=0; id_u.setZero(); + } + + for (unsigned int i = body_id; i > 0; i--) { + if (i == body_id) { + CS.d_pA[i] = -model.X_base[i].applyAdjoint(f_t[i]); + } + + if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + CS.d_multdof3_u[i] = - model.multdof3_S[i].transpose() * (CS.d_pA[i]); + + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + CS.d_pA[lambda] = CS.d_pA[lambda] + + model.X_lambda[i].applyTranspose ( + CS.d_pA[i] + (model.multdof3_U[i] + * model.multdof3_Dinv[i] + * CS.d_multdof3_u[i])); + } + } else if(model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + CS.d_u[i] = - model.S[i].dot(CS.d_pA[i]); + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + CS.d_pA[lambda] = CS.d_pA[lambda] + + model.X_lambda[i].applyTranspose ( + CS.d_pA[i] + model.U[i] * CS.d_u[i] / model.d[i]); + } + } else if (model.mJoints[i].mJointType == JointTypeCustom){ + + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + //CS. + model.mCustomJoints[kI]->d_u = + - model.mCustomJoints[kI]->S.transpose() * (CS.d_pA[i]); + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + CS.d_pA[lambda] = + CS.d_pA[lambda] + + model.X_lambda[i].applyTranspose ( + CS.d_pA[i] + ( model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->d_u) + ); + } + } + } + + for (unsigned int i = 0; i < f_t.size(); i++) { + LOG << "f_t[" << i << "] = " << f_t[i].transpose() << std::endl; + } + + for (unsigned int i = 0; i < model.mBodies.size(); i++) { + LOG << "i = " << i << ": d_pA[i] " << CS.d_pA[i].transpose() << std::endl; + } + for (unsigned int i = 0; i < model.mBodies.size(); i++) { + LOG << "i = " << i << ": d_u[i] = " << CS.d_u[i] << std::endl; + } + + QDDot_t[0] = 0.; + CS.d_a[0] = model.a[0]; + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + + SpatialVector Xa = model.X_lambda[i].apply(CS.d_a[lambda]); + + if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + Vector3d qdd_temp = model.multdof3_Dinv[i] + * (CS.d_multdof3_u[i] - model.multdof3_U[i].transpose() * Xa); + + QDDot_t[q_index] = qdd_temp[0]; + QDDot_t[q_index + 1] = qdd_temp[1]; + QDDot_t[q_index + 2] = qdd_temp[2]; + model.a[i] = model.a[i] + model.multdof3_S[i] * qdd_temp; + CS.d_a[i] = Xa + model.multdof3_S[i] * qdd_temp; + } else if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom){ + + QDDot_t[q_index] = (CS.d_u[i] - model.U[i].dot(Xa) ) / model.d[i]; + CS.d_a[i] = Xa + model.S[i] * QDDot_t[q_index]; + } else if (model.mJoints[i].mJointType == JointTypeCustom){ + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + VectorNd qdd_temp = VectorNd::Zero(dofI); + + qdd_temp = model.mCustomJoints[kI]->Dinv + * (model.mCustomJoints[kI]->d_u + - model.mCustomJoints[kI]->U.transpose() * Xa); + + for(int z=0; zS * qdd_temp; + CS.d_a[i] = Xa + model.mCustomJoints[kI]->S * qdd_temp; + } + + LOG << "QDDot_t[" << i - 1 << "] = " << QDDot_t[i - 1] << std::endl; + LOG << "d_a[i] = " << CS.d_a[i].transpose() << std::endl; + } +} + +inline void set_zero (std::vector &spatial_values) { + for (unsigned int i = 0; i < spatial_values.size(); i++) + spatial_values[i].setZero(); +} + +RBDL_DLLAPI +void ForwardDynamicsContactsKokkevis ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &Tau, + ConstraintSet &CS, + VectorNd &QDDot + ) { + LOG << "-------- " << __func__ << " ------" << std::endl; + + assert (CS.f_ext_constraints.size() == model.mBodies.size()); + assert (CS.QDDot_0.size() == model.dof_count); + assert (CS.QDDot_t.size() == model.dof_count); + assert (CS.f_t.size() == CS.size()); + assert (CS.point_accel_0.size() == CS.size()); + assert (CS.K.rows() == CS.size()); + assert (CS.K.cols() == CS.size()); + assert (CS.force.size() == CS.size()); + assert (CS.a.size() == CS.size()); + + Vector3d point_accel_t; + + unsigned int ci = 0; + + // The default acceleration only needs to be computed once + { + SUPPRESS_LOGGING; + ForwardDynamics(model, Q, QDot, Tau, CS.QDDot_0); + } + + LOG << "=== Initial Loop Start ===" << std::endl; + // we have to compute the standard accelerations first as we use them to + // compute the effects of each test force + for(ci = 0; ci < CS.size(); ci++) { + + { + SUPPRESS_LOGGING; + UpdateKinematicsCustom(model, NULL, NULL, &CS.QDDot_0); + } + + if(CS.constraintType[ci] == ConstraintSet::ContactConstraint) + { + LOG << "body_id = " << CS.body[ci] << std::endl; + LOG << "point = " << CS.point[ci] << std::endl; + LOG << "normal = " << CS.normal[ci] << std::endl; + LOG << "QDDot_0 = " << CS.QDDot_0.transpose() << std::endl; + { + SUPPRESS_LOGGING; + CS.point_accel_0[ci] = CalcPointAcceleration (model, Q, QDot + , CS.QDDot_0, CS.body[ci], CS.point[ci], false); + CS.a[ci] = - CS.acceleration[ci] + + CS.normal[ci].dot(CS.point_accel_0[ci]); + } + LOG << "point_accel_0 = " << CS.point_accel_0[ci].transpose(); + } + else + { + std::cerr << "Forward Dynamic Contact Kokkevis: unsupported constraint \ + type." << std::endl; + assert(false); + abort(); + } + } + + // Now we can compute and apply the test forces and use their net effect + // to compute the inverse articlated inertia to fill K. + for (ci = 0; ci < CS.size(); ci++) { + + LOG << "=== Testforce Loop Start ===" << std::endl; + + unsigned int movable_body_id = 0; + Vector3d point_global; + + switch (CS.constraintType[ci]) { + + case ConstraintSet::ContactConstraint: + + movable_body_id = GetMovableBodyId(model, CS.body[ci]); + + // assemble the test force + LOG << "normal = " << CS.normal[ci].transpose() << std::endl; + + point_global = CalcBodyToBaseCoordinates(model, Q, CS.body[ci] + , CS.point[ci], false); + + LOG << "point_global = " << point_global.transpose() << std::endl; + + CS.f_t[ci] = SpatialTransform(Matrix3d::Identity(), -point_global) + .applyAdjoint(SpatialVector (0., 0., 0. + , -CS.normal[ci][0], -CS.normal[ci][1], -CS.normal[ci][2])); + CS.f_ext_constraints[movable_body_id] = CS.f_t[ci]; + + LOG << "f_t[" << movable_body_id << "] = " << CS.f_t[ci].transpose() + << std::endl; + + { + ForwardDynamicsAccelerationDeltas(model, CS, CS.QDDot_t + , movable_body_id, CS.f_ext_constraints); + + LOG << "QDDot_0 = " << CS.QDDot_0.transpose() << std::endl; + LOG << "QDDot_t = " << (CS.QDDot_t + CS.QDDot_0).transpose() + << std::endl; + LOG << "QDDot_t - QDDot_0 = " << (CS.QDDot_t).transpose() << std::endl; + } + + CS.f_ext_constraints[movable_body_id].setZero(); + + CS.QDDot_t += CS.QDDot_0; + + // compute the resulting acceleration + { + SUPPRESS_LOGGING; + UpdateKinematicsCustom(model, NULL, NULL, &CS.QDDot_t); + } + + for(unsigned int cj = 0; cj < CS.size(); cj++) { + { + SUPPRESS_LOGGING; + + point_accel_t = CalcPointAcceleration(model, Q, QDot, CS.QDDot_t + , CS.body[cj], CS.point[cj], false); + } + + LOG << "point_accel_0 = " << CS.point_accel_0[ci].transpose() + << std::endl; + LOG << "point_accel_t = " << point_accel_t.transpose() << std::endl; + + CS.K(ci,cj) = CS.normal[cj].dot(point_accel_t - CS.point_accel_0[cj]); + + } + + break; + + default: + + std::cerr << "Forward Dynamic Contact Kokkevis: unsupported constraint \ + type." << std::endl; + assert(false); + abort(); + + break; + + } + + } + + LOG << "K = " << std::endl << CS.K << std::endl; + LOG << "a = " << std::endl << CS.a << std::endl; + +#ifndef RBDL_USE_SIMPLE_MATH + switch (CS.linear_solver) { + case (LinearSolverPartialPivLU) : + CS.force = CS.K.partialPivLu().solve(CS.a); + break; + case (LinearSolverColPivHouseholderQR) : + CS.force = CS.K.colPivHouseholderQr().solve(CS.a); + break; + case (LinearSolverHouseholderQR) : + CS.force = CS.K.householderQr().solve(CS.a); + break; + default: + LOG << "Error: Invalid linear solver: " << CS.linear_solver << std::endl; + assert (0); + break; + } +#else + bool solve_successful = LinSolveGaussElimPivot (CS.K, CS.a, CS.force); + assert (solve_successful); +#endif + + LOG << "f = " << CS.force.transpose() << std::endl; + + for (ci = 0; ci < CS.size(); ci++) { + unsigned int body_id = CS.body[ci]; + unsigned int movable_body_id = body_id; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + movable_body_id = model.mFixedBodies[fbody_id].mMovableParent; + } + + CS.f_ext_constraints[movable_body_id] -= CS.f_t[ci] * CS.force[ci]; + LOG << "f_ext[" << movable_body_id << "] = " << CS.f_ext_constraints[movable_body_id].transpose() << std::endl; + } + + { + SUPPRESS_LOGGING; + ForwardDynamicsApplyConstraintForces (model, Tau, CS, QDDot); + } + + LOG << "QDDot after applying f_ext: " << QDDot.transpose() << std::endl; +} + +void SolveLinearSystem ( + const MatrixNd& A, + const VectorNd& b, + VectorNd& x, + LinearSolver ls + ) { + if(A.rows() != b.size() || A.cols() != x.size()) { + std::cerr << "Mismatching sizes." << std::endl; + assert(false); + abort(); + } + + // Solve the sistem A*x = b. + switch (ls) { + case (LinearSolverPartialPivLU) : + #ifdef RBDL_USE_SIMPLE_MATH + // SimpleMath does not have a LU solver so just use its QR solver + x = A.householderQr().solve(b); + #else + x = A.partialPivLu().solve(b); + #endif + break; + case (LinearSolverColPivHouseholderQR) : + x = A.colPivHouseholderQr().solve(b); + break; + case (LinearSolverHouseholderQR) : + x = A.householderQr().solve(b); + break; + default: + std::cerr << "Error: Invalid linear solver: " << ls << std::endl; + assert(false); + abort(); + break; + } +} + +unsigned int GetMovableBodyId (Model& model, unsigned int id) { + if(model.IsFixedBodyId(id)) { + unsigned int fbody_id = id - model.fixed_body_discriminator; + return model.mFixedBodies[fbody_id].mMovableParent; + } + else { + return id; + } +} + +} /* namespace RigidBodyDynamics */ diff --git a/3rdparty/rbdl/src/Dynamics.cc b/3rdparty/rbdl/src/Dynamics.cc new file mode 100644 index 0000000..228fd00 --- /dev/null +++ b/3rdparty/rbdl/src/Dynamics.cc @@ -0,0 +1,863 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include +#include +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Joint.h" +#include "rbdl/Body.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Kinematics.h" + +namespace RigidBodyDynamics { + +using namespace Math; + +RBDL_DLLAPI void InverseDynamics ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &QDDot, + VectorNd &Tau, + std::vector *f_ext) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0].set (0., 0., 0., -model.gravity[0], -model.gravity[1], -model.gravity[2]); + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, Q, QDot); + + model.v[i] = model.X_lambda[i].apply(model.v[lambda]) + model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + + if(model.mJoints[i].mJointType != JointTypeCustom){ + if (model.mJoints[i].mDoFCount == 1) { + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + + model.c[i] + + model.S[i] * QDDot[q_index]; + } else if (model.mJoints[i].mDoFCount == 3) { + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + + model.c[i] + + model.multdof3_S[i] * Vector3d (QDDot[q_index], + QDDot[q_index + 1], + QDDot[q_index + 2]); + } + }else if(model.mJoints[i].mJointType == JointTypeCustom){ + unsigned int k = model.mJoints[i].custom_joint_index; + VectorNd customJointQDDot(model.mCustomJoints[k]->mDoFCount); + for(int z=0; zmDoFCount; ++z){ + customJointQDDot[z] = QDDot[q_index+z]; + } + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + + model.c[i] + + model.mCustomJoints[k]->S * customJointQDDot; + } + + if (!model.mBodies[i].mIsVirtual) { + model.f[i] = model.I[i] * model.a[i] + crossf(model.v[i],model.I[i] * model.v[i]); + } else { + model.f[i].setZero(); + } + } + + if (f_ext != NULL) { + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + model.f[i] -= model.X_base[i].toMatrixAdjoint() * (*f_ext)[i]; + } + } + + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + if(model.mJoints[i].mJointType != JointTypeCustom){ + if (model.mJoints[i].mDoFCount == 1) { + Tau[model.mJoints[i].q_index] = model.S[i].dot(model.f[i]); + } else if (model.mJoints[i].mDoFCount == 3) { + Tau.block<3,1>(model.mJoints[i].q_index, 0) + = model.multdof3_S[i].transpose() * model.f[i]; + } + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int k = model.mJoints[i].custom_joint_index; + Tau.block(model.mJoints[i].q_index,0, + model.mCustomJoints[k]->mDoFCount, 1) + = model.mCustomJoints[k]->S.transpose() * model.f[i]; + } + + if (model.lambda[i] != 0) { + model.f[model.lambda[i]] = model.f[model.lambda[i]] + model.X_lambda[i].applyTranspose(model.f[i]); + } + } +} + +RBDL_DLLAPI void NonlinearEffects ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + VectorNd &Tau) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + SpatialVector spatial_gravity (0., 0., 0., -model.gravity[0], -model.gravity[1], -model.gravity[2]); + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0] = spatial_gravity; + + for (unsigned int i = 1; i < model.mJointUpdateOrder.size(); i++) { + jcalc (model, model.mJointUpdateOrder[i], Q, QDot); + } + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + if (model.lambda[i] == 0) { + model.v[i] = model.v_J[i]; + model.a[i] = model.X_lambda[i].apply(spatial_gravity); + } else { + model.v[i] = model.X_lambda[i].apply(model.v[model.lambda[i]]) + model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + model.a[i] = model.X_lambda[i].apply(model.a[model.lambda[i]]) + model.c[i]; + } + + if (!model.mBodies[i].mIsVirtual) { + model.f[i] = model.I[i] * model.a[i] + crossf(model.v[i],model.I[i] * model.v[i]); + } else { + model.f[i].setZero(); + } + } + + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + if(model.mJoints[i].mJointType != JointTypeCustom){ + if (model.mJoints[i].mDoFCount == 1) { + Tau[model.mJoints[i].q_index] + = model.S[i].dot(model.f[i]); + } else if (model.mJoints[i].mDoFCount == 3) { + Tau.block<3,1>(model.mJoints[i].q_index, 0) + = model.multdof3_S[i].transpose() * model.f[i]; + } + } else if(model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int k = model.mJoints[i].custom_joint_index; + Tau.block(model.mJoints[i].q_index,0, + model.mCustomJoints[k]->mDoFCount, 1) + = model.mCustomJoints[k]->S.transpose() * model.f[i]; + } + + if (model.lambda[i] != 0) { + model.f[model.lambda[i]] = model.f[model.lambda[i]] + model.X_lambda[i].applyTranspose(model.f[i]); + } + } +} + +RBDL_DLLAPI void CompositeRigidBodyAlgorithm ( + Model& model, + const VectorNd &Q, + MatrixNd &H, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + assert (H.rows() == model.dof_count && H.cols() == model.dof_count); + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + if (update_kinematics) { + jcalc_X_lambda_S (model, i, Q); + } + model.Ic[i] = model.I[i]; + } + + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + if (model.lambda[i] != 0) { + model.Ic[model.lambda[i]] = model.Ic[model.lambda[i]] + model.X_lambda[i].applyTranspose(model.Ic[i]); + } + + unsigned int dof_index_i = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + + SpatialVector F = model.Ic[i] * model.S[i]; + H(dof_index_i, dof_index_i) = model.S[i].dot(F); + + unsigned int j = i; + unsigned int dof_index_j = dof_index_i; + + while (model.lambda[j] != 0) { + F = model.X_lambda[j].applyTranspose(F); + j = model.lambda[j]; + dof_index_j = model.mJoints[j].q_index; + + if(model.mJoints[j].mJointType != JointTypeCustom) { + if (model.mJoints[j].mDoFCount == 1) { + H(dof_index_i,dof_index_j) = F.dot(model.S[j]); + H(dof_index_j,dof_index_i) = H(dof_index_i,dof_index_j); + } else if (model.mJoints[j].mDoFCount == 3) { + Vector3d H_temp2 = + (F.transpose() * model.multdof3_S[j]).transpose(); + LOG << F.transpose() << std::endl + << model.multdof3_S[j] << std::endl; + LOG << H_temp2.transpose() << std::endl; + + H.block<1,3>(dof_index_i,dof_index_j) = H_temp2.transpose(); + H.block<3,1>(dof_index_j,dof_index_i) = H_temp2; + } + } else if (model.mJoints[j].mJointType == JointTypeCustom){ + unsigned int k = model.mJoints[j].custom_joint_index; + unsigned int dof = model.mCustomJoints[k]->mDoFCount; + VectorNd H_temp2 = + (F.transpose() * model.mCustomJoints[k]->S).transpose(); + + LOG << F.transpose() + << std::endl + << model.mCustomJoints[j]->S << std::endl; + + LOG << H_temp2.transpose() << std::endl; + + H.block(dof_index_i,dof_index_j,1,dof) = H_temp2.transpose(); + H.block(dof_index_j,dof_index_i,dof,1) = H_temp2; + } + } + } else if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + Matrix63 F_63 = model.Ic[i].toMatrix() * model.multdof3_S[i]; + H.block<3,3>(dof_index_i, dof_index_i) = model.multdof3_S[i].transpose() * F_63; + + unsigned int j = i; + unsigned int dof_index_j = dof_index_i; + + while (model.lambda[j] != 0) { + F_63 = model.X_lambda[j].toMatrixTranspose() * (F_63); + j = model.lambda[j]; + dof_index_j = model.mJoints[j].q_index; + + if(model.mJoints[j].mJointType != JointTypeCustom){ + if (model.mJoints[j].mDoFCount == 1) { + Vector3d H_temp2 = F_63.transpose() * (model.S[j]); + + H.block<3,1>(dof_index_i,dof_index_j) = H_temp2; + H.block<1,3>(dof_index_j,dof_index_i) = H_temp2.transpose(); + } else if (model.mJoints[j].mDoFCount == 3) { + Matrix3d H_temp2 = F_63.transpose() * (model.multdof3_S[j]); + + H.block<3,3>(dof_index_i,dof_index_j) = H_temp2; + H.block<3,3>(dof_index_j,dof_index_i) = H_temp2.transpose(); + } + } else if (model.mJoints[j].mJointType == JointTypeCustom){ + unsigned int k = model.mJoints[j].custom_joint_index; + unsigned int dof = model.mCustomJoints[k]->mDoFCount; + + MatrixNd H_temp2 = F_63.transpose() * (model.mCustomJoints[k]->S); + + H.block(dof_index_i,dof_index_j,3,dof) = H_temp2; + H.block(dof_index_j,dof_index_i,dof,3) = H_temp2.transpose(); + } + } + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + + MatrixNd F_Nd = model.Ic[i].toMatrix() + * model.mCustomJoints[kI]->S; + + H.block(dof_index_i, dof_index_i,dofI,dofI) + = model.mCustomJoints[kI]->S.transpose() * F_Nd; + + unsigned int j = i; + unsigned int dof_index_j = dof_index_i; + + while (model.lambda[j] != 0) { + F_Nd = model.X_lambda[j].toMatrixTranspose() * (F_Nd); + j = model.lambda[j]; + dof_index_j = model.mJoints[j].q_index; + + if(model.mJoints[j].mJointType != JointTypeCustom){ + if (model.mJoints[j].mDoFCount == 1) { + MatrixNd H_temp2 = F_Nd.transpose() * (model.S[j]); + H.block( dof_index_i, dof_index_j, + H_temp2.rows(),H_temp2.cols()) = H_temp2; + H.block(dof_index_j,dof_index_i, + H_temp2.cols(),H_temp2.rows()) = H_temp2.transpose(); + } else if (model.mJoints[j].mDoFCount == 3) { + MatrixNd H_temp2 = F_Nd.transpose() * (model.multdof3_S[j]); + H.block(dof_index_i, dof_index_j, + H_temp2.rows(),H_temp2.cols()) = H_temp2; + H.block(dof_index_j, dof_index_i, + H_temp2.cols(),H_temp2.rows()) = H_temp2.transpose(); + } + } else if (model.mJoints[j].mJointType == JointTypeCustom){ + unsigned int k = model.mJoints[j].custom_joint_index; + unsigned int dof = model.mCustomJoints[k]->mDoFCount; + + MatrixNd H_temp2 = F_Nd.transpose() * (model.mCustomJoints[k]->S); + + H.block(dof_index_i,dof_index_j,3,dof) = H_temp2; + H.block(dof_index_j,dof_index_i,dof,3) = H_temp2.transpose(); + } + } + } + } +} + +RBDL_DLLAPI void ForwardDynamics ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &Tau, + VectorNd &QDDot, + std::vector *f_ext) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + SpatialVector spatial_gravity (0., 0., 0., model.gravity[0], model.gravity[1], model.gravity[2]); + + unsigned int i = 0; + + LOG << "Q = " << Q.transpose() << std::endl; + LOG << "QDot = " << QDot.transpose() << std::endl; + LOG << "Tau = " << Tau.transpose() << std::endl; + LOG << "---" << std::endl; + + // Reset the velocity of the root body + model.v[0].setZero(); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, Q, QDot); + + if (lambda != 0) + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + else + model.X_base[i] = model.X_lambda[i]; + + model.v[i] = model.X_lambda[i].apply( model.v[lambda]) + model.v_J[i]; + + /* + LOG << "X_J (" << i << "):" << std::endl << X_J << std::endl; + LOG << "v_J (" << i << "):" << std::endl << v_J << std::endl; + LOG << "v_lambda" << i << ":" << std::endl << model.v.at(lambda) << std::endl; + LOG << "X_base (" << i << "):" << std::endl << model.X_base[i] << std::endl; + LOG << "X_lambda (" << i << "):" << std::endl << model.X_lambda[i] << std::endl; + LOG << "SpatialVelocity (" << i << "): " << model.v[i] << std::endl; + */ + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + model.I[i].setSpatialMatrix (model.IA[i]); + + model.pA[i] = crossf(model.v[i],model.I[i] * model.v[i]); + + if (f_ext != NULL && (*f_ext)[i] != SpatialVector::Zero()) { + LOG << "External force (" << i << ") = " << model.X_base[i].toMatrixAdjoint() * (*f_ext)[i] << std::endl; + model.pA[i] -= model.X_base[i].toMatrixAdjoint() * (*f_ext)[i]; + } + } + + // ClearLogOutput(); + + LOG << "--- first loop ---" << std::endl; + + for (i = model.mBodies.size() - 1; i > 0; i--) { + unsigned int q_index = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + + model.U[i] = model.IA[i] * model.S[i]; + model.d[i] = model.S[i].dot(model.U[i]); + model.u[i] = Tau[q_index] - model.S[i].dot(model.pA[i]); + // LOG << "u[" << i << "] = " << model.u[i] << std::endl; + + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - model.U[i] + * (model.U[i] / model.d[i]).transpose(); + + SpatialVector pa = model.pA[i] + + Ia * model.c[i] + + model.U[i] * model.u[i] / model.d[i]; + +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() + += model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix(); + model.pA[lambda].noalias() + += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] + += model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix(); + + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() << std::endl; + } + } else if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + model.multdof3_U[i] = model.IA[i] * model.multdof3_S[i]; +#ifdef EIGEN_CORE_H + model.multdof3_Dinv[i] = (model.multdof3_S[i].transpose() + * model.multdof3_U[i]).inverse().eval(); +#else + model.multdof3_Dinv[i] = (model.multdof3_S[i].transpose() + * model.multdof3_U[i]).inverse(); +#endif + VectorNd tau_temp(Tau.block(q_index,0,3,1)); + model.multdof3_u[i] = tau_temp + - model.multdof3_S[i].transpose() * model.pA[i]; + + // LOG << "multdof3_u[" << i << "] = " + // << model.multdof3_u[i].transpose() << std::endl; + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - model.multdof3_U[i] + * model.multdof3_Dinv[i] + * model.multdof3_U[i].transpose(); + SpatialVector pa = model.pA[i] + + Ia + * model.c[i] + + model.multdof3_U[i] + * model.multdof3_Dinv[i] + * model.multdof3_u[i]; +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() + += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); + + model.pA[lambda].noalias() + += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] + += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); + + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() + << std::endl; + } + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + model.mCustomJoints[kI]->U = + model.IA[i] * model.mCustomJoints[kI]->S; + +#ifdef EIGEN_CORE_H + model.mCustomJoints[kI]->Dinv + = (model.mCustomJoints[kI]->S.transpose() + * model.mCustomJoints[kI]->U).inverse().eval(); +#else + model.mCustomJoints[kI]->Dinv + = (model.mCustomJoints[kI]->S.transpose() + * model.mCustomJoints[kI]->U).inverse(); +#endif + VectorNd tau_temp(Tau.block(q_index,0,dofI,1)); + model.mCustomJoints[kI]->u = tau_temp + - model.mCustomJoints[kI]->S.transpose() * model.pA[i]; + + // LOG << "multdof3_u[" << i << "] = " + // << model.multdof3_u[i].transpose() << std::endl; + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - (model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->U.transpose()); + SpatialVector pa = model.pA[i] + + Ia * model.c[i] + + (model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->u); + +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.IA[lambda] += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() + << std::endl; + } + } + } + + // ClearLogOutput(); + + model.a[0] = spatial_gravity * -1.; + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + SpatialTransform X_lambda = model.X_lambda[i]; + + model.a[i] = X_lambda.apply(model.a[lambda]) + model.c[i]; + LOG << "a'[" << i << "] = " << model.a[i].transpose() << std::endl; + + if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + QDDot[q_index] = (1./model.d[i]) * (model.u[i] - model.U[i].dot(model.a[i])); + model.a[i] = model.a[i] + model.S[i] * QDDot[q_index]; + } else if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + Vector3d qdd_temp = model.multdof3_Dinv[i] * (model.multdof3_u[i] - model.multdof3_U[i].transpose() * model.a[i]); + QDDot[q_index] = qdd_temp[0]; + QDDot[q_index + 1] = qdd_temp[1]; + QDDot[q_index + 2] = qdd_temp[2]; + model.a[i] = model.a[i] + model.multdof3_S[i] * qdd_temp; + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI=model.mCustomJoints[kI]->mDoFCount; + + VectorNd qdd_temp = model.mCustomJoints[kI]->Dinv + * ( model.mCustomJoints[kI]->u + - model.mCustomJoints[kI]->U.transpose() + * model.a[i]); + + for(int z=0; zS * qdd_temp; + } + } + + LOG << "QDDot = " << QDDot.transpose() << std::endl; +} + +RBDL_DLLAPI void ForwardDynamicsLagrangian ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &Tau, + VectorNd &QDDot, + Math::LinearSolver linear_solver, + std::vector *f_ext, + Math::MatrixNd *H, + Math::VectorNd *C) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + bool free_H = false; + bool free_C = false; + + if (H == NULL) { + H = new MatrixNd (MatrixNd::Zero(model.dof_count, model.dof_count)); + free_H = true; + } + + if (C == NULL) { + C = new VectorNd (VectorNd::Zero(model.dof_count)); + free_C = true; + } + + // we set QDDot to zero to compute C properly with the InverseDynamics + // method. + QDDot.setZero(); + + InverseDynamics (model, Q, QDot, QDDot, (*C), f_ext); + CompositeRigidBodyAlgorithm (model, Q, *H, false); + + LOG << "A = " << std::endl << *H << std::endl; + LOG << "b = " << std::endl << *C * -1. + Tau << std::endl; + +#ifndef RBDL_USE_SIMPLE_MATH + switch (linear_solver) { + case (LinearSolverPartialPivLU) : + QDDot = H->partialPivLu().solve (*C * -1. + Tau); + break; + case (LinearSolverColPivHouseholderQR) : + QDDot = H->colPivHouseholderQr().solve (*C * -1. + Tau); + break; + case (LinearSolverHouseholderQR) : + QDDot = H->householderQr().solve (*C * -1. + Tau); + break; + case (LinearSolverLLT) : + QDDot = H->llt().solve (*C * -1. + Tau); + break; + default: + LOG << "Error: Invalid linear solver: " << linear_solver << std::endl; + assert (0); + break; + } +#else + bool solve_successful = LinSolveGaussElimPivot (*H, *C * -1. + Tau, QDDot); + assert (solve_successful); +#endif + + if (free_C) { + delete C; + } + + if (free_H) { + delete H; + } + + LOG << "x = " << QDDot << std::endl; +} + +RBDL_DLLAPI void CalcMInvTimesTau ( Model &model, + const VectorNd &Q, + const VectorNd &Tau, + VectorNd &QDDot, + bool update_kinematics) { + + LOG << "Q = " << Q.transpose() << std::endl; + LOG << "---" << std::endl; + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0].setZero(); + + if (update_kinematics) { + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + jcalc_X_lambda_S (model, model.mJointUpdateOrder[i], Q); + + model.v_J[i].setZero(); + model.v[i].setZero(); + model.c_J[i].setZero(); + model.pA[i].setZero(); + model.I[i].setSpatialMatrix (model.IA[i]); + } + } + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + model.pA[i].setZero(); + } + + // ClearLogOutput(); + + if (update_kinematics) { + // Compute Articulate Body Inertias + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + unsigned int q_index = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + model.U[i] = model.IA[i] * model.S[i]; + model.d[i] = model.S[i].dot(model.U[i]); + // LOG << "u[" << i << "] = " << model.u[i] << std::endl; + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] - + model.U[i] * (model.U[i] / model.d[i]).transpose(); +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); +#else + model.IA[lambda] += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); +#endif + } + } else if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + + model.multdof3_U[i] = model.IA[i] * model.multdof3_S[i]; + +#ifdef EIGEN_CORE_H + model.multdof3_Dinv[i] = + (model.multdof3_S[i].transpose()*model.multdof3_U[i]).inverse().eval(); +#else + model.multdof3_Dinv[i] = + (model.multdof3_S[i].transpose() * model.multdof3_U[i]).inverse(); +#endif + // LOG << "mCustomJoints[kI]->u[" << i << "] = " + //<< model.mCustomJoints[kI]->u[i].transpose() << std::endl; + + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - ( model.multdof3_U[i] + * model.multdof3_Dinv[i] + * model.multdof3_U[i].transpose()); +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += + model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); +#else + model.IA[lambda] += + model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix(); +#endif + } + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + model.mCustomJoints[kI]->U = model.IA[i] * model.mCustomJoints[kI]->S; + +#ifdef EIGEN_CORE_H + model.mCustomJoints[kI]->Dinv = (model.mCustomJoints[kI]->S.transpose() + * model.mCustomJoints[kI]->U + ).inverse().eval(); +#else + model.mCustomJoints[kI]->Dinv=(model.mCustomJoints[kI]->S.transpose() + * model.mCustomJoints[kI]->U + ).inverse(); +#endif + // LOG << "mCustomJoints[kI]->u[" << i << "] = " + //<< model.mCustomJoints[kI]->u.transpose() << std::endl; + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + SpatialMatrix Ia = model.IA[i] + - ( model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->U.transpose()); +#ifdef EIGEN_CORE_H + model.IA[lambda].noalias() += model.X_lambda[i].toMatrixTranspose() + * Ia + * model.X_lambda[i].toMatrix(); +#else + model.IA[lambda] += model.X_lambda[i].toMatrixTranspose() + * Ia * model.X_lambda[i].toMatrix(); +#endif + } + } + } + } + + // compute articulated bias forces + for (unsigned int i = model.mBodies.size() - 1; i > 0; i--) { + unsigned int q_index = model.mJoints[i].q_index; + + if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + + model.u[i] = Tau[q_index] - model.S[i].dot(model.pA[i]); + // LOG << "u[" << i << "] = " << model.u[i] << std::endl; + unsigned int lambda = model.lambda[i]; + if (lambda != 0) { + SpatialVector pa = model.pA[i] + model.U[i] * model.u[i] / model.d[i]; + +#ifdef EIGEN_CORE_H + model.pA[lambda].noalias() += model.X_lambda[i].applyTranspose(pa); +#else + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() << std::endl; + } + } else if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + + Vector3d tau_temp ( Tau[q_index], + Tau[q_index + 1], + Tau[q_index + 2]); + model.multdof3_u[i] = tau_temp + - model.multdof3_S[i].transpose()*model.pA[i]; + // LOG << "multdof3_u[" << i << "] = " + // << model.multdof3_u[i].transpose() << std::endl; + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + SpatialVector pa = model.pA[i] + + model.multdof3_U[i] + * model.multdof3_Dinv[i] + * model.multdof3_u[i]; + +#ifdef EIGEN_CORE_H + model.pA[lambda].noalias() += + model.X_lambda[i].applyTranspose(pa); +#else + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() << std::endl; + } + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + VectorNd tau_temp(Tau.block(q_index,0,dofI,1)); + + model.mCustomJoints[kI]->u = + tau_temp - ( model.mCustomJoints[kI]->S.transpose()* model.pA[i]); + // LOG << "mCustomJoints[kI]->u" + // << model.mCustomJoints[kI]->u.transpose() << std::endl; + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + SpatialVector pa = model.pA[i] + + ( model.mCustomJoints[kI]->U + * model.mCustomJoints[kI]->Dinv + * model.mCustomJoints[kI]->u); + +#ifdef EIGEN_CORE_H + model.pA[lambda].noalias() += + model.X_lambda[i].applyTranspose(pa); +#else + model.pA[lambda] += model.X_lambda[i].applyTranspose(pa); +#endif + LOG << "pA[" << lambda << "] = " + << model.pA[lambda].transpose() << std::endl; + } + } + } + + // ClearLogOutput(); + + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + SpatialTransform X_lambda = model.X_lambda[i]; + + model.a[i] = X_lambda.apply(model.a[lambda]) + model.c[i]; + LOG << "a'[" << i << "] = " << model.a[i].transpose() << std::endl; + + if (model.mJoints[i].mDoFCount == 1 + && model.mJoints[i].mJointType != JointTypeCustom) { + QDDot[q_index] = (1./model.d[i])*(model.u[i]-model.U[i].dot(model.a[i])); + model.a[i] = model.a[i] + model.S[i] * QDDot[q_index]; + } else if (model.mJoints[i].mDoFCount == 3 + && model.mJoints[i].mJointType != JointTypeCustom) { + Vector3d qdd_temp = + model.multdof3_Dinv[i] * (model.multdof3_u[i] + - model.multdof3_U[i].transpose()*model.a[i]); + + QDDot[q_index] = qdd_temp[0]; + QDDot[q_index + 1] = qdd_temp[1]; + QDDot[q_index + 2] = qdd_temp[2]; + model.a[i] = model.a[i] + model.multdof3_S[i] * qdd_temp; + } else if (model.mJoints[i].mJointType == JointTypeCustom) { + unsigned int kI = model.mJoints[i].custom_joint_index; + unsigned int dofI = model.mCustomJoints[kI]->mDoFCount; + + VectorNd qdd_temp = model.mCustomJoints[kI]->Dinv + * ( model.mCustomJoints[kI]->u + - model.mCustomJoints[kI]->U.transpose() * model.a[i]); + + for(int z=0; zS * qdd_temp; + } + } + + LOG << "QDDot = " << QDDot.transpose() << std::endl; +} + +} /* namespace RigidBodyDynamics */ diff --git a/3rdparty/rbdl/src/Joint.cc b/3rdparty/rbdl/src/Joint.cc new file mode 100644 index 0000000..0c393d4 --- /dev/null +++ b/3rdparty/rbdl/src/Joint.cc @@ -0,0 +1,436 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Joint.h" + +namespace RigidBodyDynamics { + +using namespace Math; + +RBDL_DLLAPI void jcalc ( + Model &model, + unsigned int joint_id, + const VectorNd &q, + const VectorNd &qdot + ) { + // exception if we calculate it for the root body + assert (joint_id > 0); + + if (model.mJoints[joint_id].mJointType == JointTypeRevoluteX) { + model.X_J[joint_id] = Xrotx (q[model.mJoints[joint_id].q_index]); + model.v_J[joint_id][0] = qdot[model.mJoints[joint_id].q_index]; + } else if (model.mJoints[joint_id].mJointType == JointTypeRevoluteY) { + model.X_J[joint_id] = Xroty (q[model.mJoints[joint_id].q_index]); + model.v_J[joint_id][1] = qdot[model.mJoints[joint_id].q_index]; + } else if (model.mJoints[joint_id].mJointType == JointTypeRevoluteZ) { + model.X_J[joint_id] = Xrotz (q[model.mJoints[joint_id].q_index]); + model.v_J[joint_id][2] = qdot[model.mJoints[joint_id].q_index]; + } else if (model.mJoints[joint_id].mJointType == JointTypeHelical) { + model.X_J[joint_id] = jcalc_XJ (model, joint_id, q); + jcalc_X_lambda_S(model, joint_id, q); + double Jqd = qdot[model.mJoints[joint_id].q_index]; + model.v_J[joint_id] = model.S[joint_id] * Jqd; + + Vector3d St = model.S[joint_id].block(0,0,3,1); + Vector3d c = model.X_J[joint_id].E * model.mJoints[joint_id].mJointAxes[0].block(3,0,3,1); + c = St.cross(c); + c *= -Jqd * Jqd; + model.c_J[joint_id] = SpatialVector(0,0,0,c[0],c[1],c[2]); + } else if (model.mJoints[joint_id].mDoFCount == 1 && + model.mJoints[joint_id].mJointType != JointTypeCustom) { + model.X_J[joint_id] = jcalc_XJ (model, joint_id, q); + model.v_J[joint_id] = + model.S[joint_id] * qdot[model.mJoints[joint_id].q_index]; + } else if (model.mJoints[joint_id].mJointType == JointTypeSpherical) { + model.X_J[joint_id] = + SpatialTransform (model.GetQuaternion (joint_id, q).toMatrix(), + Vector3d (0., 0., 0.)); + + model.multdof3_S[joint_id](0,0) = 1.; + model.multdof3_S[joint_id](1,1) = 1.; + model.multdof3_S[joint_id](2,2) = 1.; + + Vector3d omega (qdot[model.mJoints[joint_id].q_index], + qdot[model.mJoints[joint_id].q_index+1], + qdot[model.mJoints[joint_id].q_index+2]); + + model.v_J[joint_id] = SpatialVector ( + omega[0], omega[1], omega[2], + 0., 0., 0.); + } else if (model.mJoints[joint_id].mJointType == JointTypeEulerZYX) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_J[joint_id].E = Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ); + + model.multdof3_S[joint_id](0,0) = -s1; + model.multdof3_S[joint_id](0,2) = 1.; + + model.multdof3_S[joint_id](1,0) = c1 * s2; + model.multdof3_S[joint_id](1,1) = c2; + + model.multdof3_S[joint_id](2,0) = c1 * c2; + model.multdof3_S[joint_id](2,1) = - s2; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = + model.multdof3_S[joint_id] * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set( + -c1*qdot0*qdot1, + -s1*s2*qdot0*qdot1 + c1*c2*qdot0*qdot2 - s2*qdot1*qdot2, + -s1*c2*qdot0*qdot1 - c1*s2*qdot0*qdot2 - c2*qdot1*qdot2, + 0.,0., 0.); + } else if (model.mJoints[joint_id].mJointType == JointTypeEulerXYZ) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_J[joint_id].E = Matrix3d( + c2 * c1, s2 * c0 + c2 * s1 * s0, s2 * s0 - c2 * s1 * c0, + -s2 * c1, c2 * c0 - s2 * s1 * s0, c2 * s0 + s2 * s1 * c0, + s1, -c1 * s0, c1 * c0 + ); + + model.multdof3_S[joint_id](0,0) = c2 * c1; + model.multdof3_S[joint_id](0,1) = s2; + + model.multdof3_S[joint_id](1,0) = -s2 * c1; + model.multdof3_S[joint_id](1,1) = c2; + + model.multdof3_S[joint_id](2,0) = s1; + model.multdof3_S[joint_id](2,2) = 1.; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = + model.multdof3_S[joint_id] * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set( + -s2*c1*qdot2*qdot0 - c2*s1*qdot1*qdot0 + c2*qdot2*qdot1, + -c2*c1*qdot2*qdot0 + s2*s1*qdot1*qdot0 - s2*qdot2*qdot1, + c1*qdot1*qdot0, + 0., 0., 0. + ); + } else if (model.mJoints[joint_id].mJointType == JointTypeEulerYXZ) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_J[joint_id].E = Matrix3d( + c2 * c0 + s2 * s1 * s0, s2 * c1, -c2 * s0 + s2 * s1 * c0, + -s2 * c0 + c2 * s1 * s0, c2 * c1, s2 * s0 + c2 * s1 * c0, + c1 * s0, - s1, c1 * c0); + + model.multdof3_S[joint_id](0,0) = s2 * c1; + model.multdof3_S[joint_id](0,1) = c2; + + model.multdof3_S[joint_id](1,0) = c2 * c1; + model.multdof3_S[joint_id](1,1) = -s2; + + model.multdof3_S[joint_id](2,0) = -s1; + model.multdof3_S[joint_id](2,2) = 1.; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = + model.multdof3_S[joint_id] * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set( + c2*c1*qdot2*qdot0 - s2*s1*qdot1*qdot0 - s2*qdot2*qdot1, + -s2*c1*qdot2*qdot0 - c2*s1*qdot1*qdot0 - c2*qdot2*qdot1, + -c1*qdot1*qdot0, + 0., 0., 0. + ); + } else if(model.mJoints[joint_id].mJointType == JointTypeTranslationXYZ){ + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + model.X_J[joint_id].E = Matrix3d::Identity(); + model.X_J[joint_id].r = Vector3d (q0, q1, q2); + + model.multdof3_S[joint_id](3,0) = 1.; + model.multdof3_S[joint_id](4,1) = 1.; + model.multdof3_S[joint_id](5,2) = 1.; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = + model.multdof3_S[joint_id] * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set(0., 0., 0., 0., 0., 0.); + } else if (model.mJoints[joint_id].mJointType == JointTypeCustom) { + const Joint &joint = model.mJoints[joint_id]; + CustomJoint *custom_joint = + model.mCustomJoints[joint.custom_joint_index]; + custom_joint->jcalc (model, joint_id, q, qdot); + } else { + std::cerr << "Error: invalid joint type " << model.mJoints[joint_id].mJointType << " at id " << joint_id << std::endl; + abort(); + } + + model.X_lambda[joint_id] = model.X_J[joint_id] * model.X_T[joint_id]; +} + +RBDL_DLLAPI Math::SpatialTransform jcalc_XJ ( + Model &model, + unsigned int joint_id, + const Math::VectorNd &q) { + // exception if we calculate it for the root body + assert (joint_id > 0); + + if (model.mJoints[joint_id].mDoFCount == 1 + && model.mJoints[joint_id].mJointType != JointTypeCustom) { + if (model.mJoints[joint_id].mJointType == JointTypeRevolute) { + return Xrot (q[model.mJoints[joint_id].q_index], Vector3d ( + model.mJoints[joint_id].mJointAxes[0][0], + model.mJoints[joint_id].mJointAxes[0][1], + model.mJoints[joint_id].mJointAxes[0][2] + )); + } else if (model.mJoints[joint_id].mJointType == JointTypePrismatic) { + return Xtrans ( Vector3d ( + model.mJoints[joint_id].mJointAxes[0][3] + * q[model.mJoints[joint_id].q_index], + model.mJoints[joint_id].mJointAxes[0][4] + * q[model.mJoints[joint_id].q_index], + model.mJoints[joint_id].mJointAxes[0][5] + * q[model.mJoints[joint_id].q_index] + ) + ); + } else if (model.mJoints[joint_id].mJointType == JointTypeHelical) { + SpatialTransform rot = Xrot( + q[model.mJoints[joint_id].q_index], Vector3d ( + model.mJoints[joint_id].mJointAxes[0][0], + model.mJoints[joint_id].mJointAxes[0][1], + model.mJoints[joint_id].mJointAxes[0][2] + )); + SpatialTransform trans = Xtrans ( Vector3d ( + model.mJoints[joint_id].mJointAxes[0][3] + * q[model.mJoints[joint_id].q_index], + model.mJoints[joint_id].mJointAxes[0][4] + * q[model.mJoints[joint_id].q_index], + model.mJoints[joint_id].mJointAxes[0][5] + * q[model.mJoints[joint_id].q_index] + ) + ); + return rot * trans; + } + } + std::cerr << "Error: invalid joint type: " << model.mJoints[joint_id].mJointType << std::endl; + abort(); + return SpatialTransform(); +} + +RBDL_DLLAPI void jcalc_X_lambda_S ( + Model &model, + unsigned int joint_id, + const VectorNd &q + ) { + // exception if we calculate it for the root body + assert (joint_id > 0); + + if (model.mJoints[joint_id].mJointType == JointTypeRevoluteX) { + model.X_lambda[joint_id] = + Xrotx (q[model.mJoints[joint_id].q_index]) * model.X_T[joint_id]; + model.S[joint_id] = model.mJoints[joint_id].mJointAxes[0]; + } else if (model.mJoints[joint_id].mJointType == JointTypeRevoluteY) { + model.X_lambda[joint_id] = + Xroty (q[model.mJoints[joint_id].q_index]) * model.X_T[joint_id]; + model.S[joint_id] = model.mJoints[joint_id].mJointAxes[0]; + } else if (model.mJoints[joint_id].mJointType == JointTypeRevoluteZ) { + model.X_lambda[joint_id] = + Xrotz (q[model.mJoints[joint_id].q_index]) * model.X_T[joint_id]; + model.S[joint_id] = model.mJoints[joint_id].mJointAxes[0]; + } else if (model.mJoints[joint_id].mJointType == JointTypeHelical){ + SpatialTransform XJ = jcalc_XJ (model, joint_id, q); + model.X_lambda[joint_id] = XJ * model.X_T[joint_id]; + // Set the joint axis + Vector3d trans = XJ.E * model.mJoints[joint_id].mJointAxes[0].block(3,0,3,1); + + model.S[joint_id] = SpatialVector(model.mJoints[joint_id].mJointAxes[0][0], + model.mJoints[joint_id].mJointAxes[0][1], + model.mJoints[joint_id].mJointAxes[0][2], + trans[0], trans[1], trans[2]); + } else if (model.mJoints[joint_id].mDoFCount == 1 + && model.mJoints[joint_id].mJointType != JointTypeCustom){ + model.X_lambda[joint_id] = + jcalc_XJ (model, joint_id, q) * model.X_T[joint_id]; + model.S[joint_id] = model.mJoints[joint_id].mJointAxes[0]; + } else if (model.mJoints[joint_id].mJointType == JointTypeSpherical) { + model.X_lambda[joint_id] = SpatialTransform ( + model.GetQuaternion (joint_id, q).toMatrix(), + Vector3d (0., 0., 0.)) + * model.X_T[joint_id]; + + model.multdof3_S[joint_id].setZero(); + + model.multdof3_S[joint_id](0,0) = 1.; + model.multdof3_S[joint_id](1,1) = 1.; + model.multdof3_S[joint_id](2,2) = 1.; + } else if (model.mJoints[joint_id].mJointType == JointTypeEulerZYX) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_lambda[joint_id] = SpatialTransform ( + Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ), + Vector3d (0., 0., 0.)) + * model.X_T[joint_id]; + + model.multdof3_S[joint_id].setZero(); + + model.multdof3_S[joint_id](0,0) = -s1; + model.multdof3_S[joint_id](0,2) = 1.; + + model.multdof3_S[joint_id](1,0) = c1 * s2; + model.multdof3_S[joint_id](1,1) = c2; + + model.multdof3_S[joint_id](2,0) = c1 * c2; + model.multdof3_S[joint_id](2,1) = - s2; + } else if (model.mJoints[joint_id].mJointType == JointTypeEulerXYZ) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_lambda[joint_id] = SpatialTransform ( + Matrix3d( + c2 * c1, s2 * c0 + c2 * s1 * s0, s2 * s0 - c2 * s1 * c0, + -s2 * c1, c2 * c0 - s2 * s1 * s0, c2 * s0 + s2 * s1 * c0, + s1, -c1 * s0, c1 * c0 + ), + Vector3d (0., 0., 0.)) + * model.X_T[joint_id]; + + model.multdof3_S[joint_id].setZero(); + + model.multdof3_S[joint_id](0,0) = c2 * c1; + model.multdof3_S[joint_id](0,1) = s2; + + model.multdof3_S[joint_id](1,0) = -s2 * c1; + model.multdof3_S[joint_id](1,1) = c2; + + model.multdof3_S[joint_id](2,0) = s1; + model.multdof3_S[joint_id](2,2) = 1.; + } else if (model.mJoints[joint_id].mJointType == JointTypeEulerYXZ ) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_lambda[joint_id] = SpatialTransform ( + Matrix3d( + c2 * c0 + s2 * s1 * s0, s2 * c1, -c2 * s0 + s2 * s1 * c0, + -s2 * c0 + c2 * s1 * s0, c2 * c1, s2 * s0 + c2 * s1 * c0, + c1 * s0, - s1, c1 * c0 + ), + Vector3d (0., 0., 0.)) + * model.X_T[joint_id]; + + model.multdof3_S[joint_id].setZero(); + + model.multdof3_S[joint_id](0,0) = s2 * c1; + model.multdof3_S[joint_id](0,1) = c2; + + model.multdof3_S[joint_id](1,0) = c2 * c1; + model.multdof3_S[joint_id](1,1) = -s2; + + model.multdof3_S[joint_id](2,0) = -s1; + model.multdof3_S[joint_id](2,2) = 1.; + } else if (model.mJoints[joint_id].mJointType == JointTypeTranslationXYZ) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + model.X_lambda[joint_id] = SpatialTransform ( + Matrix3d::Identity (3,3), + Vector3d (q0, q1, q2)) + * model.X_T[joint_id]; + + model.multdof3_S[joint_id].setZero(); + + model.multdof3_S[joint_id](3,0) = 1.; + model.multdof3_S[joint_id](4,1) = 1.; + model.multdof3_S[joint_id](5,2) = 1.; + } else if (model.mJoints[joint_id].mJointType == JointTypeCustom) { + const Joint &joint = model.mJoints[joint_id]; + CustomJoint *custom_joint + = model.mCustomJoints[joint.custom_joint_index]; + + custom_joint->jcalc_X_lambda_S (model, joint_id, q); + } else { + std::cerr << "Error: invalid joint type!" << std::endl; + abort(); + } +} +} diff --git a/3rdparty/rbdl/src/Kinematics.cc b/3rdparty/rbdl/src/Kinematics.cc new file mode 100644 index 0000000..d21f5e7 --- /dev/null +++ b/3rdparty/rbdl/src/Kinematics.cc @@ -0,0 +1,918 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include +#include +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" + +namespace RigidBodyDynamics { + +using namespace Math; + +RBDL_DLLAPI void UpdateKinematics( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &QDDot) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + unsigned int i; + + SpatialVector spatial_gravity (0., + 0., + 0., + model.gravity[0], + model.gravity[1], + model.gravity[2]); + + model.a[0].setZero(); + + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, Q, QDot); + + model.X_lambda[i] = model.X_J[i] * model.X_T[i]; + + if (lambda != 0) { + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + model.v[i] = model.X_lambda[i].apply(model.v[lambda]) + model.v_J[i]; + } else { + model.X_base[i] = model.X_lambda[i]; + model.v[i] = model.v_J[i]; + } + + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + model.c[i]; + + if(model.mJoints[i].mJointType != JointTypeCustom){ + if (model.mJoints[i].mDoFCount == 1) { + model.a[i] = model.a[i] + model.S[i] * QDDot[q_index]; + } else if (model.mJoints[i].mDoFCount == 3) { + Vector3d omegadot_temp (QDDot[q_index], + QDDot[q_index + 1], + QDDot[q_index + 2]); + model.a[i] = model.a[i] + model.multdof3_S[i] * omegadot_temp; + } + } else { + unsigned int custom_index = model.mJoints[i].custom_joint_index; + const CustomJoint* custom_joint = model.mCustomJoints[custom_index]; + unsigned int joint_dof_count = custom_joint->mDoFCount; + + model.a[i] = model.a[i] + + ( model.mCustomJoints[custom_index]->S + * QDDot.block(q_index, 0, joint_dof_count, 1)); + } + } + + for (i = 1; i < model.mBodies.size(); i++) { + LOG << "a[" << i << "] = " << model.a[i].transpose() << std::endl; + } +} + +RBDL_DLLAPI void UpdateKinematicsCustom( + Model &model, + const VectorNd *Q, + const VectorNd *QDot, + const VectorNd *QDDot) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + unsigned int i; + + if (Q) { + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + + VectorNd QDot_zero (VectorNd::Zero (model.q_size)); + + jcalc (model, i, (*Q), QDot_zero); + + model.X_lambda[i] = model.X_J[i] * model.X_T[i]; + + if (lambda != 0) { + model.X_base[i] = model.X_lambda[i] * model.X_base[lambda]; + } else { + model.X_base[i] = model.X_lambda[i]; + } + } + } + + if (QDot) { + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int lambda = model.lambda[i]; + + jcalc (model, i, *Q, *QDot); + + if (lambda != 0) { + model.v[i] = model.X_lambda[i].apply(model.v[lambda]) + model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + } else { + model.v[i] = model.v_J[i]; + model.c[i] = model.c_J[i] + crossm(model.v[i],model.v_J[i]); + } + // LOG << "v[" << i << "] = " << model.v[i].transpose() << std::endl; + } + } + + // FIXME?: Changing QDot can alter body accelerations via c[] - update to QDot but not QDDot can result in incorrect a[] + if (QDDot) { + for (i = 1; i < model.mBodies.size(); i++) { + unsigned int q_index = model.mJoints[i].q_index; + + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + model.a[i] = model.X_lambda[i].apply(model.a[lambda]) + model.c[i]; + } else { + model.a[i] = model.c[i]; + } + + if( model.mJoints[i].mJointType != JointTypeCustom){ + if (model.mJoints[i].mDoFCount == 1) { + model.a[i] = model.a[i] + model.S[i] * (*QDDot)[q_index]; + } else if (model.mJoints[i].mDoFCount == 3) { + Vector3d omegadot_temp ((*QDDot)[q_index], + (*QDDot)[q_index + 1], + (*QDDot)[q_index + 2]); + model.a[i] = model.a[i] + + model.multdof3_S[i] * omegadot_temp; + } + } else { + unsigned int k = model.mJoints[i].custom_joint_index; + + const CustomJoint* custom_joint = model.mCustomJoints[k]; + unsigned int joint_dof_count = custom_joint->mDoFCount; + + model.a[i] = model.a[i] + + ( (model.mCustomJoints[k]->S) + *(QDDot->block(q_index, 0, joint_dof_count, 1))); + } + } + } +} + +RBDL_DLLAPI Vector3d CalcBodyToBaseCoordinates ( + Model &model, + const VectorNd &Q, + unsigned int body_id, + const Vector3d &point_body_coordinates, + bool update_kinematics) { + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + if (body_id >= model.fixed_body_discriminator) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + unsigned int parent_id = model.mFixedBodies[fbody_id].mMovableParent; + + Matrix3d fixed_rotation = + model.mFixedBodies[fbody_id].mParentTransform.E.transpose(); + Vector3d fixed_position = model.mFixedBodies[fbody_id].mParentTransform.r; + + Matrix3d parent_body_rotation = model.X_base[parent_id].E.transpose(); + Vector3d parent_body_position = model.X_base[parent_id].r; + + return (parent_body_position + + (parent_body_rotation + * (fixed_position + fixed_rotation * (point_body_coordinates))) ); + } + + Matrix3d body_rotation = model.X_base[body_id].E.transpose(); + Vector3d body_position = model.X_base[body_id].r; + + return body_position + body_rotation * point_body_coordinates; +} + +RBDL_DLLAPI Vector3d CalcBaseToBodyCoordinates ( + Model &model, + const VectorNd &Q, + unsigned int body_id, + const Vector3d &point_base_coordinates, + bool update_kinematics) { + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + if (body_id >= model.fixed_body_discriminator) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + unsigned int parent_id = model.mFixedBodies[fbody_id].mMovableParent; + + Matrix3d fixed_rotation = model.mFixedBodies[fbody_id].mParentTransform.E; + Vector3d fixed_position = model.mFixedBodies[fbody_id].mParentTransform.r; + + Matrix3d parent_body_rotation = model.X_base[parent_id].E; + Vector3d parent_body_position = model.X_base[parent_id].r; + + return (fixed_rotation + * ( - fixed_position + - parent_body_rotation + * (parent_body_position - point_base_coordinates))); + } + + Matrix3d body_rotation = model.X_base[body_id].E; + Vector3d body_position = model.X_base[body_id].r; + + return body_rotation * (point_base_coordinates - body_position); +} + +RBDL_DLLAPI Matrix3d CalcBodyWorldOrientation( + Model &model, + const VectorNd &Q, + const unsigned int body_id, + bool update_kinematics) { + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + if (body_id >= model.fixed_body_discriminator) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + model.mFixedBodies[fbody_id].mBaseTransform = + model.mFixedBodies[fbody_id].mParentTransform + * model.X_base[model.mFixedBodies[fbody_id].mMovableParent]; + + return model.mFixedBodies[fbody_id].mBaseTransform.E; + } + + return model.X_base[body_id].E; +} + +RBDL_DLLAPI void CalcPointJacobian ( + Model &model, + const VectorNd &Q, + unsigned int body_id, + const Vector3d &point_position, + MatrixNd &G, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + SpatialTransform point_trans = + SpatialTransform (Matrix3d::Identity(), + CalcBodyToBaseCoordinates ( model, + Q, + body_id, + point_position, + false)); + + assert (G.rows() == 3 && G.cols() == model.qdot_size ); + + unsigned int reference_body_id = body_id; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + } + + unsigned int j = reference_body_id; + + // e[j] is set to 1 if joint j contributes to the jacobian that we are + // computing. For all other joints the column will be zero. + while (j != 0) { + unsigned int q_index = model.mJoints[j].q_index; + + if(model.mJoints[j].mJointType != JointTypeCustom){ + if (model.mJoints[j].mDoFCount == 1) { + G.block(0,q_index, 3, 1) = + point_trans.apply( + model.X_base[j].inverse().apply( + model.S[j])).block(3,0,3,1); + } else if (model.mJoints[j].mDoFCount == 3) { + G.block(0, q_index, 3, 3) = + ((point_trans + * model.X_base[j].inverse()).toMatrix() + * model.multdof3_S[j]).block(3,0,3,3); + } + } else { + unsigned int k = model.mJoints[j].custom_joint_index; + + G.block(0, q_index, 3, model.mCustomJoints[k]->mDoFCount) = + ((point_trans + * model.X_base[j].inverse()).toMatrix() + * model.mCustomJoints[k]->S).block( + 3,0,3,model.mCustomJoints[k]->mDoFCount); + } + + j = model.lambda[j]; + } +} + +RBDL_DLLAPI void CalcPointJacobian6D ( + Model &model, + const VectorNd &Q, + unsigned int body_id, + const Vector3d &point_position, + MatrixNd &G, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + SpatialTransform point_trans = + SpatialTransform (Matrix3d::Identity(), + CalcBodyToBaseCoordinates (model, + Q, + body_id, + point_position, + false)); + + assert (G.rows() == 6 && G.cols() == model.qdot_size ); + + unsigned int reference_body_id = body_id; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + } + + unsigned int j = reference_body_id; + + while (j != 0) { + unsigned int q_index = model.mJoints[j].q_index; + + if(model.mJoints[j].mJointType != JointTypeCustom){ + if (model.mJoints[j].mDoFCount == 1) { + G.block(0,q_index, 6, 1) + = point_trans.apply( + model.X_base[j].inverse().apply( + model.S[j])).block(0,0,6,1); + } else if (model.mJoints[j].mDoFCount == 3) { + G.block(0, q_index, 6, 3) + = ((point_trans + * model.X_base[j].inverse()).toMatrix() + * model.multdof3_S[j]).block(0,0,6,3); + } + } else { + unsigned int k = model.mJoints[j].custom_joint_index; + + G.block(0, q_index, 6, model.mCustomJoints[k]->mDoFCount) + = ((point_trans + * model.X_base[j].inverse()).toMatrix() + * model.mCustomJoints[k]->S).block( + 0,0,6,model.mCustomJoints[k]->mDoFCount); + } + + j = model.lambda[j]; + } +} + +RBDL_DLLAPI void CalcBodySpatialJacobian ( + Model &model, + const VectorNd &Q, + unsigned int body_id, + MatrixNd &G, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + // update the Kinematics if necessary + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, NULL, NULL); + } + + assert (G.rows() == 6 && G.cols() == model.qdot_size ); + + unsigned int reference_body_id = body_id; + + SpatialTransform base_to_body; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id + - model.fixed_body_discriminator; + + reference_body_id = model + .mFixedBodies[fbody_id] + .mMovableParent; + + base_to_body = model.mFixedBodies[fbody_id] + .mParentTransform + * model.X_base[reference_body_id]; + } else { + base_to_body = model.X_base[reference_body_id]; + } + + unsigned int j = reference_body_id; + + while (j != 0) { + unsigned int q_index = model.mJoints[j].q_index; + + if(model.mJoints[j].mJointType != JointTypeCustom){ + if (model.mJoints[j].mDoFCount == 1) { + G.block(0,q_index,6,1) = + base_to_body.apply( + model.X_base[j] + .inverse() + .apply(model.S[j]) + ); + } else if (model.mJoints[j].mDoFCount == 3) { + G.block(0,q_index,6,3) = + (base_to_body * model.X_base[j].inverse() + ).toMatrix() * model.multdof3_S[j]; + } + }else if(model.mJoints[j].mJointType == JointTypeCustom) { + unsigned int k = model.mJoints[j].custom_joint_index; + + G.block(0,q_index,6,model.mCustomJoints[k]->mDoFCount ) = + (base_to_body * model.X_base[j].inverse() + ).toMatrix() * model.mCustomJoints[k]->S; + } + + j = model.lambda[j]; + } +} + +RBDL_DLLAPI Vector3d CalcPointVelocity ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + unsigned int body_id, + const Vector3d &point_position, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + assert (model.IsBodyId(body_id)); + assert (model.q_size == Q.size()); + assert (model.qdot_size == QDot.size()); + + // Reset the velocity of the root body + model.v[0].setZero(); + + // update the Kinematics with zero acceleration + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, &QDot, NULL); + } + + unsigned int reference_body_id = body_id; + Vector3d reference_point = point_position; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + Vector3d base_coords = + CalcBodyToBaseCoordinates(model, Q, body_id, point_position, false); + reference_point = + CalcBaseToBodyCoordinates(model, Q, reference_body_id, base_coords,false); + } + + SpatialVector point_spatial_velocity = + SpatialTransform ( + CalcBodyWorldOrientation (model, Q, reference_body_id, false).transpose(), + reference_point).apply(model.v[reference_body_id]); + + return Vector3d ( + point_spatial_velocity[3], + point_spatial_velocity[4], + point_spatial_velocity[5] + ); +} + +RBDL_DLLAPI Math::SpatialVector CalcPointVelocity6D( + Model &model, + const Math::VectorNd &Q, + const Math::VectorNd &QDot, + unsigned int body_id, + const Math::Vector3d &point_position, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + assert (model.IsBodyId(body_id)); + assert (model.q_size == Q.size()); + assert (model.qdot_size == QDot.size()); + + // Reset the velocity of the root body + model.v[0].setZero(); + + // update the Kinematics with zero acceleration + if (update_kinematics) { + UpdateKinematicsCustom (model, &Q, &QDot, NULL); + } + + unsigned int reference_body_id = body_id; + Vector3d reference_point = point_position; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + Vector3d base_coords = + CalcBodyToBaseCoordinates(model, Q, body_id, point_position, false); + reference_point = + CalcBaseToBodyCoordinates(model, Q, reference_body_id, base_coords,false); + } + + return SpatialTransform ( + CalcBodyWorldOrientation (model, Q, reference_body_id, false).transpose(), + reference_point).apply(model.v[reference_body_id]); +} + +RBDL_DLLAPI Vector3d CalcPointAcceleration ( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &QDDot, + unsigned int body_id, + const Vector3d &point_position, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0].setZero(); + + if (update_kinematics) + UpdateKinematics (model, Q, QDot, QDDot); + + LOG << std::endl; + + unsigned int reference_body_id = body_id; + Vector3d reference_point = point_position; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + Vector3d base_coords = + CalcBodyToBaseCoordinates (model, Q, body_id, point_position, false); + reference_point = + CalcBaseToBodyCoordinates (model, Q, reference_body_id,base_coords,false); + } + + SpatialTransform p_X_i ( + CalcBodyWorldOrientation (model, Q, reference_body_id, false).transpose(), + reference_point); + + SpatialVector p_v_i = p_X_i.apply(model.v[reference_body_id]); + Vector3d a_dash = Vector3d (p_v_i[0], p_v_i[1], p_v_i[2] + ).cross(Vector3d (p_v_i[3], p_v_i[4], p_v_i[5])); + SpatialVector p_a_i = p_X_i.apply(model.a[reference_body_id]); + + return Vector3d ( + p_a_i[3] + a_dash[0], + p_a_i[4] + a_dash[1], + p_a_i[5] + a_dash[2] + ); +} + +RBDL_DLLAPI SpatialVector CalcPointAcceleration6D( + Model &model, + const VectorNd &Q, + const VectorNd &QDot, + const VectorNd &QDDot, + unsigned int body_id, + const Vector3d &point_position, + bool update_kinematics) { + LOG << "-------- " << __func__ << " --------" << std::endl; + + // Reset the velocity of the root body + model.v[0].setZero(); + model.a[0].setZero(); + + if (update_kinematics) + UpdateKinematics (model, Q, QDot, QDDot); + + LOG << std::endl; + + unsigned int reference_body_id = body_id; + Vector3d reference_point = point_position; + + if (model.IsFixedBodyId(body_id)) { + unsigned int fbody_id = body_id - model.fixed_body_discriminator; + reference_body_id = model.mFixedBodies[fbody_id].mMovableParent; + Vector3d base_coords = + CalcBodyToBaseCoordinates (model, Q, body_id, point_position, false); + reference_point = + CalcBaseToBodyCoordinates (model, Q, reference_body_id,base_coords,false); + } + + SpatialTransform p_X_i ( + CalcBodyWorldOrientation (model, Q, reference_body_id, false).transpose(), + reference_point); + + SpatialVector p_v_i = p_X_i.apply(model.v[reference_body_id]); + Vector3d a_dash = Vector3d (p_v_i[0], p_v_i[1], p_v_i[2] + ).cross(Vector3d (p_v_i[3], p_v_i[4], p_v_i[5])); + return (p_X_i.apply(model.a[reference_body_id]) + + SpatialVector (0, 0, 0, a_dash[0], a_dash[1], a_dash[2])); +} + +RBDL_DLLAPI bool InverseKinematics ( + Model &model, + const VectorNd &Qinit, + const std::vector& body_id, + const std::vector& body_point, + const std::vector& target_pos, + VectorNd &Qres, + double step_tol, + double lambda, + unsigned int max_iter) { + assert (Qinit.size() == model.q_size); + assert (body_id.size() == body_point.size()); + assert (body_id.size() == target_pos.size()); + + MatrixNd J = MatrixNd::Zero(3 * body_id.size(), model.qdot_size); + VectorNd e = VectorNd::Zero(3 * body_id.size()); + + Qres = Qinit; + + for (unsigned int ik_iter = 0; ik_iter < max_iter; ik_iter++) { + UpdateKinematicsCustom (model, &Qres, NULL, NULL); + + for (unsigned int k = 0; k < body_id.size(); k++) { + MatrixNd G (MatrixNd::Zero(3, model.qdot_size)); + CalcPointJacobian (model, Qres, body_id[k], body_point[k], G, false); + Vector3d point_base = + CalcBodyToBaseCoordinates (model, Qres, body_id[k], body_point[k], false); + LOG << "current_pos = " << point_base.transpose() << std::endl; + + for (unsigned int i = 0; i < 3; i++) { + for (unsigned int j = 0; j < model.qdot_size; j++) { + unsigned int row = k * 3 + i; + LOG << "i = " << i << " j = " << j << " k = " << k << " row = " + << row << " col = " << j << std::endl; + J(row, j) = G (i,j); + } + + e[k * 3 + i] = target_pos[k][i] - point_base[i]; + } + } + + LOG << "J = " << J << std::endl; + LOG << "e = " << e.transpose() << std::endl; + + // abort if we are getting "close" + if (e.norm() < step_tol) { + LOG << "Reached target close enough after " << ik_iter << " steps" << std::endl; + return true; + } + + MatrixNd JJTe_lambda2_I = + J * J.transpose() + + lambda*lambda * MatrixNd::Identity(e.size(), e.size()); + + VectorNd z (body_id.size() * 3); +#ifndef RBDL_USE_SIMPLE_MATH + z = JJTe_lambda2_I.colPivHouseholderQr().solve (e); +#else + bool solve_successful = LinSolveGaussElimPivot (JJTe_lambda2_I, e, z); + assert (solve_successful); +#endif + + LOG << "z = " << z << std::endl; + + VectorNd delta_theta = J.transpose() * z; + LOG << "change = " << delta_theta << std::endl; + + Qres = Qres + delta_theta; + LOG << "Qres = " << Qres.transpose() << std::endl; + + if (delta_theta.norm() < step_tol) { + LOG << "reached convergence after " << ik_iter << " steps" << std::endl; + return true; + } + + VectorNd test_1 (z.size()); + VectorNd test_res (z.size()); + + test_1.setZero(); + + for (unsigned int i = 0; i < z.size(); i++) { + test_1[i] = 1.; + + VectorNd test_delta = J.transpose() * test_1; + + test_res[i] = test_delta.squaredNorm(); + + test_1[i] = 0.; + } + + LOG << "test_res = " << test_res.transpose() << std::endl; + } + + return false; +} + +RBDL_DLLAPI +Vector3d CalcAngularVelocityfromMatrix ( + const Matrix3d &RotMat + ) { + double tol = 1e-12; + + Vector3d l = Vector3d (RotMat(2,1) - RotMat(1,2), RotMat(0,2) - RotMat(2,0), RotMat(1,0) - RotMat(0,1)); + if(l.norm() > tol){ + double preFactor = atan2(l.norm(),(RotMat.trace() - 1.0))/l.norm(); + return preFactor*l; + } + else if((RotMat(0,0)>0 && RotMat(1,1)>0 && RotMat(2,2) > 0) || l.norm() < tol){ + return Vector3dZero; + } + else{ + double PI = atan(1)*4.0; + return Vector3d (PI/2*(RotMat(0,0) + 1.0),PI/2*(RotMat(1,1) + 1.0),PI/2*(RotMat(2,2) + 1.0)); + } +} + +RBDL_DLLAPI +InverseKinematicsConstraintSet::InverseKinematicsConstraintSet() { + lambda = 1e-9; + num_steps = 0; + max_steps = 300; + step_tol = 1e-12; + constraint_tol = 1e-12; + num_constraints = 0; +} + +RBDL_DLLAPI +unsigned int InverseKinematicsConstraintSet::AddPointConstraint( + unsigned int body_id, + const Vector3d& body_point, + const Vector3d& target_pos + ) { + constraint_type.push_back (ConstraintTypePosition); + body_ids.push_back(body_id); + body_points.push_back(body_point); + target_positions.push_back(target_pos); + target_orientations.push_back(Matrix3d::Zero(3,3)); + constraint_row_index.push_back(num_constraints); + num_constraints = num_constraints + 3; + return constraint_type.size() - 1; +} + +RBDL_DLLAPI +unsigned int InverseKinematicsConstraintSet::AddOrientationConstraint( + unsigned int body_id, + const Matrix3d& target_orientation + ) { + constraint_type.push_back (ConstraintTypeOrientation); + body_ids.push_back(body_id); + body_points.push_back(Vector3d::Zero()); + target_positions.push_back(Vector3d::Zero()); + target_orientations.push_back(target_orientation); + constraint_row_index.push_back(num_constraints); + num_constraints = num_constraints + 3; + return constraint_type.size() - 1; +} + +RBDL_DLLAPI +unsigned int InverseKinematicsConstraintSet::AddFullConstraint( + unsigned int body_id, + const Vector3d& body_point, + const Vector3d& target_pos, + const Matrix3d& target_orientation + ) { + constraint_type.push_back (ConstraintTypeFull); + body_ids.push_back(body_id); + body_points.push_back(body_point); + target_positions.push_back(target_pos); + target_orientations.push_back(target_orientation); + constraint_row_index.push_back(num_constraints); + num_constraints = num_constraints + 6; + return constraint_type.size() - 1; +} + +RBDL_DLLAPI +unsigned int InverseKinematicsConstraintSet::ClearConstraints() +{ + for (unsigned int i =0; i < constraint_type.size(); i++){ + constraint_type.pop_back(); + body_ids.pop_back(); + body_points.pop_back(); + target_positions.pop_back(); + target_orientations.pop_back(); + num_constraints = 0; + } + return constraint_type.size(); +} + + +RBDL_DLLAPI +bool InverseKinematics ( + Model &model, + const Math::VectorNd &Qinit, + InverseKinematicsConstraintSet &CS, + Math::VectorNd &Qres + ) { + assert (Qinit.size() == model.q_size); + assert (Qres.size() == Qinit.size()); + + CS.J = MatrixNd::Zero(CS.num_constraints, model.qdot_size); + CS.e = VectorNd::Zero(CS.num_constraints); + + Qres = Qinit; + + for (CS.num_steps = 0; CS.num_steps < CS.max_steps; CS.num_steps++) { + UpdateKinematicsCustom (model, &Qres, NULL, NULL); + + for (unsigned int k = 0; k < CS.body_ids.size(); k++) { + CS.G = MatrixNd::Zero(6, model.qdot_size); + CalcPointJacobian6D (model, Qres, CS.body_ids[k], CS.body_points[k], CS.G, false); + Vector3d point_base = CalcBodyToBaseCoordinates (model, Qres, CS.body_ids[k], CS.body_points[k], false); + Matrix3d R = CalcBodyWorldOrientation(model, Qres, CS.body_ids[k], false); + Vector3d angular_velocity = R.transpose()*CalcAngularVelocityfromMatrix(R*CS.target_orientations[k].transpose()); + + //assign offsets and Jacobians + if (CS.constraint_type[k] == InverseKinematicsConstraintSet::ConstraintTypeFull){ + for (unsigned int i = 0; i < 3; i++){ + unsigned int row = CS.constraint_row_index[k] + i; + CS.e[row + 3] = CS.target_positions[k][i] - point_base[i]; + CS.e[row] = angular_velocity[i]; + for (unsigned int j = 0; j < model.qdot_size; j++) { + CS.J(row + 3, j) = CS.G (i + 3,j); + CS.J(row, j) = CS.G (i,j); + } + } + } + else if (CS.constraint_type[k] == InverseKinematicsConstraintSet::ConstraintTypeOrientation){ + for (unsigned int i = 0; i < 3; i++){ + unsigned int row = CS.constraint_row_index[k] + i; + CS.e[row] = angular_velocity[i]; + for (unsigned int j = 0; j < model.qdot_size; j++) { + CS.J(row, j) = CS.G (i,j); + } + } + } + else if (CS.constraint_type[k] == InverseKinematicsConstraintSet::ConstraintTypePosition){ + for (unsigned int i = 0; i < 3; i++){ + unsigned int row = CS.constraint_row_index[k] + i; + CS.e[row] = CS.target_positions[k][i] - point_base[i]; + for (unsigned int j = 0; j < model.qdot_size; j++) { + CS.J(row, j) = CS.G (i + 3,j); + } + } + } + else { + assert (false && !"Invalid inverse kinematics constraint"); + } + } + + LOG << "J = " << CS.J << std::endl; + LOG << "e = " << CS.e.transpose() << std::endl; + CS.error_norm = CS.e.norm(); + + // abort if we are getting "close" + if (CS.error_norm < CS.step_tol) { + LOG << "Reached target close enough after " << CS.num_steps << " steps" << std::endl; + return true; + } + + // // "task space" from puppeteer + // MatrixNd Ek = MatrixNd::Zero (CS.e.size(), CS.e.size()); + // + // for (size_t ei = 0; ei < CS.e.size(); ei ++) { + // // Ek(ei,ei) = CS.error_norm * CS.error_norm * 0.5 + CS.lambda; + // Ek(ei,ei) = CS.e[ei]*CS.e[ei] * 0.5 + CS.lambda; + // } + // + // MatrixNd JJT_Ek_wnI = CS.J * CS.J.transpose() + Ek; + // + // VectorNd delta_theta = CS.J.transpose() * JJT_Ek_wnI.colPivHouseholderQr().solve (CS.e); + // + // LOG << "change = " << delta_theta << std::endl; + + + // "joint space" from puppeteer + + double Ek = 0.; + + for (size_t ei = 0; ei < CS.e.size(); ei ++) { + Ek += CS.e[ei] * CS.e[ei] * 0.5; + } + + VectorNd ek = CS.J.transpose() * CS.e; + MatrixNd Wn = MatrixNd::Zero (Qres.size(), Qres.size()); + + assert (ek.size() == Qres.size()); + + for (size_t wi = 0; wi < Qres.size(); wi++) { + Wn(wi, wi) = ek[wi] * ek[wi] * 0.5 + CS.lambda; + // Wn(wi, wi) = Ek + 1.0e-3; + } + + MatrixNd A = CS.J.transpose() * CS.J + Wn; + VectorNd delta_theta = A.colPivHouseholderQr().solve(CS.J.transpose() * CS.e); + + Qres = Qres + delta_theta; + if (delta_theta.norm() < CS.step_tol) { + LOG << "reached convergence after " << CS.num_steps << " steps" << std::endl; + return true; + } + } + + return false; +} + +} diff --git a/3rdparty/rbdl/src/Logging.cc b/3rdparty/rbdl/src/Logging.cc new file mode 100644 index 0000000..9825a64 --- /dev/null +++ b/3rdparty/rbdl/src/Logging.cc @@ -0,0 +1,14 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include "rbdl/Logging.h" + +RBDL_DLLAPI std::ostringstream LogOutput; + +RBDL_DLLAPI void ClearLogOutput() { + LogOutput.str(""); +} diff --git a/3rdparty/rbdl/src/Model.cc b/3rdparty/rbdl/src/Model.cc new file mode 100644 index 0000000..7344ede --- /dev/null +++ b/3rdparty/rbdl/src/Model.cc @@ -0,0 +1,494 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include +#include + +#include "rbdl/rbdl_mathutils.h" + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Body.h" +#include "rbdl/Joint.h" + +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +Model::Model() { + Body root_body; + Joint root_joint; + + Vector3d zero_position (0., 0., 0.); + SpatialVector zero_spatial (0., 0., 0., 0., 0., 0.); + + // structural information + lambda.push_back(0); + lambda_q.push_back(0); + mu.push_back(std::vector()); + dof_count = 0; + q_size = 0; + qdot_size = 0; + previously_added_body_id = 0; + + gravity = Vector3d (0., -9.81, 0.); + + // state information + v.push_back(zero_spatial); + a.push_back(zero_spatial); + + // Joints + mJoints.push_back(root_joint); + S.push_back (zero_spatial); + X_T.push_back(SpatialTransform()); + + X_J.push_back (SpatialTransform()); + v_J.push_back (zero_spatial); + c_J.push_back (zero_spatial); + + // Spherical joints + multdof3_S.push_back (Matrix63::Zero()); + multdof3_U.push_back (Matrix63::Zero()); + multdof3_Dinv.push_back (Matrix3d::Zero()); + multdof3_u.push_back (Vector3d::Zero()); + multdof3_w_index.push_back (0); + + // Dynamic variables + c.push_back(zero_spatial); + IA.push_back(SpatialMatrix::Identity()); + pA.push_back(zero_spatial); + U.push_back(zero_spatial); + + u = VectorNd::Zero(1); + d = VectorNd::Zero(1); + + f.push_back (zero_spatial); + SpatialRigidBodyInertia rbi(0., + Vector3d (0., 0., 0.), + Matrix3d::Zero(3,3)); + Ic.push_back (rbi); + I.push_back(rbi); + hc.push_back (zero_spatial); + + // Bodies + X_lambda.push_back(SpatialTransform()); + X_base.push_back(SpatialTransform()); + + mBodies.push_back(root_body); + mBodyNameMap["ROOT"] = 0; + + fixed_body_discriminator = std::numeric_limits::max() / 2; +} + +unsigned int AddBodyFixedJoint ( + Model &model, + const unsigned int parent_id, + const SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name) { + FixedBody fbody = FixedBody::CreateFromBody (body); + fbody.mMovableParent = parent_id; + fbody.mParentTransform = joint_frame; + + if (model.IsFixedBodyId(parent_id)) { + FixedBody fixed_parent = + model.mFixedBodies[parent_id - model.fixed_body_discriminator]; + + fbody.mMovableParent = fixed_parent.mMovableParent; + fbody.mParentTransform = joint_frame * fixed_parent.mParentTransform; + } + + // merge the two bodies + Body parent_body = model.mBodies[fbody.mMovableParent]; + parent_body.Join (fbody.mParentTransform, body); + model.mBodies[fbody.mMovableParent] = parent_body; + model.I[fbody.mMovableParent] = + SpatialRigidBodyInertia::createFromMassComInertiaC ( + parent_body.mMass, + parent_body.mCenterOfMass, + parent_body.mInertia); + + model.mFixedBodies.push_back (fbody); + + if (model.mFixedBodies.size() > + std::numeric_limits::max() - model.fixed_body_discriminator) { + std::cerr << "Error: cannot add more than " + << std::numeric_limits::max() + - model.mFixedBodies.size() + << " fixed bodies. You need to modify " + << "Model::fixed_body_discriminator for this." + << std::endl; + assert (0); + abort(); + } + + if (body_name.size() != 0) { + if (model.mBodyNameMap.find(body_name) != model.mBodyNameMap.end()) { + std::cerr << "Error: Body with name '" + << body_name + << "' already exists!" + << std::endl; + assert (0); + abort(); + } + model.mBodyNameMap[body_name] = model.mFixedBodies.size() + + model.fixed_body_discriminator - 1; + } + + return model.mFixedBodies.size() + model.fixed_body_discriminator - 1; +} + +unsigned int AddBodyMultiDofJoint ( + Model &model, + const unsigned int parent_id, + const SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name) { + // Here we emulate multi DoF joints by simply adding nullbodies. This + // allows us to use fixed size elements for S,v,a, etc. which is very + // fast in Eigen. + unsigned int joint_count = 0; + if (joint.mJointType == JointType1DoF) + joint_count = 1; + else if (joint.mJointType == JointType2DoF) + joint_count = 2; + else if (joint.mJointType == JointType3DoF) + joint_count = 3; + else if (joint.mJointType == JointType4DoF) + joint_count = 4; + else if (joint.mJointType == JointType5DoF) + joint_count = 5; + else if (joint.mJointType == JointType6DoF) + joint_count = 6; + else if (joint.mJointType == JointTypeFloatingBase) + // no action required + {} + else { + std::cerr << "Error: Invalid joint type: " + << joint.mJointType + << std::endl; + + assert (0 && !"Invalid joint type!"); + } + + Body null_body (0., Vector3d (0., 0., 0.), Vector3d (0., 0., 0.)); + null_body.mIsVirtual = true; + + unsigned int null_parent = parent_id; + SpatialTransform joint_frame_transform; + + if (joint.mJointType == JointTypeFloatingBase) { + null_parent = model.AddBody (parent_id, + joint_frame, + JointTypeTranslationXYZ, + null_body); + + return model.AddBody (null_parent, + SpatialTransform(), + JointTypeSpherical, + body, + body_name); + } + + Joint single_dof_joint; + unsigned int j; + + // Here we add multiple virtual bodies that have no mass or inertia for + // which each is attached to the model with a single degree of freedom + // joint. + for (j = 0; j < joint_count; j++) { + single_dof_joint = Joint (joint.mJointAxes[j]); + + if (single_dof_joint.mJointType == JointType1DoF) { + Vector3d rotation ( + joint.mJointAxes[j][0], + joint.mJointAxes[j][1], + joint.mJointAxes[j][2]); + Vector3d translation ( + joint.mJointAxes[j][3], + joint.mJointAxes[j][4], + joint.mJointAxes[j][5]); + + if (rotation == Vector3d (0., 0., 0.)) { + single_dof_joint = Joint (JointTypePrismatic, translation); + } else if (translation == Vector3d (0., 0., 0.)) { + single_dof_joint = Joint (JointTypeRevolute, rotation); + } + } + + // the first joint has to be transformed by joint_frame, all the + // others must have a null transformation + if (j == 0) + joint_frame_transform = joint_frame; + else + joint_frame_transform = SpatialTransform(); + + if (j == joint_count - 1) + // if we are at the last we must add the real body + break; + else { + // otherwise we just add an intermediate body + null_parent = model.AddBody (null_parent, + joint_frame_transform, + single_dof_joint, + null_body); + } + } + + return model.AddBody (null_parent, + joint_frame_transform, + single_dof_joint, + body, + body_name); +} + +unsigned int Model::AddBody( + const unsigned int parent_id, + const SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name) { + assert (lambda.size() > 0); + assert (joint.mJointType != JointTypeUndefined); + + if (joint.mJointType == JointTypeFixed) { + previously_added_body_id = AddBodyFixedJoint (*this, + parent_id, + joint_frame, + joint, + body, + body_name); + + return previously_added_body_id; + } else if ( (joint.mJointType == JointTypeSpherical) + || (joint.mJointType == JointTypeEulerZYX) + || (joint.mJointType == JointTypeEulerXYZ) + || (joint.mJointType == JointTypeEulerYXZ) + || (joint.mJointType == JointTypeTranslationXYZ) + || (joint.mJointType == JointTypeCustom) + ) { + // no action required + } else if (joint.mJointType != JointTypePrismatic + && joint.mJointType != JointTypeRevolute + && joint.mJointType != JointTypeRevoluteX + && joint.mJointType != JointTypeRevoluteY + && joint.mJointType != JointTypeRevoluteZ + && joint.mJointType != JointTypeHelical + ) { + previously_added_body_id = AddBodyMultiDofJoint (*this, + parent_id, + joint_frame, + joint, + body, + body_name); + return previously_added_body_id; + } + + // If we add the body to a fixed body we have to make sure that we + // actually add it to its movable parent. + unsigned int movable_parent_id = parent_id; + SpatialTransform movable_parent_transform; + + if (IsFixedBodyId(parent_id)) { + unsigned int fbody_id = parent_id - fixed_body_discriminator; + movable_parent_id = mFixedBodies[fbody_id].mMovableParent; + movable_parent_transform = mFixedBodies[fbody_id].mParentTransform; + } + + // structural information + lambda.push_back(movable_parent_id); + unsigned int lambda_q_last = mJoints[mJoints.size() - 1].q_index; + + if (mJoints[mJoints.size() - 1].mDoFCount > 0 + && mJoints[mJoints.size() - 1].mJointType != JointTypeCustom) { + lambda_q_last = lambda_q_last + mJoints[mJoints.size() - 1].mDoFCount; + } else if (mJoints[mJoints.size() - 1].mJointType == JointTypeCustom) { + unsigned int custom_index = mJoints[mJoints.size() - 1].custom_joint_index; + lambda_q_last = lambda_q_last + + mCustomJoints[mCustomJoints.size() - 1]->mDoFCount; + } + + for (unsigned int i = 0; i < joint.mDoFCount; i++) { + lambda_q.push_back(lambda_q_last + i); + } + mu.push_back(std::vector()); + mu.at(movable_parent_id).push_back(mBodies.size()); + + // Bodies + X_lambda.push_back(SpatialTransform()); + X_base.push_back(SpatialTransform()); + mBodies.push_back(body); + + if (body_name.size() != 0) { + if (mBodyNameMap.find(body_name) != mBodyNameMap.end()) { + std::cerr << "Error: Body with name '" + << body_name + << "' already exists!" + << std::endl; + assert (0); + abort(); + } + mBodyNameMap[body_name] = mBodies.size() - 1; + } + + // state information + v.push_back(SpatialVector(0., 0., 0., 0., 0., 0.)); + a.push_back(SpatialVector(0., 0., 0., 0., 0., 0.)); + + // Joints + unsigned int prev_joint_index = mJoints.size() - 1; + mJoints.push_back(joint); + + if (mJoints[prev_joint_index].mJointType != JointTypeCustom) { + mJoints[mJoints.size() - 1].q_index = + mJoints[prev_joint_index].q_index + mJoints[prev_joint_index].mDoFCount; + } else { + mJoints[mJoints.size() - 1].q_index = + mJoints[prev_joint_index].q_index + mJoints[prev_joint_index].mDoFCount; + } + + S.push_back (joint.mJointAxes[0]); + + // Joint state variables + X_J.push_back (SpatialTransform()); + v_J.push_back (joint.mJointAxes[0]); + c_J.push_back (SpatialVector(0., 0., 0., 0., 0., 0.)); + + // workspace for joints with 3 dof + multdof3_S.push_back (Matrix63::Zero(6,3)); + multdof3_U.push_back (Matrix63::Zero()); + multdof3_Dinv.push_back (Matrix3d::Zero()); + multdof3_u.push_back (Vector3d::Zero()); + multdof3_w_index.push_back (0); + + dof_count = dof_count + joint.mDoFCount; + + // update the w components of the Quaternions. They are stored at the end + // of the q vector + int multdof3_joint_counter = 0; + int mCustomJoint_joint_counter = 0; + for (unsigned int i = 1; i < mJoints.size(); i++) { + if (mJoints[i].mJointType == JointTypeSpherical) { + multdof3_w_index[i] = dof_count + multdof3_joint_counter; + multdof3_joint_counter++; + } + } + + q_size = dof_count + + multdof3_joint_counter; + + qdot_size = qdot_size + joint.mDoFCount; + + // we have to invert the transformation as it is later always used from the + // child bodies perspective. + X_T.push_back(joint_frame * movable_parent_transform); + + // Dynamic variables + c.push_back(SpatialVector(0., 0., 0., 0., 0., 0.)); + IA.push_back(SpatialMatrix::Zero(6,6)); + pA.push_back(SpatialVector(0., 0., 0., 0., 0., 0.)); + U.push_back(SpatialVector(0., 0., 0., 0., 0., 0.)); + + d = VectorNd::Zero (mBodies.size()); + u = VectorNd::Zero (mBodies.size()); + + f.push_back (SpatialVector (0., 0., 0., 0., 0., 0.)); + + SpatialRigidBodyInertia rbi = + SpatialRigidBodyInertia::createFromMassComInertiaC (body.mMass, + body.mCenterOfMass, + body.mInertia); + + Ic.push_back (rbi); + I.push_back (rbi); + hc.push_back (SpatialVector(0., 0., 0., 0., 0., 0.)); + + if (mBodies.size() == fixed_body_discriminator) { + std::cerr << "Error: cannot add more than " + << fixed_body_discriminator + << " movable bodies. You need to modify " + "Model::fixed_body_discriminator for this." + << std::endl; + assert (0); + abort(); + } + + previously_added_body_id = mBodies.size() - 1; + + mJointUpdateOrder.clear(); + + // update the joint order computation + std::vector > joint_types; + for (unsigned int i = 0; i < mJoints.size(); i++) { + joint_types.push_back( + std::pair (mJoints[i].mJointType,i)); + mJointUpdateOrder.push_back (mJoints[i].mJointType); + } + + mJointUpdateOrder.clear(); + JointType current_joint_type = JointTypeUndefined; + while (joint_types.size() != 0) { + current_joint_type = joint_types[0].first; + + std::vector >::iterator type_iter = + joint_types.begin(); + + while (type_iter != joint_types.end()) { + if (type_iter->first == current_joint_type) { + mJointUpdateOrder.push_back (type_iter->second); + type_iter = joint_types.erase (type_iter); + } else { + ++type_iter; + } + } + } + + // for (unsigned int i = 0; i < mJointUpdateOrder.size(); i++) { + // std::cout << "i = " << i << ": joint_id = " << mJointUpdateOrder[i] + // << " joint_type = " << mJoints[mJointUpdateOrder[i]].mJointType << std::endl; + // } + + return previously_added_body_id; +} + +unsigned int Model::AppendBody ( + const Math::SpatialTransform &joint_frame, + const Joint &joint, + const Body &body, + std::string body_name) { + return Model::AddBody (previously_added_body_id, + joint_frame, + joint, + body, + body_name); +} + +unsigned int Model::AddBodyCustomJoint ( + const unsigned int parent_id, + const Math::SpatialTransform &joint_frame, + CustomJoint *custom_joint, + const Body &body, + std::string body_name) { + Joint proxy_joint (JointTypeCustom, custom_joint->mDoFCount); + proxy_joint.custom_joint_index = mCustomJoints.size(); + //proxy_joint.mDoFCount = custom_joint->mDoFCount; //MM added. Otherwise + //model.q_size = 0, which is not good. + + mCustomJoints.push_back (custom_joint); + + unsigned int body_id = AddBody (parent_id, + joint_frame, + proxy_joint, + body, + body_name); + + return body_id; +} + diff --git a/3rdparty/rbdl/src/rbdl_mathutils.cc b/3rdparty/rbdl/src/rbdl_mathutils.cc new file mode 100644 index 0000000..a865b60 --- /dev/null +++ b/3rdparty/rbdl/src/rbdl_mathutils.cc @@ -0,0 +1,319 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include +#include + +#include +#include + +#include +#include + +#include "rbdl/Logging.h" + +namespace RigidBodyDynamics { +namespace Math { + +RBDL_DLLAPI Vector3d Vector3dZero (0., 0., 0.); +RBDL_DLLAPI Matrix3d Matrix3dIdentity ( + 1., 0., 0., + 0., 1., 0., + 0., 0., 1 + ); +RBDL_DLLAPI Matrix3d Matrix3dZero ( + 0., 0., 0., + 0., 0., 0., + 0., 0., 0. + ); + +RBDL_DLLAPI SpatialVector SpatialVectorZero ( 0., 0., 0., 0., 0., 0.); + +RBDL_DLLAPI bool LinSolveGaussElimPivot (MatrixNd A, VectorNd b, VectorNd &x) { + x = VectorNd::Zero(x.size()); + + // We can only solve quadratic systems + assert (A.rows() == A.cols()); + + unsigned int n = A.rows(); + unsigned int pi; + + // the pivots + size_t *pivot = new size_t[n]; + + // temporary result vector which contains the pivoted result + VectorNd px(x); + + unsigned int i,j,k; + + for (i = 0; i < n; i++) + pivot[i] = i; + + for (j = 0; j < n; j++) { + pi = j; + double pv = fabs (A(j,pivot[j])); + + // LOG << "j = " << j << " pv = " << pv << std::endl; + // find the pivot + for (k = j; k < n; k++) { + double pt = fabs (A(j,pivot[k])); + if (pt > pv) { + pv = pt; + pi = k; + unsigned int p_swap = pivot[j]; + pivot[j] = pivot[pi]; + pivot[pi] = p_swap; + // LOG << "swap " << j << " with " << pi << std::endl; + // LOG << "j = " << j << " pv = " << pv << std::endl; + } + } + + for (i = j + 1; i < n; i++) { + if (fabs(A(j,pivot[j])) <= std::numeric_limits::epsilon()) { + std::cerr << "Error: pivoting failed for matrix A = " << std::endl; + std::cerr << "A = " << std::endl << A << std::endl; + std::cerr << "b = " << b << std::endl; + } + // assert (fabs(A(j,pivot[j])) > std::numeric_limits::epsilon()); + double d = A(i,pivot[j])/A(j,pivot[j]); + + b[i] -= b[j] * d; + + for (k = j; k < n; k++) { + A(i,pivot[k]) -= A(j,pivot[k]) * d; + } + } + } + + // warning: i is an unsigned int, therefore a for loop of the + // form "for (i = n - 1; i >= 0; i--)" might end up in getting an invalid + // value for i! + i = n; + do { + i--; + + for (j = i + 1; j < n; j++) { + px[i] += A(i,pivot[j]) * px[j]; + } + px[i] = (b[i] - px[i]) / A(i,pivot[i]); + + } while (i > 0); + + // Unswapping + for (i = 0; i < n; i++) { + x[pivot[i]] = px[i]; + } + + /* + LOG << "A = " << std::endl << A << std::endl; + LOG << "b = " << b << std::endl; + LOG << "x = " << x << std::endl; + LOG << "pivot = " << pivot[0] << " " << pivot[1] << " " << pivot[2] << std::endl; + std::cout << LogOutput.str() << std::endl; + */ + + delete[] pivot; + + return true; +} + +RBDL_DLLAPI void SpatialMatrixSetSubmatrix( + SpatialMatrix &dest, + unsigned int row, + unsigned int col, + const Matrix3d &matrix) { + assert (row < 2 && col < 2); + + dest(row*3,col*3) = matrix(0,0); + dest(row*3,col*3 + 1) = matrix(0,1); + dest(row*3,col*3 + 2) = matrix(0,2); + + dest(row*3 + 1,col*3) = matrix(1,0); + dest(row*3 + 1,col*3 + 1) = matrix(1,1); + dest(row*3 + 1,col*3 + 2) = matrix(1,2); + + dest(row*3 + 2,col*3) = matrix(2,0); + dest(row*3 + 2,col*3 + 1) = matrix(2,1); + dest(row*3 + 2,col*3 + 2) = matrix(2,2); +} + +RBDL_DLLAPI bool SpatialMatrixCompareEpsilon ( + const SpatialMatrix &matrix_a, + const SpatialMatrix &matrix_b, + double epsilon) { + assert (epsilon >= 0.); + unsigned int i, j; + + for (i = 0; i < 6; i++) { + for (j = 0; j < 6; j++) { + if (fabs(matrix_a(i,j) - matrix_b(i,j)) >= epsilon) { + std::cerr << "Expected:" + << std::endl << matrix_a << std::endl + << "but was" << std::endl + << matrix_b << std::endl; + return false; + } + } + } + + return true; +} + +RBDL_DLLAPI bool SpatialVectorCompareEpsilon ( + const SpatialVector &vector_a, + const SpatialVector &vector_b, + double epsilon) { + assert (epsilon >= 0.); + unsigned int i; + + for (i = 0; i < 6; i++) { + if (fabs(vector_a[i] - vector_b[i]) >= epsilon) { + std::cerr << "Expected:" + << std::endl << vector_a << std::endl + << "but was" << std::endl + << vector_b << std::endl; + return false; + } + } + + return true; +} + +RBDL_DLLAPI Matrix3d parallel_axis ( + const Matrix3d &inertia, + double mass, + const Vector3d &com) { + Matrix3d com_cross = VectorCrossMatrix (com); + + return inertia + mass * com_cross * com_cross.transpose(); +} + +RBDL_DLLAPI SpatialMatrix Xtrans_mat (const Vector3d &r) { + return SpatialMatrix ( + 1., 0., 0., 0., 0., 0., + 0., 1., 0., 0., 0., 0., + 0., 0., 1., 0., 0., 0., + 0., r[2], -r[1], 1., 0., 0., + -r[2], 0., r[0], 0., 1., 0., + r[1], -r[0], 0., 0., 0., 1. + ); +} + +RBDL_DLLAPI SpatialMatrix Xrotx_mat (const double &xrot) { + double s, c; + s = sin (xrot); + c = cos (xrot); + + return SpatialMatrix( + 1., 0., 0., 0., 0., 0., + 0., c, s, 0., 0., 0., + 0., -s, c, 0., 0., 0., + 0., 0., 0., 1., 0., 0., + 0., 0., 0., 0., c, s, + 0., 0., 0., 0., -s, c + ); +} + +RBDL_DLLAPI SpatialMatrix Xroty_mat (const double &yrot) { + double s, c; + s = sin (yrot); + c = cos (yrot); + + return SpatialMatrix( + c, 0., -s, 0., 0., 0., + 0., 1., 0., 0., 0., 0., + s, 0., c, 0., 0., 0., + 0., 0., 0., c, 0., -s, + 0., 0., 0., 0., 1., 0., + 0., 0., 0., s, 0., c + ); +} + +RBDL_DLLAPI SpatialMatrix Xrotz_mat (const double &zrot) { + double s, c; + s = sin (zrot); + c = cos (zrot); + + return SpatialMatrix( + c, s, 0., 0., 0., 0., + -s, c, 0., 0., 0., 0., + 0., 0., 1., 0., 0., 0., + 0., 0., 0., c, s, 0., + 0., 0., 0., -s, c, 0., + 0., 0., 0., 0., 0., 1. + ); +} + +RBDL_DLLAPI SpatialMatrix XtransRotZYXEuler ( + const Vector3d &displacement, + const Vector3d &zyx_euler) { + return Xrotz_mat(zyx_euler[0]) * Xroty_mat(zyx_euler[1]) * Xrotx_mat(zyx_euler[2]) * Xtrans_mat(displacement); +} + +RBDL_DLLAPI void SparseFactorizeLTL (Model &model, Math::MatrixNd &H) { + for (unsigned int i = 0; i < model.qdot_size; i++) { + for (unsigned int j = i + 1; j < model.qdot_size; j++) { + H(i,j) = 0.; + } + } + + for (unsigned int k = model.qdot_size; k > 0; k--) { + H(k - 1,k - 1) = sqrt (H(k - 1,k - 1)); + unsigned int i = model.lambda_q[k]; + while (i != 0) { + H(k - 1,i - 1) = H(k - 1,i - 1) / H(k - 1,k - 1); + i = model.lambda_q[i]; + } + + i = model.lambda_q[k]; + while (i != 0) { + unsigned int j = i; + while (j != 0) { + H(i - 1,j - 1) = H(i - 1,j - 1) - H(k - 1,i - 1) * H(k - 1, j - 1); + j = model.lambda_q[j]; + } + i = model.lambda_q[i]; + } + } +} + +RBDL_DLLAPI void SparseMultiplyHx (Model &model, Math::MatrixNd &L) { + assert (0 && !"Not yet implemented!"); +} + +RBDL_DLLAPI void SparseMultiplyLx (Model &model, Math::MatrixNd &L) { + assert (0 && !"Not yet implemented!"); +} + +RBDL_DLLAPI void SparseMultiplyLTx (Model &model, Math::MatrixNd &L) { + assert (0 && !"Not yet implemented!"); +} + +RBDL_DLLAPI void SparseSolveLx (Model &model, Math::MatrixNd &L, Math::VectorNd &x) { + for (unsigned int i = 1; i <= model.qdot_size; i++) { + unsigned int j = model.lambda_q[i]; + while (j != 0) { + x[i - 1] = x[i - 1] - L(i - 1,j - 1) * x[j - 1]; + j = model.lambda_q[j]; + } + x[i - 1] = x[i - 1] / L(i - 1,i - 1); + } +} + +RBDL_DLLAPI void SparseSolveLTx (Model &model, Math::MatrixNd &L, Math::VectorNd &x) { + for (int i = model.qdot_size; i > 0; i--) { + x[i - 1] = x[i - 1] / L(i - 1,i - 1); + unsigned int j = model.lambda_q[i]; + while (j != 0) { + x[j - 1] = x[j - 1] - L(i - 1,j - 1) * x[i - 1]; + j = model.lambda_q[j]; + } + } +} + +} /* Math */ +} /* RigidBodyDynamics */ diff --git a/3rdparty/rbdl/src/rbdl_utils.cc b/3rdparty/rbdl/src/rbdl_utils.cc new file mode 100644 index 0000000..db219cc --- /dev/null +++ b/3rdparty/rbdl/src/rbdl_utils.cc @@ -0,0 +1,229 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include "rbdl/rbdl_utils.h" + +#include "rbdl/rbdl_math.h" +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" + +#include +#include + +namespace RigidBodyDynamics { + +namespace Utils { + +using namespace std; +using namespace Math; + +string get_dof_name (const SpatialVector &joint_dof) { + if (joint_dof == SpatialVector (1., 0., 0., 0., 0., 0.)) + return "RX"; + else if (joint_dof == SpatialVector (0., 1., 0., 0., 0., 0.)) + return "RY"; + else if (joint_dof == SpatialVector (0., 0., 1., 0., 0., 0.)) + return "RZ"; + else if (joint_dof == SpatialVector (0., 0., 0., 1., 0., 0.)) + return "TX"; + else if (joint_dof == SpatialVector (0., 0., 0., 0., 1., 0.)) + return "TY"; + else if (joint_dof == SpatialVector (0., 0., 0., 0., 0., 1.)) + return "TZ"; + + ostringstream dof_stream(ostringstream::out); + dof_stream << "custom (" << joint_dof.transpose() << ")"; + return dof_stream.str(); +} + +string get_body_name (const RigidBodyDynamics::Model &model, unsigned int body_id) { + if (model.mBodies[body_id].mIsVirtual) { + // if there is not a unique child we do not know what to do... + if (model.mu[body_id].size() != 1) + return ""; + + return get_body_name (model, model.mu[body_id][0]); + } + + return model.GetBodyName(body_id); +} + +RBDL_DLLAPI std::string GetModelDOFOverview (const Model &model) { + stringstream result (""); + + unsigned int q_index = 0; + for (unsigned int i = 1; i < model.mBodies.size(); i++) { + if (model.mJoints[i].mDoFCount == 1) { + result << setfill(' ') << setw(3) << q_index << ": " << get_body_name(model, i) << "_" << get_dof_name (model.S[i]) << endl; + q_index++; + } else { + for (unsigned int j = 0; j < model.mJoints[i].mDoFCount; j++) { + result << setfill(' ') << setw(3) << q_index << ": " << get_body_name(model, i) << "_" << get_dof_name (model.mJoints[i].mJointAxes[j]) << endl; + q_index++; + } + } + } + + return result.str(); +} + +std::string print_hierarchy (const RigidBodyDynamics::Model &model, unsigned int body_index = 0, int indent = 0) { + stringstream result (""); + + for (int j = 0; j < indent; j++) + result << " "; + + result << get_body_name (model, body_index); + + if (body_index > 0) + result << " [ "; + + while (model.mBodies[body_index].mIsVirtual) { + if (model.mu[body_index].size() == 0) { + result << " end"; + break; + } else if (model.mu[body_index].size() > 1) { + cerr << endl << "Error: Cannot determine multi-dof joint as massless body with id " << body_index << " (name: " << model.GetBodyName(body_index) << ") has more than one child:" << endl; + for (unsigned int ci = 0; ci < model.mu[body_index].size(); ci++) { + cerr << " id: " << model.mu[body_index][ci] << " name: " << model.GetBodyName(model.mu[body_index][ci]) << endl; + } + abort(); + } + + result << get_dof_name(model.S[body_index]) << ", "; + + body_index = model.mu[body_index][0]; + } + + if (body_index > 0) + result << get_dof_name(model.S[body_index]) << " ]"; + result << endl; + + unsigned int child_index = 0; + for (child_index = 0; child_index < model.mu[body_index].size(); child_index++) { + result << print_hierarchy (model, model.mu[body_index][child_index], indent + 1); + } + + // print fixed children + for (unsigned int fbody_index = 0; fbody_index < model.mFixedBodies.size(); fbody_index++) { + if (model.mFixedBodies[fbody_index].mMovableParent == body_index) { + for (int j = 0; j < indent + 1; j++) + result << " "; + + result << model.GetBodyName(model.fixed_body_discriminator + fbody_index) << " [fixed]" << endl; + } + } + + + return result.str(); +} + +RBDL_DLLAPI std::string GetModelHierarchy (const Model &model) { + stringstream result (""); + + result << print_hierarchy (model); + + return result.str(); +} + +RBDL_DLLAPI std::string GetNamedBodyOriginsOverview (Model &model) { + stringstream result (""); + + VectorNd Q (VectorNd::Zero(model.dof_count)); + UpdateKinematicsCustom (model, &Q, NULL, NULL); + + for (unsigned int body_id = 0; body_id < model.mBodies.size(); body_id++) { + std::string body_name = model.GetBodyName (body_id); + + if (body_name.size() == 0) + continue; + + Vector3d position = CalcBodyToBaseCoordinates (model, Q, body_id, Vector3d (0., 0., 0.), false); + + result << body_name << ": " << position.transpose() << endl; + } + + return result.str(); +} + +RBDL_DLLAPI void CalcCenterOfMass ( + Model &model, + const Math::VectorNd &q, + const Math::VectorNd &qdot, + double &mass, + Math::Vector3d &com, + Math::Vector3d *com_velocity, + Vector3d *angular_momentum, + bool update_kinematics) { + if (update_kinematics) + UpdateKinematicsCustom (model, &q, &qdot, NULL); + + for (size_t i = 1; i < model.mBodies.size(); i++) { + model.Ic[i] = model.I[i]; + model.hc[i] = model.Ic[i].toMatrix() * model.v[i]; + } + + SpatialRigidBodyInertia Itot (0., Vector3d (0., 0., 0.), Matrix3d::Zero(3,3)); + SpatialVector htot (SpatialVector::Zero(6)); + + for (size_t i = model.mBodies.size() - 1; i > 0; i--) { + unsigned int lambda = model.lambda[i]; + + if (lambda != 0) { + model.Ic[lambda] = model.Ic[lambda] + model.X_lambda[i].applyTranspose (model.Ic[i]); + model.hc[lambda] = model.hc[lambda] + model.X_lambda[i].applyTranspose (model.hc[i]); + } else { + Itot = Itot + model.X_lambda[i].applyTranspose (model.Ic[i]); + htot = htot + model.X_lambda[i].applyTranspose (model.hc[i]); + } + } + + mass = Itot.m; + com = Itot.h / mass; + LOG << "mass = " << mass << " com = " << com.transpose() << " htot = " << htot.transpose() << std::endl; + + if (com_velocity) + *com_velocity = Vector3d (htot[3] / mass, htot[4] / mass, htot[5] / mass); + + if (angular_momentum) { + htot = Xtrans (com).applyAdjoint (htot); + angular_momentum->set (htot[0], htot[1], htot[2]); + } +} + +RBDL_DLLAPI double CalcPotentialEnergy ( + Model &model, + const Math::VectorNd &q, + bool update_kinematics) { + double mass; + Vector3d com; + CalcCenterOfMass (model, q, VectorNd::Zero (model.qdot_size), mass, com, NULL, NULL, update_kinematics); + + Vector3d g = - Vector3d (model.gravity[0], model.gravity[1], model.gravity[2]); + LOG << "pot_energy: " << " mass = " << mass << " com = " << com.transpose() << std::endl; + + return mass * com.dot(g); +} + +RBDL_DLLAPI double CalcKineticEnergy ( + Model &model, + const Math::VectorNd &q, + const Math::VectorNd &qdot, + bool update_kinematics) { + if (update_kinematics) + UpdateKinematicsCustom (model, &q, &qdot, NULL); + + double result = 0.; + + for (size_t i = 1; i < model.mBodies.size(); i++) { + result += 0.5 * model.v[i].transpose() * (model.I[i] * model.v[i]); + } + return result; +} + +} +} diff --git a/3rdparty/rbdl/src/rbdl_version.cc b/3rdparty/rbdl/src/rbdl_version.cc new file mode 100644 index 0000000..c7ccd12 --- /dev/null +++ b/3rdparty/rbdl/src/rbdl_version.cc @@ -0,0 +1,91 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * + * Licensed under the zlib license. See LICENSE for more details. + */ + +#include + +#include +#include +#include +#include + +RBDL_DLLAPI int rbdl_get_api_version() { + static int compile_version = RBDL_API_VERSION; + return compile_version; +} + +RBDL_DLLAPI void rbdl_check_api_version(int version) { + int compile_version = rbdl_get_api_version(); + + int compile_major = (compile_version & 0xff0000) >> 16; + int compile_minor = (compile_version & 0x00ff00) >> 8; + int compile_patch = (compile_version & 0x0000ff); + + std::ostringstream compile_version_string(""); + compile_version_string << compile_major << "." << compile_minor << "." << compile_patch; + + int version_major = (version & 0xff0000) >> 16; + int version_minor = (version & 0x00ff00) >> 8; + int version_patch = (version & 0x0000ff); + + std::ostringstream link_version_string (""); + link_version_string << version_major << "." << version_minor << "." << version_patch; + + if (version_major != compile_major) { + std::cerr << "Error: trying to link against an incompatible RBDL library." << std::endl; + std::cerr << "The library version is: " << compile_version_string.str() << " but rbdl_config.h is version " << link_version_string.str() << std::endl; + abort(); + } else if (version_minor != compile_minor) { + std::cout << "Warning: RBDL library is of version " << compile_version_string.str() << " but rbdl_config.h is from version " << link_version_string.str() << std::endl; + } +} + +RBDL_DLLAPI void rbdl_print_version() { + int compile_version = rbdl_get_api_version(); + + int compile_major = (compile_version & 0xff0000) >> 16; + int compile_minor = (compile_version & 0x00ff00) >> 8; + int compile_patch = (compile_version & 0x0000ff); + + std::ostringstream compile_version_string(""); + compile_version_string << compile_major << "." << compile_minor << "." << compile_patch; + + std::cout << "RBDL version:" << std::endl + << " API version : " << compile_version_string.str() << std::endl; + + if (std::string("unknown") != RBDL_BUILD_REVISION) { + std::cout << " revision : " << RBDL_BUILD_REVISION + << " (branch: " << RBDL_BUILD_BRANCH << ")" << std::endl + << " build type : " << RBDL_BUILD_TYPE << std::endl; + } + +#ifdef RBDL_ENABLE_LOGGING + std::cout << " logging : on (warning: reduces performance!)" << std::endl; +#else + std::cout << " logging : off" << std::endl; +#endif +#ifdef RBDL_USE_SIMPLE_MATH + std::cout << " simplemath : on (warning: reduces performance!)" << std::endl; +#else + std::cout << " simplemath : off" << std::endl; +#endif + +#ifdef RBDL_BUILD_ADDON_LUAMODEL + std::cout << " LuaModel : on" << std::endl; +#else + std::cout << " LuaModel : off" << std::endl; +#endif +#ifdef RBDL_BUILD_ADDON_URDFREADER + std::cout << " URDFReader : on" << std::endl; +#else + std::cout << " URDFReader : off" << std::endl; +#endif + + std::string build_revision (RBDL_BUILD_REVISION); + if (build_revision == "unknown") { + std::cout << std::endl << "Version information incomplete: to enable version information re-build" << std::endl << "library from valid repository and enable RBDL_STORE_VERSION." << std::endl; + } +} diff --git a/3rdparty/rbdl/tests/BodyTests.cc b/3rdparty/rbdl/tests/BodyTests.cc new file mode 100644 index 0000000..476fe2c --- /dev/null +++ b/3rdparty/rbdl/tests/BodyTests.cc @@ -0,0 +1,244 @@ +#include + +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Body.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +/* Tests whether the spatial inertia matches the one specified by its + * parameters + */ +TEST ( TestComputeSpatialInertiaFromAbsoluteRadiiGyration ) { + Body body(1.1, Vector3d (1.5, 1.2, 1.3), Vector3d (1.4, 2., 3.)); + + Matrix3d inertia_C ( + 1.4, 0., 0., + 0., 2., 0., + 0., 0., 3.); + + SpatialMatrix reference_inertia ( + 4.843, -1.98, -2.145, 0, -1.43, 1.32, + -1.98, 6.334, -1.716, 1.43, 0, -1.65, + -2.145, -1.716, 7.059, -1.32, 1.65, 0, + 0, 1.43, -1.32, 1.1, 0, 0, + -1.43, 0, 1.65, 0, 1.1, 0, + 1.32, -1.65, 0, 0, 0, 1.1 + ); + + // cout << LogOutput.str() << endl; + + SpatialRigidBodyInertia body_rbi = SpatialRigidBodyInertia::createFromMassComInertiaC (body.mMass, body.mCenterOfMass, body.mInertia); + + CHECK_ARRAY_CLOSE (reference_inertia.data(), body_rbi.toMatrix().data(), 36, TEST_PREC); + CHECK_ARRAY_CLOSE (inertia_C.data(), body.mInertia.data(), 9, TEST_PREC); +} + +TEST ( TestBodyConstructorMassComInertia ) { + double mass = 1.1; + Vector3d com (1.5, 1.2, 1.3); + Matrix3d inertia_C ( + 8.286, -3.96, -4.29, + -3.96, 10.668, -3.432, + -4.29, -3.432, 11.118 + ); + + Body body (mass, com, inertia_C); + + SpatialMatrix reference_inertia ( + 11.729, -5.94, -6.435, 0, -1.43, 1.32, + -5.94, 15.002, -5.148, 1.43, 0, -1.65, + -6.435, -5.148, 15.177, -1.32, 1.65, 0, + 0, 1.43, -1.32, 1.1, 0, 0, + -1.43, 0, 1.65, 0, 1.1, 0, + 1.32, -1.65, 0, 0, 0, 1.1 + ); + + SpatialRigidBodyInertia body_rbi = SpatialRigidBodyInertia::createFromMassComInertiaC (body.mMass, body.mCenterOfMass, body.mInertia); + CHECK_ARRAY_CLOSE (reference_inertia.data(), body_rbi.toMatrix().data(), 36, TEST_PREC); + CHECK_ARRAY_CLOSE (inertia_C.data(), body.mInertia.data(), 9, TEST_PREC); +} + +TEST ( TestBodyJoinNullbody ) { + ClearLogOutput(); + Body body(1.1, Vector3d (1.5, 1.2, 1.3), Vector3d (1.4, 2., 3.)); + Body nullbody (0., Vector3d (0., 0., 0.), Vector3d (0., 0., 0.)); + + Body joined_body = body; + joined_body.Join (Xtrans(Vector3d (0., 0., 0.)), nullbody); + + SpatialRigidBodyInertia body_rbi (body.mMass, body.mCenterOfMass, body.mInertia); + SpatialRigidBodyInertia joined_body_rbi (joined_body.mMass, joined_body.mCenterOfMass, joined_body.mInertia); + + CHECK_EQUAL (1.1, body.mMass); + CHECK_ARRAY_CLOSE (body.mCenterOfMass.data(), joined_body.mCenterOfMass.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (body_rbi.toMatrix().data(), joined_body_rbi.toMatrix().data(), 36, TEST_PREC); +} + +TEST ( TestBodyJoinTwoBodies ) { + ClearLogOutput(); + Body body_a(1.1, Vector3d (-1.1, 1.3, 0.), Vector3d (3.1, 3.2, 3.3)); + Body body_b(1.1, Vector3d (1.1, 1.3, 0.), Vector3d (3.1, 3.2, 3.3)); + + Body body_joined (body_a); + body_joined.Join (Xtrans(Vector3d (0., 0., 0.)), body_b); + + SpatialRigidBodyInertia body_joined_rbi = SpatialRigidBodyInertia::createFromMassComInertiaC (body_joined.mMass, body_joined.mCenterOfMass, body_joined.mInertia); + + SpatialMatrix reference_inertia ( + 9.918, 0, 0, 0, -0, 2.86, + 0, 9.062, 0, 0, 0, -0, + 0, 0, 12.98, -2.86, 0, 0, + 0, 0, -2.86, 2.2, 0, 0, + -0, 0, 0, 0, 2.2, 0, + 2.86, -0, 0, 0, 0, 2.2 + ); + + CHECK_EQUAL (2.2, body_joined.mMass); + CHECK_ARRAY_EQUAL (Vector3d (0., 1.3, 0.).data(), body_joined.mCenterOfMass.data(), 3); + CHECK_ARRAY_CLOSE (reference_inertia.data(), body_joined_rbi.toMatrix().data(), 36, TEST_PREC); +} + +TEST ( TestBodyJoinTwoBodiesDisplaced ) { + ClearLogOutput(); + Body body_a(1.1, Vector3d (-1.1, 1.3, 0.), Vector3d (3.1, 3.2, 3.3)); + Body body_b(1.1, Vector3d (0., 0., 0.), Vector3d (3.1, 3.2, 3.3)); + + Body body_joined (body_a); + body_joined.Join (Xtrans(Vector3d (1.1, 1.3, 0.)), body_b); + + SpatialRigidBodyInertia body_joined_rbi = SpatialRigidBodyInertia::createFromMassComInertiaC (body_joined.mMass, body_joined.mCenterOfMass, body_joined.mInertia); + + SpatialMatrix reference_inertia ( + 9.918, 0, 0, 0, -0, 2.86, + 0, 9.062, 0, 0, 0, -0, + 0, 0, 12.98, -2.86, 0, 0, + 0, 0, -2.86, 2.2, 0, 0, + -0, 0, 0, 0, 2.2, 0, + 2.86, -0, 0, 0, 0, 2.2 + ); + + CHECK_EQUAL (2.2, body_joined.mMass); + CHECK_ARRAY_EQUAL (Vector3d (0., 1.3, 0.).data(), body_joined.mCenterOfMass.data(), 3); + CHECK_ARRAY_CLOSE (reference_inertia.data(), body_joined_rbi.toMatrix().data(), 36, TEST_PREC); + + +} + +TEST ( TestBodyJoinTwoBodiesRotated ) { + ClearLogOutput(); + Body body_a(1.1, Vector3d (0., 0., 0.), Vector3d (3.1, 3.2, 3.3)); + Body body_b(1.1, Vector3d (0., 0., 0.), Vector3d (3.1, 3.3, 3.2)); + + Body body_joined (body_a); + body_joined.Join (Xrotx(-M_PI*0.5), body_b); + + SpatialRigidBodyInertia body_joined_rbi (body_joined.mMass, body_joined.mCenterOfMass, body_joined.mInertia); + + SpatialMatrix reference_inertia ( + 6.2, 0., 0., 0., 0., 0., + 0., 6.4, 0., 0., 0., 0., + 0., 0., 6.6, 0., 0., 0., + 0., 0., 0., 2.2, 0., 0., + 0., 0., 0., 0., 2.2, 0., + 0., 0., 0., 0., 0., 2.2 + ); + + CHECK_EQUAL (2.2, body_joined.mMass); + CHECK_ARRAY_EQUAL (Vector3d (0., 0., 0.).data(), body_joined.mCenterOfMass.data(), 3); + CHECK_ARRAY_CLOSE (reference_inertia.data(), body_joined_rbi.toMatrix().data(), 36, TEST_PREC); +} + +TEST ( TestBodyJoinTwoBodiesRotatedAndTranslated ) { + ClearLogOutput(); + Body body_a(1.1, Vector3d (0., 0., 0.), Vector3d (3.1, 3.2, 3.3)); + Body body_b(1.1, Vector3d (-1., 1., 0.), Vector3d (3.2, 3.1, 3.3)); + + Body body_joined (body_a); + body_joined.Join (Xrotz(M_PI*0.5) * Xtrans(Vector3d (1., 1., 0.)), body_b); + + SpatialRigidBodyInertia body_joined_rbi (body_joined.mMass, body_joined.mCenterOfMass, body_joined.mInertia); + + SpatialMatrix reference_inertia ( + 6.2, 0., 0., 0., 0., 0., + 0., 6.4, 0., 0., 0., 0., + 0., 0., 6.6, 0., 0., 0., + 0., 0., 0., 2.2, 0., 0., + 0., 0., 0., 0., 2.2, 0., + 0., 0., 0., 0., 0., 2.2 + ); + + CHECK_EQUAL (2.2, body_joined.mMass); + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.).data(), body_joined.mCenterOfMass.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (reference_inertia.data(), body_joined_rbi.toMatrix().data(), 36, TEST_PREC); +} + +TEST ( TestBodyConstructorSpatialRigidBodyInertiaMultiplyMotion ) { + Body body(1.1, Vector3d (1.5, 1.2, 1.3), Vector3d (1.4, 2., 3.)); + + SpatialRigidBodyInertia rbi = SpatialRigidBodyInertia( + body.mMass, + body.mCenterOfMass * body.mMass, + body.mInertia + ); + + SpatialVector mv (1.1, 1.2, 1.3, 1.4, 1.5, 1.6); + SpatialVector fv_matrix = rbi.toMatrix() * mv; + SpatialVector fv_rbi = rbi * mv; + + CHECK_ARRAY_CLOSE ( + fv_matrix.data(), + fv_rbi.data(), + 6, + TEST_PREC + ); +} + +TEST ( TestBodyConstructorSpatialRigidBodyInertia ) { + Body body(1.1, Vector3d (1.5, 1.2, 1.3), Vector3d (1.4, 2., 3.)); + + SpatialRigidBodyInertia rbi = SpatialRigidBodyInertia( + body.mMass, + body.mCenterOfMass * body.mMass, + body.mInertia + ); + SpatialMatrix spatial_inertia = rbi.toMatrix(); + + CHECK_ARRAY_CLOSE ( + spatial_inertia.data(), + rbi.toMatrix().data(), + 36, + TEST_PREC + ); +} + +TEST ( TestBodyConstructorCopySpatialRigidBodyInertia ) { + Body body(1.1, Vector3d (1.5, 1.2, 1.3), Vector3d (1.4, 2., 3.)); + + SpatialRigidBodyInertia rbi = SpatialRigidBodyInertia( + body.mMass, + body.mCenterOfMass * body.mMass, + body.mInertia + ); + + SpatialRigidBodyInertia rbi_from_matrix; + rbi_from_matrix.createFromMatrix (rbi.toMatrix()); + + // cout << "Spatial Inertia = " << endl << spatial_inertia << endl; + // cout << "rbi = " << endl << rbi.toMatrix() << endl; + // cout << "rbi.m = " << rbi.m << endl; + // cout << "rbi.h = " << rbi.h.transpose() << endl; + // cout << "rbi.I = " << endl << rbi.I << endl; + + CHECK_ARRAY_CLOSE ( + rbi.toMatrix().data(), + rbi_from_matrix.toMatrix().data(), + 36, + TEST_PREC + ); +} diff --git a/3rdparty/rbdl/tests/CMakeLists.txt b/3rdparty/rbdl/tests/CMakeLists.txt new file mode 100644 index 0000000..b7d7498 --- /dev/null +++ b/3rdparty/rbdl/tests/CMakeLists.txt @@ -0,0 +1,70 @@ +CMAKE_MINIMUM_REQUIRED (VERSION 2.6) + +CMAKE_POLICY(SET CMP0040 NEW) + +# Needed for UnitTest++ +LIST( APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../CMake ) + +# Look for unittest++ +FIND_PACKAGE (UnitTest++ REQUIRED) +INCLUDE_DIRECTORIES (${UNITTEST++_INCLUDE_DIR}) + +SET ( TESTS_SRCS + main.cc + MathTests.cc + SpatialAlgebraTests.cc + MultiDofTests.cc + KinematicsTests.cc + BodyTests.cc + ModelTests.cc + FloatingBaseTests.cc + CalcVelocitiesTests.cc + CalcAccelerationsTests.cc + DynamicsTests.cc + InverseDynamicsTests.cc + CompositeRigidBodyTests.cc + ImpulsesTests.cc + TwolegModelTests.cc + ContactsTests.cc + UtilsTests.cc + SparseFactorizationTests.cc + CustomJointSingleBodyTests.cc + CustomJointMultiBodyTests.cc + InverseKinematicsTests.cc + LoopConstraintsTests.cc + ScrewJointTests.cc + ) + +INCLUDE_DIRECTORIES ( ../src/ ) + +SET_TARGET_PROPERTIES ( ${PROJECT_EXECUTABLES} PROPERTIES + LINKER_LANGUAGE CXX + OUTPUT_NAME runtests + ) + +ADD_EXECUTABLE ( rbdl_tests ${TESTS_SRCS} ) + +SET_TARGET_PROPERTIES ( rbdl_tests PROPERTIES + LINKER_LANGUAGE CXX + OUTPUT_NAME runtests + ) + +SET (RBDL_LIBRARY rbdl) +IF (RBDL_BUILD_STATIC) + SET (RBDL_LIBRARY rbdl-static) +ENDIF (RBDL_BUILD_STATIC) + +TARGET_LINK_LIBRARIES ( rbdl_tests + ${UNITTEST++_LIBRARY} + ${RBDL_LIBRARY} + ) + +OPTION (RUN_AUTOMATIC_TESTS "Perform automatic tests after compilation?" OFF) + +IF (RUN_AUTOMATIC_TESTS) + ADD_CUSTOM_COMMAND (TARGET rbdl_tests + POST_BUILD + COMMAND ./runtests + COMMENT "Running automated tests..." + ) +ENDIF (RUN_AUTOMATIC_TESTS) diff --git a/3rdparty/rbdl/tests/CalcAccelerationsTests.cc b/3rdparty/rbdl/tests/CalcAccelerationsTests.cc new file mode 100644 index 0000000..644cec0 --- /dev/null +++ b/3rdparty/rbdl/tests/CalcAccelerationsTests.cc @@ -0,0 +1,235 @@ +#include + +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" + +#include "Fixtures.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointSimple) { + QDDot[0] = 1.; + ref_body_id = body_a_id; + point_position = Vector3d (1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE(0., point_acceleration[0], TEST_PREC); + CHECK_CLOSE(1., point_acceleration[1], TEST_PREC); + CHECK_CLOSE(0., point_acceleration[2], TEST_PREC); + + // LOG << "Point accel = " << point_acceleration << endl; +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointSimpleRotated) { + Q[0] = 0.5 * M_PI; + + ref_body_id = body_a_id; + QDDot[0] = 1.; + point_position = Vector3d (1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE(-1., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); + + // LOG << "Point accel = " << point_acceleration << endl; +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointRotation) { + ref_body_id = 1; + QDot[0] = 1.; + point_position = Vector3d (1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE(-1., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); + + ClearLogOutput(); + + // if we are on the other side we should have the opposite value + point_position = Vector3d (-1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( 1., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointRotatedBaseSimple) { + // rotated first joint + + ref_body_id = 1; + Q[0] = M_PI * 0.5; + QDot[0] = 1.; + point_position = Vector3d (1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + CHECK_CLOSE( 0., point_acceleration[0], TEST_PREC); + CHECK_CLOSE(-1., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); + + point_position = Vector3d (-1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + CHECK_CLOSE( 0., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 1., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); + // cout << LogOutput.str() << endl; +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointRotatingBodyB) { + // rotating second joint, point at third body + + ref_body_id = 3; + QDot[1] = 1.; + point_position = Vector3d (1., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( -1., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); + + // move it a bit further up (acceleration should stay the same) + point_position = Vector3d (1., 1., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( -1., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointBodyOrigin) { + // rotating second joint, point at third body + + QDot[0] = 1.; + + ref_body_id = body_b_id; + point_position = Vector3d (0., 0., 0.); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( -1., point_acceleration[0], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[1], TEST_PREC); + CHECK_CLOSE( 0., point_acceleration[2], TEST_PREC); +} + +TEST_FIXTURE(FixedBase3DoF, TestAccelerationLinearFuncOfQddot) { + // rotating second joint, point at third body + + QDot[0] = 1.1; + QDot[1] = 1.3; + QDot[2] = 1.5; + + ref_body_id = body_c_id; + point_position = Vector3d (1., 1., 1.); + + VectorNd qddot_1 = VectorNd::Zero (model->dof_count); + VectorNd qddot_2 = VectorNd::Zero (model->dof_count); + VectorNd qddot_0 = VectorNd::Zero (model->dof_count); + + qddot_1[0] = 0.1; + qddot_1[1] = 0.2; + qddot_1[2] = 0.3; + + qddot_2[0] = 0.32; + qddot_2[1] = -0.1; + qddot_2[2] = 0.53; + + Vector3d acc_1 = CalcPointAcceleration(*model, Q, QDot, qddot_1, ref_body_id, point_position); + Vector3d acc_2 = CalcPointAcceleration(*model, Q, QDot, qddot_2, ref_body_id, point_position); + + MatrixNd G = MatrixNd::Zero (3, model->dof_count); + CalcPointJacobian (*model, Q, ref_body_id, point_position, G, true); + + VectorNd net_acc = G * (qddot_1 - qddot_2); + + Vector3d acc_new = acc_1 - acc_2; + + CHECK_ARRAY_CLOSE (net_acc.data(), acc_new.data(), 3, TEST_PREC); +} + +TEST_FIXTURE (FloatingBase12DoF, TestAccelerationFloatingBaseWithUpdateKinematics ) { + ForwardDynamics (*model, Q, QDot, Tau, QDDot); + + ClearLogOutput(); + Vector3d accel = CalcPointAcceleration (*model, Q, QDot, QDDot, child_2_rot_x_id, Vector3d (0., 0., 0.), true); + + CHECK_ARRAY_CLOSE (Vector3d (0., -9.81, 0.), accel.data(), 3, TEST_PREC); +} + +TEST_FIXTURE (FloatingBase12DoF, TestAccelerationFloatingBaseWithoutUpdateKinematics ) { + ForwardDynamics (*model, Q, QDot, Tau, QDDot); + + //ClearLogOutput(); + Vector3d accel = CalcPointAcceleration (*model, Q, QDot, QDDot, child_2_rot_x_id, Vector3d (0., 0., 0.), false); + + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.), accel.data(), 3, TEST_PREC); + // cout << LogOutput.str() << endl; + // cout << accel.transpose() << endl; +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointRotationFixedJoint) { + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + unsigned int fixed_body_id = model->AddBody (body_c_id, Xtrans (Vector3d (1., -1., 0.)), Joint(JointTypeFixed), fixed_body, "fixed_body"); + + QDot[0] = 1.; + point_position = Vector3d (0., 0., 0.); + Vector3d point_acceleration_reference = CalcPointAcceleration (*model, Q, QDot, QDDot, body_c_id, Vector3d (1., -1., 0.)); + + ClearLogOutput(); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, fixed_body_id, point_position); + // cout << LogOutput.str() << endl; + + CHECK_ARRAY_CLOSE (point_acceleration_reference.data(), + point_acceleration.data(), + 3, + TEST_PREC); +} + +TEST_FIXTURE(FixedBase3DoF, TestCalcPointRotationFixedJointRotatedTransform) { + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + SpatialTransform fixed_transform = Xtrans (Vector3d (1., -1., 0.)) * Xrotz(M_PI * 0.5); + unsigned int fixed_body_id = model->AddBody (body_c_id, fixed_transform, Joint(JointTypeFixed), fixed_body, "fixed_body"); + + QDot[0] = 1.; + point_position = Vector3d (0., 0., 0.); + ClearLogOutput(); + Vector3d point_acceleration_reference = CalcPointAcceleration (*model, Q, QDot, QDDot, body_c_id, Vector3d (1., 1., 0.)); + // cout << LogOutput.str() << endl; + + // cout << "Point position = " << CalcBodyToBaseCoordinates (*model, Q, fixed_body_id, Vector3d (0., 0., 0.)).transpose() << endl; + // cout << "Point position_ref = " << CalcBodyToBaseCoordinates (*model, Q, body_c_id, Vector3d (1., 1., 0.)).transpose() << endl; + + ClearLogOutput(); + point_acceleration = CalcPointAcceleration(*model, Q, QDot, QDDot, fixed_body_id, point_position); + // cout << LogOutput.str() << endl; + + CHECK_ARRAY_CLOSE (point_acceleration_reference.data(), + point_acceleration.data(), + 3, + TEST_PREC); +} + diff --git a/3rdparty/rbdl/tests/CalcVelocitiesTests.cc b/3rdparty/rbdl/tests/CalcVelocitiesTests.cc new file mode 100644 index 0000000..059a16c --- /dev/null +++ b/3rdparty/rbdl/tests/CalcVelocitiesTests.cc @@ -0,0 +1,215 @@ +#include + +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +struct ModelVelocitiesFixture { + ModelVelocitiesFixture () { + ClearLogOutput(); + model = new Model; + + body_a = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_a ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_a_id = model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + body_b = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + Joint joint_b ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + body_b_id = model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + body_c = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_c ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + body_c_id = model->AddBody(2, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + + point_position = Vector3d::Zero(3); + point_velocity = Vector3d::Zero(3); + + ref_body_id = 0; + + ClearLogOutput(); + } + ~ModelVelocitiesFixture () { + delete model; + } + Model *model; + + unsigned int body_a_id, body_b_id, body_c_id, ref_body_id; + Body body_a, body_b, body_c; + Joint joint_a, joint_b, joint_c; + + VectorNd Q; + VectorNd QDot; + + Vector3d point_position, point_velocity; +}; + +TEST_FIXTURE(ModelVelocitiesFixture, TestCalcPointSimple) { + ref_body_id = 1; + QDot[0] = 1.; + point_position = Vector3d (1., 0., 0.); + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + CHECK_CLOSE(0., point_velocity[0], TEST_PREC); + CHECK_CLOSE(1., point_velocity[1], TEST_PREC); + CHECK_CLOSE(0., point_velocity[2], TEST_PREC); + + LOG << "Point velocity = " << point_velocity << endl; + // cout << LogOutput.str() << endl; +} + +TEST_FIXTURE(ModelVelocitiesFixture, TestCalcPointRotatedBaseSimple) { + // rotated first joint + + ref_body_id = 1; + Q[0] = M_PI * 0.5; + QDot[0] = 1.; + point_position = Vector3d (1., 0., 0.); + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + CHECK_CLOSE(-1., point_velocity[0], TEST_PREC); + CHECK_CLOSE( 0., point_velocity[1], TEST_PREC); + CHECK_CLOSE( 0., point_velocity[2], TEST_PREC); + + // cout << LogOutput.str() << endl; +} + +TEST_FIXTURE(ModelVelocitiesFixture, TestCalcPointRotatingBodyB) { + // rotating second joint, point at third body + + ref_body_id = 3; + QDot[1] = 1.; + point_position = Vector3d (1., 0., 0.); + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( 0., point_velocity[0], TEST_PREC); + CHECK_CLOSE( 0., point_velocity[1], TEST_PREC); + CHECK_CLOSE(-1., point_velocity[2], TEST_PREC); +} + +TEST_FIXTURE(ModelVelocitiesFixture, TestCalcPointRotatingBaseXAxis) { + // also rotate the first joint and take a point that is + // on the X direction + + ref_body_id = 3; + QDot[0] = 1.; + QDot[1] = 1.; + point_position = Vector3d (1., -1., 0.); + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( 0., point_velocity[0], TEST_PREC); + CHECK_CLOSE( 2., point_velocity[1], TEST_PREC); + CHECK_CLOSE(-1., point_velocity[2], TEST_PREC); +} + +TEST_FIXTURE(ModelVelocitiesFixture, TestCalcPointRotatedBaseXAxis) { + // perform the previous test with the first joint rotated by pi/2 + // upwards + ClearLogOutput(); + + ref_body_id = 3; + point_position = Vector3d (1., -1., 0.); + + Q[0] = M_PI * 0.5; + QDot[0] = 1.; + QDot[1] = 1.; + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE(-2., point_velocity[0], TEST_PREC); + CHECK_CLOSE( 0., point_velocity[1], TEST_PREC); + CHECK_CLOSE(-1., point_velocity[2], TEST_PREC); +} + +TEST_FIXTURE(ModelVelocitiesFixture, TestCalcPointBodyOrigin) { + // Checks whether the computation is also correct for points at the origin + // of a body + + ref_body_id = body_b_id; + point_position = Vector3d (0., 0., 0.); + + Q[0] = 0.; + QDot[0] = 1.; + + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE( 0., point_velocity[0], TEST_PREC); + CHECK_CLOSE( 1., point_velocity[1], TEST_PREC); + CHECK_CLOSE( 0., point_velocity[2], TEST_PREC); +} + +TEST ( FixedJointCalcPointVelocity ) { + // the standard modeling using a null body + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + + SpatialTransform transform = Xtrans (Vector3d (1., 0., 0.)); + unsigned int fixed_body_id = model.AppendBody (transform, Joint(JointTypeFixed), fixed_body, "fixed_body"); + + VectorNd Q = VectorNd::Zero (model.dof_count); + VectorNd QDot = VectorNd::Zero (model.dof_count); + + QDot[0] = 1.; + + ClearLogOutput(); + Vector3d point0_velocity = CalcPointVelocity (model, Q, QDot, fixed_body_id, Vector3d (0., 0., 0.)); + // cout << LogOutput.str() << endl; + Vector3d point1_velocity = CalcPointVelocity (model, Q, QDot, fixed_body_id, Vector3d (1., 0., 0.)); + + CHECK_ARRAY_CLOSE (Vector3d (0., 1., 0.).data(), point0_velocity.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (Vector3d (0., 2., 0.).data(), point1_velocity.data(), 3, TEST_PREC); +} + +TEST ( FixedJointCalcPointVelocityRotated ) { + // the standard modeling using a null body + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + + SpatialTransform transform = Xtrans (Vector3d (1., 0., 0.)); + unsigned int fixed_body_id = model.AppendBody (transform, Joint(JointTypeFixed), fixed_body, "fixed_body"); + + VectorNd Q = VectorNd::Zero (model.dof_count); + VectorNd QDot = VectorNd::Zero (model.dof_count); + + Q[0] = M_PI * 0.5; + QDot[0] = 1.; + + ClearLogOutput(); + Vector3d point0_velocity = CalcPointVelocity (model, Q, QDot, fixed_body_id, Vector3d (0., 0., 0.)); + // cout << LogOutput.str() << endl; + Vector3d point1_velocity = CalcPointVelocity (model, Q, QDot, fixed_body_id, Vector3d (1., 0., 0.)); + + CHECK_ARRAY_CLOSE (Vector3d (-1., 0., 0.).data(), point0_velocity.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (Vector3d (-2., 0., 0.).data(), point1_velocity.data(), 3, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/CompositeRigidBodyTests.cc b/3rdparty/rbdl/tests/CompositeRigidBodyTests.cc new file mode 100644 index 0000000..6ce896c --- /dev/null +++ b/3rdparty/rbdl/tests/CompositeRigidBodyTests.cc @@ -0,0 +1,261 @@ +#include + +#include + +#include "rbdl/Logging.h" +#include "rbdl/Model.h" +#include "rbdl/Dynamics.h" + +#include "Fixtures.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-12; + +struct CompositeRigidBodyFixture { + CompositeRigidBodyFixture () { + ClearLogOutput(); + model = new Model; + model->gravity = Vector3d (0., -9.81, 0.); + } + ~CompositeRigidBodyFixture () { + delete model; + } + Model *model; +}; + +TEST_FIXTURE(CompositeRigidBodyFixture, TestCompositeRigidBodyForwardDynamicsFloatingBase) { + Body base_body(1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + + model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd TauInv = VectorNd::Constant ((size_t) model->dof_count, 0.); + + MatrixNd H = MatrixNd::Constant ((size_t) model->dof_count, (size_t) model->dof_count, 0.); + VectorNd C = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot_zero = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot_crba = VectorNd::Constant ((size_t) model->dof_count, 0.); + + Q[0] = 1.1; + Q[1] = 1.2; + Q[2] = 1.3; + Q[3] = 0.1; + Q[4] = 0.2; + Q[5] = 0.3; + + QDot[0] = 1.1; + QDot[1] = -1.2; + QDot[2] = 1.3; + QDot[3] = -0.1; + QDot[4] = 0.2; + QDot[5] = -0.3; + + Tau[0] = 2.1; + Tau[1] = 2.2; + Tau[2] = 2.3; + Tau[3] = 1.1; + Tau[4] = 1.2; + Tau[5] = 1.3; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + ClearLogOutput(); + CompositeRigidBodyAlgorithm (*model, Q, H); + // cout << LogOutput.str() << endl; + + InverseDynamics (*model, Q, QDot, QDDot_zero, C); + + CHECK (LinSolveGaussElimPivot (H, C * -1. + Tau, QDDot_crba)); + + CHECK_ARRAY_CLOSE (QDDot.data(), QDDot_crba.data(), QDDot.size(), TEST_PREC); +} + +TEST_FIXTURE(FloatingBase12DoF, TestCRBAFloatingBase12DoF) { + MatrixNd H = MatrixNd::Zero ((size_t) model->dof_count, (size_t) model->dof_count); + + VectorNd C = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot_zero = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot_crba = VectorNd::Constant ((size_t) model->dof_count, 0.); + + + Q[ 0] = 1.1; + Q[ 1] = 1.2; + Q[ 2] = 1.3; + Q[ 3] = 0.1; + Q[ 4] = 0.2; + Q[ 5] = 0.3; + Q[ 6] = -1.3; + Q[ 7] = -1.4; + Q[ 8] = -1.5; + Q[ 9] = -0.3; + Q[10] = -0.4; + Q[11] = -0.5; + + QDot[ 0] = 1.1; + QDot[ 1] = -1.2; + QDot[ 2] = 1.3; + QDot[ 3] = -0.1; + QDot[ 4] = 0.2; + QDot[ 5] = -0.3; + QDot[ 6] = -1.1; + QDot[ 7] = 1.2; + QDot[ 8] = -1.3; + QDot[ 9] = 0.1; + QDot[10] = -0.2; + QDot[11] = 0.3; + + Tau[ 0] = -1.1; + Tau[ 1] = 1.2; + Tau[ 2] = -1.3; + Tau[ 3] = 1.1; + Tau[ 4] = -1.2; + Tau[ 5] = 1.3; + Tau[ 6] = 0.1; + Tau[ 7] = -0.2; + Tau[ 8] = 0.3; + Tau[ 9] = -0.1; + Tau[10] = 0.2; + Tau[11] = -0.3; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + ClearLogOutput(); + CompositeRigidBodyAlgorithm (*model, Q, H); + // cout << LogOutput.str() << endl; + InverseDynamics (*model, Q, QDot, QDDot_zero, C); + + CHECK (LinSolveGaussElimPivot (H, C * -1. + Tau, QDDot_crba)); + + CHECK_ARRAY_CLOSE (QDDot.data(), QDDot_crba.data(), QDDot.size(), TEST_PREC); +} + +TEST_FIXTURE(FloatingBase12DoF, TestCRBAFloatingBase12DoFInverseDynamics) { + MatrixNd H_crba = MatrixNd::Zero ((size_t) model->dof_count, (size_t) model->dof_count); + MatrixNd H_id = MatrixNd::Zero ((size_t) model->dof_count, (size_t) model->dof_count); + + Q[ 0] = 1.1; + Q[ 1] = 1.2; + Q[ 2] = 1.3; + Q[ 3] = 0.1; + Q[ 4] = 0.2; + Q[ 5] = 0.3; + Q[ 6] = -1.3; + Q[ 7] = -1.4; + Q[ 8] = -1.5; + Q[ 9] = -0.3; + Q[10] = -0.4; + Q[11] = -0.5; + + QDot.setZero(); + + assert (model->dof_count == 12); + + UpdateKinematicsCustom (*model, &Q, NULL, NULL); + CompositeRigidBodyAlgorithm (*model, Q, H_crba, false); + + VectorNd H_col = VectorNd::Zero (model->dof_count); + VectorNd QDDot_zero = VectorNd::Zero (model->dof_count); + + unsigned int i; + for (i = 0; i < model->dof_count; i++) { + // compute each column + VectorNd delta_a = VectorNd::Zero (model->dof_count); + delta_a[i] = 1.; + // cout << delta_a << endl; + + // compute ID (model, q, qdot, delta_a) + VectorNd id_delta = VectorNd::Zero (model->dof_count); + InverseDynamics (*model, Q, QDot, delta_a, id_delta); + + // compute ID (model, q, qdot, zero) + VectorNd id_zero = VectorNd::Zero (model->dof_count); + InverseDynamics (*model, Q, QDot, QDDot_zero, id_zero); + + H_col = id_delta - id_zero; + // cout << "H_col = " << H_col << endl; + H_id.block<12, 1>(0, i) = H_col; + } + + // cout << "H (crba) = " << endl << H_crba << endl; + // cout << "H (id) = " << endl << H_id << endl; + + CHECK_ARRAY_CLOSE (H_crba.data(), H_id.data(), model->dof_count * model->dof_count, TEST_PREC); +} + +TEST_FIXTURE(FixedBase6DoF, TestCRBAFloatingBase12DoFInverseDynamics) { + MatrixNd H_crba = MatrixNd::Zero ((size_t) model->dof_count, (size_t) model->dof_count); + MatrixNd H_id = MatrixNd::Zero ((size_t) model->dof_count, (size_t) model->dof_count); + + Q[ 0] = 1.1; + Q[ 1] = 1.2; + Q[ 2] = 1.3; + Q[ 3] = 0.1; + Q[ 4] = 0.2; + Q[ 5] = 0.3; + + QDot.setZero(); + + assert (model->dof_count == 6); + + UpdateKinematicsCustom (*model, &Q, NULL, NULL); + CompositeRigidBodyAlgorithm (*model, Q, H_crba, false); + + VectorNd H_col = VectorNd::Zero (model->dof_count); + VectorNd QDDot_zero = VectorNd::Zero (model->dof_count); + + unsigned int i; + for (i = 0; i < 6; i++) { + // compute each column + VectorNd delta_a = VectorNd::Zero (model->dof_count); + delta_a[i] = 1.; + + ClearLogOutput(); + // compute ID (model, q, qdot, delta_a) + VectorNd id_delta = VectorNd::Zero (model->dof_count); + InverseDynamics (*model, Q, QDot, delta_a, id_delta); + + // compute ID (model, q, qdot, zero) + VectorNd id_zero = VectorNd::Zero (model->dof_count); + InverseDynamics (*model, Q, QDot, QDDot_zero, id_zero); + + H_col.setZero(); + H_col = id_delta - id_zero; + + H_id.block<6, 1>(0, i) = H_col; + } + + CHECK_ARRAY_CLOSE (H_crba.data(), H_id.data(), model->dof_count * model->dof_count, TEST_PREC); +} + +TEST_FIXTURE(CompositeRigidBodyFixture, TestCompositeRigidBodyForwardDynamicsSpherical) { + Body base_body(1., Vector3d (0., 0., 0.), Vector3d (1., 2., 3.)); + + model->AddBody(0, SpatialTransform(), Joint(JointTypeSpherical), base_body); + VectorNd Q = VectorNd::Constant ((size_t) model->q_size, 0.); + model->SetQuaternion (1, Quaternion(), Q); + MatrixNd H = MatrixNd::Constant ((size_t) model->qdot_size, (size_t) model->qdot_size, 0.); + CompositeRigidBodyAlgorithm (*model, Q, H, true); + + Matrix3d H_ref ( + 1., 0., 0., + 0., 2., 0., + 0., 0., 3. + ); + + CHECK_ARRAY_CLOSE (H_ref.data(), H.data(), 9, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/ContactsTests.cc b/3rdparty/rbdl/tests/ContactsTests.cc new file mode 100644 index 0000000..2042573 --- /dev/null +++ b/3rdparty/rbdl/tests/ContactsTests.cc @@ -0,0 +1,724 @@ +#include + +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Constraints.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Kinematics.h" + +#include "Fixtures.h" +#include "Human36Fixture.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-11; + +struct FixedBase6DoF9DoF { + FixedBase6DoF9DoF () { + ClearLogOutput(); + model = new Model; + + model->gravity = Vector3d (0., -9.81, 0.); + + /* 3 DoF (rot.) joint at base + * 3 DoF (rot.) joint child origin + * + * X Contact point (ref child) + * | + * Base | + * / body | + * O-------* + * \ + * Child body + */ + + // base body (3 DoF) + base = Body ( + 1., + Vector3d (0.5, 0., 0.), + Vector3d (1., 1., 1.) + ); + joint_rotzyx = Joint ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + base_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_rotzyx, base); + + // child body 1 (3 DoF) + child = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + child_id = model->AddBody (base_id, Xtrans (Vector3d (0., 0., 0.)), joint_rotzyx, child); + + // child body (3 DoF) + child_2 = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + child_2_id = model->AddBody (child_id, Xtrans (Vector3d (0., 0., 0.)), joint_rotzyx, child_2); + + Q = VectorNd::Constant (model->mBodies.size() - 1, 0.); + QDot = VectorNd::Constant (model->mBodies.size() - 1, 0.); + QDDot = VectorNd::Constant (model->mBodies.size() - 1, 0.); + Tau = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + contact_body_id = child_id; + contact_point = Vector3d (0.5, 0.5, 0.); + contact_normal = Vector3d (0., 1., 0.); + + ClearLogOutput(); + } + + ~FixedBase6DoF9DoF () { + delete model; + } + Model *model; + + unsigned int base_id, child_id, child_2_id; + + Body base, child, child_2; + + Joint joint_rotzyx; + + VectorNd Q; + VectorNd QDot; + VectorNd QDDot; + VectorNd Tau; + + unsigned int contact_body_id; + Vector3d contact_point; + Vector3d contact_normal; + ConstraintSet constraint_set; +}; + +// +// ForwardDynamicsConstraintsDirect +// +TEST ( TestForwardDynamicsConstraintsDirectSimple ) { + Model model; + model.gravity = Vector3d (0., -9.81, 0.); + Body base_body (1., Vector3d (0., 0., 0.), Vector3d (1., 1., 1.)); + unsigned int base_body_id = model.AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_body); + + VectorNd Q = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model.dof_count, 0.); + + Q[1] = 1.; + QDot[0] = 1.; + QDot[3] = -1.; + + unsigned int contact_body_id = base_body_id; + Vector3d contact_point ( 0., -1., 0.); + + ConstraintSet constraint_set; + + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), "ground_x"); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.), "ground_y"); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 0., 1.), "ground_z"); + + constraint_set.Bind (model); + + ClearLogOutput(); + +// cout << constraint_set.acceleration.transpose() << endl; + ForwardDynamicsConstraintsDirect (model, Q, QDot, Tau, constraint_set, QDDot); + +// cout << "A = " << endl << constraint_set.A << endl << endl; +// cout << "H = " << endl << constraint_set.H << endl << endl; +// cout << "b = " << endl << constraint_set.b << endl << endl; +// cout << "x = " << endl << constraint_set.x << endl << endl; +// cout << constraint_set.b << endl; +// cout << "QDDot = " << QDDot.transpose() << endl; + + Vector3d point_acceleration = CalcPointAcceleration (model, Q, QDot, QDDot, contact_body_id, contact_point); + + CHECK_ARRAY_CLOSE ( + Vector3d (0., 0., 0.).data(), + point_acceleration.data(), + 3, + TEST_PREC + ); + + // cout << "LagrangianSimple Logoutput Start" << endl; + // cout << LogOutput.str() << endl; + // cout << "LagrangianSimple Logoutput End" << endl; +} + +TEST ( TestForwardDynamicsConstraintsDirectMoving ) { + Model model; + model.gravity = Vector3d (0., -9.81, 0.); + Body base_body (1., Vector3d (0., 0., 0.), Vector3d (1., 1., 1.)); + unsigned int base_body_id = model.AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_body); + + + VectorNd Q = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model.dof_count, 0.); + + Q[0] = 0.1; + Q[1] = 0.2; + Q[2] = 0.3; + Q[3] = 0.4; + Q[4] = 0.5; + Q[5] = 0.6; + QDot[0] = 1.1; + QDot[1] = 1.2; + QDot[2] = 1.3; + QDot[3] = -1.4; + QDot[4] = -1.5; + QDot[5] = -1.6; + + unsigned int contact_body_id = base_body_id; + Vector3d contact_point ( 0., -1., 0.); + + ConstraintSet constraint_set; + + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), "ground_x"); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.), "ground_y"); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 0., 1.), "ground_z"); + + constraint_set.Bind (model); + + ClearLogOutput(); + + ForwardDynamicsConstraintsDirect (model, Q, QDot, Tau, constraint_set, QDDot); + + Vector3d point_acceleration = CalcPointAcceleration (model, Q, QDot, QDDot, contact_body_id, contact_point); + + CHECK_ARRAY_CLOSE ( + Vector3d (0., 0., 0.).data(), + point_acceleration.data(), + 3, + TEST_PREC + ); + + // cout << "LagrangianSimple Logoutput Start" << endl; + // cout << LogOutput.str() << endl; + // cout << "LagrangianSimple Logoutput End" << endl; +} + +// +// ForwardDynamicsContacts +// +TEST_FIXTURE (FixedBase6DoF, ForwardDynamicsContactsSingleContact) { + contact_normal.set (0., 1., 0.); + constraint_set.AddContactConstraint (contact_body_id, contact_point, contact_normal); + ConstraintSet constraint_set_lagrangian = constraint_set.Copy(); + + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Vector3d point_accel_lagrangian, point_accel_contacts; + + ClearLogOutput(); + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); + ClearLogOutput(); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); +// cout << LogOutput.str() << endl; + ClearLogOutput(); + + point_accel_lagrangian = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point, true); + point_accel_contacts = CalcPointAcceleration (*model, Q, QDot, QDDot_contacts, contact_body_id, contact_point, true); + + CHECK_CLOSE (constraint_set_lagrangian.force[0], constraint_set.force[0], TEST_PREC); + CHECK_CLOSE (contact_normal.dot(point_accel_lagrangian), contact_normal.dot(point_accel_contacts), TEST_PREC); + CHECK_ARRAY_CLOSE (point_accel_lagrangian.data(), point_accel_contacts.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot_contacts.data(), QDDot_lagrangian.size(), TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF, ForwardDynamicsContactsSingleContactRotated) { + Q[0] = 0.6; + Q[3] = M_PI * 0.6; + Q[4] = 0.1; + + contact_normal.set (0., 1., 0.); + + constraint_set.AddContactConstraint (contact_body_id, contact_point, contact_normal); + ConstraintSet constraint_set_lagrangian = constraint_set.Copy(); + + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Vector3d point_accel_lagrangian, point_accel_contacts, point_accel_contacts_opt; + + ClearLogOutput(); + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts_opt = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts_opt); + + point_accel_lagrangian = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point, true); + point_accel_contacts_opt = CalcPointAcceleration (*model, Q, QDot, QDDot_contacts_opt, contact_body_id, contact_point, true); + + CHECK_CLOSE (constraint_set_lagrangian.force[0], constraint_set.force[0], TEST_PREC); + CHECK_CLOSE (contact_normal.dot(point_accel_lagrangian), contact_normal.dot(point_accel_contacts_opt), TEST_PREC); + CHECK_ARRAY_CLOSE (point_accel_lagrangian.data(), point_accel_contacts_opt.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot_contacts_opt.data(), QDDot_lagrangian.size(), TEST_PREC); +} + +// +// Similiar to the previous test, this test compares the results of +// - ForwardDynamicsConstraintsDirect +// - ForwardDynamcsContactsOpt +// for the example model in FixedBase6DoF and a moving state (i.e. a +// nonzero QDot) +// +TEST_FIXTURE (FixedBase6DoF, ForwardDynamicsContactsSingleContactRotatedMoving) { + Q[0] = 0.6; + Q[3] = M_PI * 0.6; + Q[4] = 0.1; + + QDot[0] = -0.3; + QDot[1] = 0.1; + QDot[2] = -0.5; + QDot[3] = 0.8; + + contact_normal.set (0., 1., 0.); + constraint_set.AddContactConstraint (contact_body_id, contact_point, contact_normal); + ConstraintSet constraint_set_lagrangian = constraint_set.Copy(); + + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Vector3d point_accel_lagrangian, point_accel_contacts; + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); +// cout << LogOutput.str() << endl; + ClearLogOutput(); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); +// cout << LogOutput.str() << endl; + + point_accel_lagrangian = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point, true); + point_accel_contacts = CalcPointAcceleration (*model, Q, QDot, QDDot_contacts, contact_body_id, contact_point, true); + + // check whether FDContactsLagrangian and FDContactsOld match + CHECK_CLOSE (constraint_set_lagrangian.force[0], constraint_set.force[0], TEST_PREC); + + CHECK_CLOSE (contact_normal.dot(point_accel_lagrangian), contact_normal.dot(point_accel_contacts), TEST_PREC); + CHECK_ARRAY_CLOSE (point_accel_lagrangian.data(), point_accel_contacts.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot_contacts.data(), QDDot_lagrangian.size(), TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF, ForwardDynamicsContactsOptDoubleContact) { + ConstraintSet constraint_set_lagrangian; + + constraint_set.AddContactConstraint (contact_body_id, Vector3d (1., 0., 0.), contact_normal); + constraint_set.AddContactConstraint (contact_body_id, Vector3d (0., 1., 0.), contact_normal); + + constraint_set_lagrangian = constraint_set.Copy(); + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Vector3d point_accel_lagrangian, point_accel_contacts; + + ClearLogOutput(); + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); + + point_accel_lagrangian = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point, true); + point_accel_contacts = CalcPointAcceleration (*model, Q, QDot, QDDot_contacts, contact_body_id, contact_point, true); + + // check whether FDContactsLagrangian and FDContacts match + CHECK_ARRAY_CLOSE ( + constraint_set_lagrangian.force.data(), + constraint_set.force.data(), + constraint_set.size(), TEST_PREC + ); + + // check whether the point accelerations match + CHECK_ARRAY_CLOSE (point_accel_lagrangian.data(), point_accel_contacts.data(), 3, TEST_PREC); + + // check whether the generalized accelerations match + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot_contacts.data(), QDDot_lagrangian.size(), TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF, ForwardDynamicsContactsOptDoubleContactRepeated) { + // makes sure that all variables in the constraint set gets reset + // properly when making repeated calls to ForwardDynamicsContacts. + ConstraintSet constraint_set_lagrangian; + + constraint_set.AddContactConstraint (contact_body_id, Vector3d (1., 0., 0.), contact_normal); + constraint_set.AddContactConstraint (contact_body_id, Vector3d (0., 1., 0.), contact_normal); + + constraint_set_lagrangian = constraint_set.Copy(); + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Vector3d point_accel_lagrangian, point_accel_contacts; + + ClearLogOutput(); + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); + // Call ForwardDynamicsContacts multiple times such that old values might + // be re-used and thus cause erroneus values. + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); + + point_accel_lagrangian = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point, true); + point_accel_contacts = CalcPointAcceleration (*model, Q, QDot, QDDot_contacts, contact_body_id, contact_point, true); + + // check whether FDContactsLagrangian and FDContacts match + CHECK_ARRAY_CLOSE ( + constraint_set_lagrangian.force.data(), + constraint_set.force.data(), + constraint_set.size(), TEST_PREC + ); + + // check whether the point accelerations match + CHECK_ARRAY_CLOSE (point_accel_lagrangian.data(), point_accel_contacts.data(), 3, TEST_PREC); + + // check whether the generalized accelerations match + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot_contacts.data(), QDDot_lagrangian.size(), TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF, ForwardDynamicsContactsOptMultipleContact) { + ConstraintSet constraint_set_lagrangian; + + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (1., 0., 0.)); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.)); + + constraint_set_lagrangian = constraint_set.Copy(); + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + // we rotate the joints so that we have full mobility at the contact + // point: + // + // O X (contact point) + // \ / + // \ / + // \ / + // * + // + + Q[0] = M_PI * 0.25; + Q[1] = 0.2; + Q[3] = M_PI * 0.5; + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + VectorNd QDDot_contacts = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot_contacts); + +// cout << LogOutput.str() << endl; + + Vector3d point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot, contact_body_id, contact_point); +// cout << "point_accel_c = " << point_accel_c.transpose() << endl; + +// cout << "Lagrangian contact force " << contact_data_lagrangian[0].force << ", " << contact_data_lagrangian[1].force << endl; + + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot_contacts.data(), QDDot_lagrangian.size(), TEST_PREC); + + CHECK_ARRAY_CLOSE ( + constraint_set_lagrangian.force.data(), + constraint_set.force.data(), + constraint_set.size(), TEST_PREC + ); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF9DoF, ForwardDynamicsContactsOptMultipleContactsMultipleBodiesMoving) { + ConstraintSet constraint_set_lagrangian; + + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (1., 0., 0.)); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.)); + constraint_set.AddContactConstraint (child_2_id, contact_point, Vector3d (0., 1., 0.)); + + constraint_set_lagrangian = constraint_set.Copy(); + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Q[0] = 0.1; + Q[1] = -0.1; + Q[2] = 0.1; + Q[3] = -0.1; + Q[4] = -0.1; + Q[5] = 0.1; + + QDot[0] = 1.; + QDot[1] = -1.; + QDot[2] = 1; + QDot[3] = -1.5; + QDot[4] = 1.5; + QDot[5] = -1.5; + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot); +// cout << LogOutput.str() << endl; + + Vector3d point_accel_c, point_accel_2_c; + + point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot, contact_body_id, contact_point); + point_accel_2_c = CalcPointAcceleration (*model, Q, QDot, QDDot, child_2_id, contact_point); + +// cout << "point_accel_c = " << point_accel_c.transpose() << endl; + + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); +// cout << "Lagrangian contact force " << contact_data_lagrangian[0].force << ", " << contact_data_lagrangian[1].force << ", " << contact_data_lagrangian[2].force << endl; + + CHECK_ARRAY_CLOSE ( + constraint_set_lagrangian.force.data(), + constraint_set.force.data(), + constraint_set.size(), TEST_PREC + ); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); + CHECK_CLOSE (0., point_accel_2_c[1], TEST_PREC); + + point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point); + point_accel_2_c = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, child_2_id, contact_point); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); + CHECK_CLOSE (0., point_accel_2_c[1], TEST_PREC); + + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot.data(), QDDot.size(), TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF9DoF, ForwardDynamicsContactsOptMultipleContactsMultipleBodiesMovingAlternate) { + ConstraintSet constraint_set_lagrangian; + + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (1., 0., 0.)); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.)); + constraint_set.AddContactConstraint (child_2_id, contact_point, Vector3d (0., 1., 0.)); + + constraint_set_lagrangian = constraint_set.Copy(); + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + Q[0] = 0.1; + Q[1] = -0.3; + Q[2] = 0.15; + Q[3] = -0.21; + Q[4] = -0.81; + Q[5] = 0.11; + Q[6] = 0.31; + Q[7] = -0.91; + Q[8] = 0.61; + + QDot[0] = 1.3; + QDot[1] = -1.7; + QDot[2] = 3; + QDot[3] = -2.5; + QDot[4] = 1.5; + QDot[5] = -5.5; + QDot[6] = 2.5; + QDot[7] = -1.5; + QDot[8] = -3.5; + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot); +// cout << LogOutput.str() << endl; + + Vector3d point_accel_c, point_accel_2_c; + + point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot, contact_body_id, contact_point); + point_accel_2_c = CalcPointAcceleration (*model, Q, QDot, QDDot, child_2_id, contact_point); + +// cout << "point_accel_c = " << point_accel_c.transpose() << endl; + + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); +// cout << "Lagrangian contact force " << contact_data_lagrangian[0].force << ", " << contact_data_lagrangian[1].force << ", " << contact_data_lagrangian[2].force << endl; + + CHECK_ARRAY_CLOSE ( + constraint_set_lagrangian.force.data(), + constraint_set.force.data(), + constraint_set.size(), TEST_PREC + ); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); + CHECK_CLOSE (0., point_accel_2_c[1], TEST_PREC); + + point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point); + point_accel_2_c = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, child_2_id, contact_point); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); + CHECK_CLOSE (0., point_accel_2_c[1], TEST_PREC); + + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot.data(), QDDot.size(), TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF12DoFFloatingBase, ForwardDynamicsContactsMultipleContactsFloatingBase) { + ConstraintSet constraint_set_lagrangian; + + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (1., 0., 0.)); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.)); + constraint_set.AddContactConstraint (child_2_id, contact_point, Vector3d (0., 1., 0.)); + + constraint_set_lagrangian = constraint_set.Copy(); + constraint_set_lagrangian.Bind (*model); + constraint_set.Bind (*model); + + VectorNd QDDot_lagrangian = VectorNd::Constant (model->dof_count, 0.); + + Q[0] = 0.1; + Q[1] = -0.3; + Q[2] = 0.15; + Q[3] = -0.21; + Q[4] = -0.81; + Q[5] = 0.11; + Q[6] = 0.31; + Q[7] = -0.91; + Q[8] = 0.61; + + QDot[0] = 1.3; + QDot[1] = -1.7; + QDot[2] = 3; + QDot[3] = -2.5; + QDot[4] = 1.5; + QDot[5] = -5.5; + QDot[6] = 2.5; + QDot[7] = -1.5; + QDot[8] = -3.5; + + ClearLogOutput(); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot); +// cout << LogOutput.str() << endl; + + Vector3d point_accel_c, point_accel_2_c; + + point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot, contact_body_id, contact_point); + point_accel_2_c = CalcPointAcceleration (*model, Q, QDot, QDDot, child_2_id, contact_point); + +// cout << "point_accel_c = " << point_accel_c.transpose() << endl; + + ClearLogOutput(); + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_lagrangian, QDDot_lagrangian); +// cout << "Lagrangian contact force " << contact_data_lagrangian[0].force << ", " << contact_data_lagrangian[1].force << ", " << contact_data_lagrangian[2].force << endl; +// cout << LogOutput.str() << endl; + + CHECK_ARRAY_CLOSE ( + constraint_set_lagrangian.force.data(), + constraint_set.force.data(), + constraint_set.size(), TEST_PREC + ); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); + CHECK_CLOSE (0., point_accel_2_c[1], TEST_PREC); + + point_accel_c = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, contact_body_id, contact_point); + point_accel_2_c = CalcPointAcceleration (*model, Q, QDot, QDDot_lagrangian, child_2_id, contact_point); + + CHECK_CLOSE (0., point_accel_c[0], TEST_PREC); + CHECK_CLOSE (0., point_accel_c[1], TEST_PREC); + CHECK_CLOSE (0., point_accel_2_c[1], TEST_PREC); + + CHECK_ARRAY_CLOSE (QDDot_lagrangian.data(), QDDot.data(), QDDot.size(), TEST_PREC); +} + +TEST_FIXTURE (Human36, ForwardDynamicsContactsFixedBody) { + VectorNd qddot_lagrangian (VectorNd::Zero(qddot.size())); + VectorNd qddot_sparse (VectorNd::Zero(qddot.size())); + + randomizeStates(); + + ConstraintSet constraint_upper_trunk; + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyUpperTrunk], Vector3d (1.1, 2.2, 3.3), Vector3d (1., 0., 0.)); + constraint_upper_trunk.Bind (*model_3dof); + + ForwardDynamicsConstraintsDirect (*model_3dof, q, qdot, tau, constraint_upper_trunk, qddot_lagrangian); + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraint_upper_trunk, qddot_sparse); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraint_upper_trunk, qddot); + + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm() * 10.); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_sparse.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm() * 10.); +} + +TEST_FIXTURE (Human36, ForwardDynamicsContactsImpulses) { + VectorNd qddot_lagrangian (VectorNd::Zero(qddot.size())); + + for (int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot_3dof[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + Vector3d heel_point (-0.03, 0., -0.03); + + ConstraintSet constraint_upper_trunk; + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyFootLeft], heel_point, Vector3d (1., 0., 0.)); + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyFootLeft], heel_point, Vector3d (0., 1., 0.)); + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyFootLeft], heel_point, Vector3d (0., 0., 1.)); + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyFootRight], heel_point, Vector3d (1., 0., 0.)); + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyFootRight], heel_point, Vector3d (0., 1., 0.)); + constraint_upper_trunk.AddContactConstraint (body_id_3dof[BodyFootRight], heel_point, Vector3d (0., 0., 1.)); + constraint_upper_trunk.Bind (*model_3dof); + + VectorNd qdotplus (VectorNd::Zero (qdot.size())); + + ComputeConstraintImpulsesDirect (*model_3dof, q, qdot, constraint_upper_trunk, qdotplus); + + Vector3d heel_left_velocity = CalcPointVelocity (*model_3dof, q, qdotplus, body_id_3dof[BodyFootLeft], heel_point); + Vector3d heel_right_velocity = CalcPointVelocity (*model_3dof, q, qdotplus, body_id_3dof[BodyFootRight], heel_point); + + CHECK_ARRAY_CLOSE (Vector3d(0., 0., 0.).data(), heel_left_velocity.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (Vector3d(0., 0., 0.).data(), heel_right_velocity.data(), 3, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/CustomJointMultiBodyTests.cc b/3rdparty/rbdl/tests/CustomJointMultiBodyTests.cc new file mode 100644 index 0000000..fdfe616 --- /dev/null +++ b/3rdparty/rbdl/tests/CustomJointMultiBodyTests.cc @@ -0,0 +1,1044 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * Copyright (c) 2016 Matthew Millard + */ + + +#include + +#include + +#include "Fixtures.h" +#include "Human36Fixture.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Constraints.h" +#include + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-11; +const int NUMBER_OF_MODELS = 3; +const int NUMBER_OF_BODIES = 3; + +//============================================================================== +/* + + The purpose of this test is to test that all of the code in RBDL + related to a multibody mechanism that includes a custom joint functions. + Specifically this test is for the multi-pass algorithms in rbdl ... namely + the CompositeRigidBodyAlgorithm. However, because these tests have already + been written for CustomJointSingleBodyTests.cc, we'll run them all on the + multibody models that we will be testing. + + We will be testing 3 models to get good coverage of the + CompositeRigidBodyAlgorithm: + + 1. Rx - multidof - custom + 2. Rx - custom - multidof + 3. custom - multidof - Rx + + As before, to test that the model works, we will create a model using + standard RBDL versions (the reference model), and then we will create + a model using a custom joint in the correct location. The following + algorithms will be tested in this manner: + + UpdateKinematicsCustom + Jacobians + InverseDynamics + CompositeRigidBodyAlgorithm + ForwardDynamics + CalcMInvTimestau + ForwardDynamicsContactsKokkevis + +*/ +//============================================================================== + +struct CustomJointTypeRevoluteX : public CustomJoint { + CustomJointTypeRevoluteX(){ + mDoFCount = 1; + S = MatrixNd::Zero(6,1); + S(0,0)=1.0; + d_u = MatrixNd::Zero(mDoFCount,1); + } + + virtual void jcalc (Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot) + { + model.X_J[joint_id] = Xrotx(q[model.mJoints[joint_id].q_index]); + model.v_J[joint_id][0] = qdot[model.mJoints[joint_id].q_index]; + } + + virtual void jcalc_X_lambda_S ( Model &model, + unsigned int joint_id, + const Math::VectorNd &q) + { + model.X_lambda[joint_id] = + Xrotx (q[model.mJoints[joint_id].q_index]) + * model.X_T[joint_id]; + + + const Joint &joint = model.mJoints[joint_id]; + model.mCustomJoints[joint.custom_joint_index]->S = S; + + } + +}; + +struct CustomEulerZYXJoint : public CustomJoint { + CustomEulerZYXJoint () { + mDoFCount = 3; + S = MatrixNd::Zero (6,3); + d_u = MatrixNd::Zero(mDoFCount,1); + } + + virtual void jcalc (Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot) + { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_J[joint_id].E = Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ); + + S.setZero(); + S(0,0) = -s1; + S(0,2) = 1.; + + S(1,0) = c1 * s2; + S(1,1) = c2; + + S(2,0) = c1 * c2; + S(2,1) = - s2; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = S * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set( + -c1*qdot0*qdot1, + -s1*s2*qdot0*qdot1 + c1*c2*qdot0*qdot2 - s2*qdot1*qdot2, + -s1*c2*qdot0*qdot1 - c1*s2*qdot0*qdot2 - c2*qdot1*qdot2, + 0., 0., 0. + ); + } + + virtual void jcalc_X_lambda_S ( Model &model, + unsigned int joint_id, + const Math::VectorNd &q) + { + + + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + + model.X_lambda[joint_id] = SpatialTransform ( + Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ), + Vector3d (0., 0., 0.)) * model.X_T[joint_id]; + + S.setZero(); + S(0,0) = -s1; + S(0,2) = 1.; + + S(1,0) = c1 * s2; + S(1,1) = c2; + + S(2,0) = c1 * c2; + S(2,1) = - s2; + + const Joint &joint = model.mJoints[joint_id]; + model.mCustomJoints[joint.custom_joint_index]->S = S; + } +}; + +//============================================================================== +//Test Fixture +//============================================================================== + + +struct CustomJointMultiBodyFixture { + CustomJointMultiBodyFixture () { + + reference_model.resize(NUMBER_OF_MODELS); + custom_model.resize(NUMBER_OF_MODELS); + + body.resize(NUMBER_OF_MODELS); + custom_joint.resize(NUMBER_OF_MODELS); + + reference_body_id.resize(NUMBER_OF_MODELS); + custom_body_id.resize(NUMBER_OF_MODELS); + + for(int i=0; i < NUMBER_OF_MODELS; ++i){ + body.at(i).resize(3); + custom_joint.at(i).resize(1); + + reference_body_id.at(i).resize(3); + custom_body_id.at(i).resize(3); + + } + + q.resize(NUMBER_OF_MODELS); + qdot.resize(NUMBER_OF_MODELS); + qddot.resize(NUMBER_OF_MODELS); + tau.resize(NUMBER_OF_MODELS); + + //======================================================== + //Test Model 1: Rx - multidof3 - custom + //======================================================== + + custom_rx_joint1 = CustomJointTypeRevoluteX(); + + Matrix3d inertia1 = Matrix3d::Identity(3,3); + + Body body11 = Body (1., Vector3d (1.1, 1.2, 1.3), inertia1); + Body body12 = Body (2., Vector3d (2.1, 2.2, 2.3), inertia1); + Body body13 = Body (3., Vector3d (3.1, 3.2, 3.3), inertia1); + + Model reference1, custom1; + + Vector3d r1 = Vector3d(0.78,-0.125,0.37); + + double th1 = M_PI/6.0; + double sinTh1 = sin(th1); + double cosTh1 = cos(th1); + + Matrix3d rm1 = Matrix3d( 1.0, 0., 0., + 0., cosTh1, -sinTh1, + 0., sinTh1, cosTh1); + + Vector3d r2 = Vector3d(-0.178,0.2125,-0.937); + + double th2 = M_PI/2.15; + double sinTh2 = sin(th2); + double cosTh2 = cos(th2); + + Matrix3d rm2 = Matrix3d( cosTh2, 0., sinTh2, + 0., 1., 0., + -sinTh2, 0., cosTh2); + + + + unsigned int reference_body_id10 = + reference1.AddBody (0, + SpatialTransform(), + Joint(JointTypeRevoluteX), + body11); + + unsigned int reference_body_id11 = + reference1.AddBody (reference_body_id10, + SpatialTransform(rm1,r1), + Joint(JointTypeEulerZYX), + body12); + + unsigned int reference_body_id12 = + reference1.AddBody (reference_body_id11, + SpatialTransform(rm2,r2), + Joint(JointTypeRevoluteX), + body13); + + + unsigned int custom_body_id10 = + custom1.AddBody ( 0, + SpatialTransform(), + Joint(JointTypeRevoluteX), + body11); + + unsigned int custom_body_id11 = + custom1.AddBody ( custom_body_id10, + SpatialTransform(rm1,r1), + Joint(JointTypeEulerZYX), + body12); + + unsigned int custom_body_id12 = + custom1.AddBodyCustomJoint ( custom_body_id11, + SpatialTransform(rm2,r2), + &custom_rx_joint1, + body13); + + VectorNd q1 = VectorNd::Zero (reference1.q_size); + VectorNd qdot1 = VectorNd::Zero (reference1.qdot_size); + VectorNd qddot1 = VectorNd::Zero (reference1.qdot_size); + VectorNd tau1 = VectorNd::Zero (reference1.qdot_size); + + int idx = 0; + reference_model.at(idx) = (reference1); + custom_model.at(idx) = (custom1); + + reference_body_id.at(idx).at(0) = (reference_body_id10); + reference_body_id.at(idx).at(1) = (reference_body_id11); + reference_body_id.at(idx).at(2) = (reference_body_id12); + + custom_body_id.at(idx).at(0) = (custom_body_id10); + custom_body_id.at(idx).at(1) = (custom_body_id11); + custom_body_id.at(idx).at(2) = (custom_body_id12); + + body.at(idx).at(0) = (body11); + body.at(idx).at(1) = (body12); + body.at(idx).at(2) = (body13); + custom_joint.at(idx).at(0) = (&custom_rx_joint1); + + q.at(idx) = (q1); + qdot.at(idx) = (qdot1); + qddot.at(idx) = (qddot1); + tau.at(idx) = (tau1); + + + + //======================================================== + //Test Model 2: Rx - custom - multidof3 + //======================================================== + + + Model reference2, custom2; + + unsigned int reference_body_id20 = + reference2.AddBody (0, + SpatialTransform(), + Joint(JointTypeRevoluteX), + body11); + + unsigned int reference_body_id21 = + reference2.AddBody (reference_body_id20, + SpatialTransform(rm2,r2), + Joint(JointTypeRevoluteX), + body12); + + unsigned int reference_body_id22 = + reference2.AddBody (reference_body_id21, + SpatialTransform(rm1,r1), + Joint(JointTypeEulerZYX), + body13); + + unsigned int custom_body_id20 = + custom2.AddBody ( 0, + SpatialTransform(), + Joint(JointTypeRevoluteX), + body11); + + unsigned int custom_body_id21 = + custom2.AddBodyCustomJoint ( custom_body_id20, + SpatialTransform(rm2,r2), + &custom_rx_joint1, + body12); + + unsigned int custom_body_id22 = + custom2.AddBody ( custom_body_id21, + SpatialTransform(rm1,r1), + Joint(JointTypeEulerZYX), + body13); + + + + VectorNd q2 = VectorNd::Zero (reference2.q_size); + VectorNd qdot2 = VectorNd::Zero (reference2.qdot_size); + VectorNd qddot2 = VectorNd::Zero (reference2.qdot_size); + VectorNd tau2 = VectorNd::Zero (reference2.qdot_size); + + idx = 1; + reference_model.at(idx) = (reference2); + custom_model.at(idx) = (custom2); + + reference_body_id.at(idx).at(0) = (reference_body_id20); + reference_body_id.at(idx).at(1) = (reference_body_id21); + reference_body_id.at(idx).at(2) = (reference_body_id22); + + custom_body_id.at(idx).at(0) = (custom_body_id20); + custom_body_id.at(idx).at(1) = (custom_body_id21); + custom_body_id.at(idx).at(2) = (custom_body_id22); + + body.at(idx).at(0) = (body11); + body.at(idx).at(1) = (body12); + body.at(idx).at(2) = (body13); + custom_joint.at(idx).at(0) = (&custom_rx_joint1); + + + q.at(idx) = (q2); + qdot.at(idx) = (qdot2); + qddot.at(idx) = (qddot2); + tau.at(idx) = (tau2); + + //======================================================== + //Test Model 3: custom - multidof3 - Rx + //======================================================== + + Model reference3, custom3; + + unsigned int reference_body_id30 = + reference3.AddBody (0, + SpatialTransform(), + Joint(JointTypeRevoluteX), + body11); + + unsigned int reference_body_id31 = + reference3.AddBody (reference_body_id30, + SpatialTransform(rm1,r1), + Joint(JointTypeEulerZYX), + body12); + + unsigned int reference_body_id32 = + reference3.AddBody (reference_body_id31, + SpatialTransform(rm2,r2), + Joint(JointTypeRevoluteX), + body13); + + unsigned int custom_body_id30 = + custom3.AddBodyCustomJoint ( 0, + SpatialTransform(), + &custom_rx_joint1, + body11); + + unsigned int custom_body_id31 = + custom3.AddBody ( custom_body_id30, + SpatialTransform(rm1,r1), + Joint(JointTypeEulerZYX), + body12); + + unsigned int custom_body_id32 = + custom3.AddBody ( custom_body_id31, + SpatialTransform(rm2,r2), + Joint(JointTypeRevoluteX), + body13); + + VectorNd q3 = VectorNd::Zero (reference3.q_size); + VectorNd qdot3 = VectorNd::Zero (reference3.qdot_size); + VectorNd qddot3 = VectorNd::Zero (reference3.qdot_size); + VectorNd tau3 = VectorNd::Zero (reference3.qdot_size); + + idx = 2; + reference_model.at(idx) = (reference3); + custom_model.at(idx) = (custom3); + + reference_body_id.at(idx).at(0) = (reference_body_id30); + reference_body_id.at(idx).at(1) = (reference_body_id31); + reference_body_id.at(idx).at(2) = (reference_body_id32); + + custom_body_id.at(idx).at(0) = (custom_body_id30); + custom_body_id.at(idx).at(1) = (custom_body_id31); + custom_body_id.at(idx).at(2) = (custom_body_id32); + + body.at(idx).at(0) = (body11); + body.at(idx).at(1) = (body12); + body.at(idx).at(2) = (body13); + custom_joint.at(idx).at(0) = (&custom_rx_joint1); + + q.at(idx) = (q3); + qdot.at(idx) = (qdot3); + qddot.at(idx) = (qddot3); + tau.at(idx) = (tau3); + + } + + /* + ~CustomJointMultiBodyFixture () { + }*/ + + vector < Model > reference_model; + vector < Model > custom_model; + + vector < vector < Body > > body; + vector < vector < CustomJoint* > > custom_joint; + + vector < vector< unsigned int > > reference_body_id; + vector < vector< unsigned int > > custom_body_id; + + vector < VectorNd > q; + vector < VectorNd > qdot; + vector < VectorNd > qddot; + vector < VectorNd > tau; + + CustomJointTypeRevoluteX custom_rx_joint1; + CustomJointTypeRevoluteX custom_rx_joint2; + CustomJointTypeRevoluteX custom_rx_joint3; +}; + + +//============================================================================== +// +// Tests +// UpdateKinematicsCustom +// Jacobians +// InverseDynamics +// CompositeRigidBodyAlgorithm +// ForwardDynamics +// CalcMInvTimestau +// ForwardDynamicsContactsKokkevis +// +//============================================================================== + +TEST_FIXTURE ( CustomJointMultiBodyFixture, UpdateKinematics ) { + + VectorNd test; + + for(int idx =0; idx < NUMBER_OF_MODELS; ++idx){ + + int dof = reference_model.at(idx).dof_count; + for (unsigned int i = 0; i < dof ; i++) { + q.at(idx)[i] = i * 0.1; + qdot.at(idx)[i] = i * 0.15; + qddot.at(idx)[i] = i * 0.17; + } + + UpdateKinematics (reference_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + UpdateKinematics (custom_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + + Matrix3d Eref = reference_model.at(idx).X_base[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].E; + Matrix3d Ecus = custom_model.at(idx).X_base[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].E; + + Matrix3d Eerr = Eref-Ecus; + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).X_base[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].E.data(), + custom_model.at(idx).X_base[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].E.data(), + 9, + TEST_PREC); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).v[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + custom_model.at(idx).v[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + 6, + TEST_PREC); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).a[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + custom_model.at(idx).a[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + 6, + TEST_PREC); + } +} + +TEST_FIXTURE (CustomJointMultiBodyFixture, UpdateKinematicsCustom) { + + for(int idx =0; idx < NUMBER_OF_MODELS; ++idx){ + int dof = reference_model.at(idx).dof_count; + for (unsigned int i = 0; i < dof; i++) { + q.at(idx)[i] = i * 9.133758561390194e-01; + qdot.at(idx)[i] = i * 6.323592462254095e-01; + qddot.at(idx)[i] = i * 9.754040499940952e-02; + } + + UpdateKinematicsCustom (reference_model.at(idx), + &q.at(idx), NULL, NULL); + UpdateKinematicsCustom (custom_model.at(idx), + &q.at(idx), NULL, NULL); + + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).X_base[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].E.data(), + custom_model.at(idx).X_base[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].E.data(), + 9, + TEST_PREC); + + + //velocity + UpdateKinematicsCustom (reference_model.at(idx), + &q.at(idx), + &qdot.at(idx), NULL); + UpdateKinematicsCustom (custom_model.at(idx), + &q.at(idx), + &qdot.at(idx), NULL); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).v[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + custom_model.at(idx).v[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + 6, + TEST_PREC); + + + //All + UpdateKinematicsCustom (reference_model.at(idx), + &q.at(idx), + &qdot.at(idx), + &qddot.at(idx)); + + UpdateKinematicsCustom (custom_model.at(idx), + &q.at(idx), + &qdot.at(idx), + &qddot.at(idx)); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).a[ + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + custom_model.at(idx).a[ + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1) + ].data(), + 6, + TEST_PREC); + } + + +} + +TEST_FIXTURE (CustomJointMultiBodyFixture, Jacobians) { + + for(int idx = 0; idx < NUMBER_OF_MODELS; ++idx){ + int dof = reference_model.at(idx).dof_count; + + for (unsigned int i = 0; i < dof; i++) { + q.at(idx)[i] = i * 9.133758561390194e-01; + qdot.at(idx)[i] = i * 6.323592462254095e-01; + qddot.at(idx)[i] = i * 9.754040499940952e-02; + } + + //position + UpdateKinematics (reference_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + UpdateKinematics (custom_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + //Check the Spatial Jacobian + MatrixNd Gref = MatrixNd( + MatrixNd::Zero(6,reference_model.at(idx).dof_count)); + + MatrixNd Gcus = MatrixNd( + MatrixNd::Zero(6,reference_model.at(idx).dof_count)); + + CalcBodySpatialJacobian ( reference_model.at(idx), + q.at(idx), + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1), + Gref); + + CalcBodySpatialJacobian ( custom_model.at(idx), + q.at(idx), + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1), + Gcus); + + for(int i=0; i<6;++i){ + for(int j=0; j 1){ + + for (unsigned int i = 0; i < dof; i++) { + q.at(idx)[i] = (i+0.1) * 9.133758561390194e-01; + qdot.at(idx)[i] = (i+0.1) * 6.323592462254095e-01; + tau.at(idx)[i] = (i+0.1) * 9.754040499940952e-02; + } + + VectorNd qddot_ref = VectorNd::Zero(dof); + VectorNd qddot_cus = VectorNd::Zero(dof); + + VectorNd qdot_plus_ref = VectorNd::Zero(dof); + VectorNd qdot_plus_cus = VectorNd::Zero(dof); + + Vector3d contact_point ( 0., 1., 0.); + + ConstraintSet constraint_set_ref; + ConstraintSet constraint_set_cus; + + + //Reference + constraint_set_ref.AddContactConstraint( + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1), + contact_point, + Vector3d (1., 0., 0.), + "ground_x"); + + constraint_set_ref.AddContactConstraint( + reference_body_id.at(idx).at(NUMBER_OF_BODIES-1), + contact_point, + Vector3d (0., 1., 0.), + "ground_y"); + + constraint_set_ref.Bind (reference_model.at(idx)); + + //Custom + constraint_set_cus.AddContactConstraint( + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1), + contact_point, + Vector3d (1., 0., 0.), + "ground_x"); + + constraint_set_cus.AddContactConstraint( + custom_body_id.at(idx).at(NUMBER_OF_BODIES-1), + contact_point, + Vector3d (0., 1., 0.), + "ground_y"); + + constraint_set_cus.Bind (custom_model.at(idx)); + + ComputeConstraintImpulsesDirect(reference_model.at(idx), + q.at(idx), + qdot.at(idx), + constraint_set_ref, + qdot_plus_ref); + + ForwardDynamicsContactsKokkevis (reference_model.at(idx), + q.at(idx), + qdot_plus_ref, + tau.at(idx), + constraint_set_ref, + qddot_ref); + + ComputeConstraintImpulsesDirect(custom_model.at(idx), + q.at(idx), + qdot.at(idx), + constraint_set_cus, + qdot_plus_cus); + + ForwardDynamicsContactsKokkevis (custom_model.at(idx), + q.at(idx), + qdot_plus_cus, + tau.at(idx), + constraint_set_cus, + qddot_cus); + + VectorNd qdot_plus_error = qdot_plus_ref - qdot_plus_cus; + VectorNd qddot_error = qddot_ref - qddot_cus; + + CHECK_ARRAY_CLOSE (qdot_plus_ref.data(), + qdot_plus_cus.data(), + dof, + TEST_PREC); + + CHECK_ARRAY_CLOSE (qddot_ref.data(), + qddot_cus.data(), + dof, + TEST_PREC); + } + } + +} + +// +//Completed? +// x : implement test for UpdateKinematicsCustom +// x : implement test for Jacobians +// x : implement test for InverseDynamics +// x : implement test for CompositeRigidBodyAlgorithm +// x : implement test for ForwardDynamics +// x : implement test for CalcMInvTimestau +// x : implement test for ForwardDynamicsContactsKokkevis diff --git a/3rdparty/rbdl/tests/CustomJointSingleBodyTests.cc b/3rdparty/rbdl/tests/CustomJointSingleBodyTests.cc new file mode 100644 index 0000000..4f00573 --- /dev/null +++ b/3rdparty/rbdl/tests/CustomJointSingleBodyTests.cc @@ -0,0 +1,868 @@ +/* + * RBDL - Rigid Body Dynamics Library + * Copyright (c) 2011-2016 Martin Felis + * Copyright (c) 2016 Matthew Millard + */ + + +#include + +#include + +#include "Fixtures.h" +#include "Human36Fixture.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Constraints.h" +#include + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 2.0e-12; +const int NUMBER_OF_MODELS = 2; + +//============================================================================== +/* + + The purpose of this test is to test that all of the code in RBDL + related to a single CustomJoint functions. To do this we will implement + joints that already exist in RBDL but using the CustomJoint interface. The + test will then numerically compare the results produced by the CustomJoint + and the equivalent built-in joint in RBDL. The following algorithms will + be tested: + + UpdateKinematicsCustom + Jacobians + InverseDynamics + CompositeRigidBodyAlgorithm + ForwardDynamics + CalcMInvTimestau + ForwardDynamicsContactsKokkevis + +*/ +//============================================================================== +//============================================================================== +/* + As a note, below are the basic fields and functions that every CustomJoint + class member must provide. Refer to Featherstone's dynamics text if the field + names below don't make sense to you. + + 1. Extend from CustomJoint: + + struct CustomJointClass: public CustomJoint + + 2. Make a default constructor, and initialize member variables + mDoFCount + S + d_u + + e.g. + + CustomJointClass() + + 3. Implement the method jcalc. This method must populate X_J, v_J, c_J, and S. + + virtual void jcalc + model.X_J[joint_id] + model.v_J + model.c_J + model.mCustomJoints[joint.custom_joint_index]->S = S + + 4. Implement the method jcalc_X_lambda_S. This method must populate X_lambda + and S. + + virtual void jcalc_X_lambda_S + model.X_lambda + model.mCustomJoints[joint.custom_joint_index]->S = S; + + */ +//============================================================================== +//Custom Joint Code +//============================================================================== +struct CustomJointTypeRevoluteX : public CustomJoint +{ + CustomJointTypeRevoluteX(){ + mDoFCount = 1; + S = MatrixNd::Zero(6,1); + S(0, 0) = 1.0; + d_u = MatrixNd::Zero(mDoFCount,1); + } + + virtual void jcalc (Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot) + { + model.X_J[joint_id] = Xrotx(q[model.mJoints[joint_id].q_index]); + model.v_J[joint_id][0] = qdot[model.mJoints[joint_id].q_index]; + } + + virtual void jcalc_X_lambda_S ( Model &model, + unsigned int joint_id, + const Math::VectorNd &q) + { + model.X_lambda[joint_id] = + Xrotx (q[model.mJoints[joint_id].q_index]) + * model.X_T[joint_id]; + + + const Joint &joint = model.mJoints[joint_id]; + model.mCustomJoints[joint.custom_joint_index]->S = S; + + } + +}; + +struct CustomEulerZYXJoint : public CustomJoint +{ + CustomEulerZYXJoint () + { + mDoFCount = 3; + S = MatrixNd::Zero (6,3); + d_u = MatrixNd::Zero(mDoFCount,1); + } + + virtual void jcalc (Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot) + { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_J[joint_id].E = Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ); + + S.setZero(); + S(0,0) = -s1; + S(0,2) = 1.; + + S(1,0) = c1 * s2; + S(1,1) = c2; + + S(2,0) = c1 * c2; + S(2,1) = - s2; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = S * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set( + -c1*qdot0*qdot1, + -s1*s2*qdot0*qdot1 + c1*c2*qdot0*qdot2 - s2*qdot1*qdot2, + -s1*c2*qdot0*qdot1 - c1*s2*qdot0*qdot2 - c2*qdot1*qdot2, + 0., 0., 0. + ); + } + + virtual void jcalc_X_lambda_S ( Model &model, + unsigned int joint_id, + const Math::VectorNd &q) + { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + + model.X_lambda[joint_id] = SpatialTransform ( + Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ), + Vector3d (0., 0., 0.)) * model.X_T[joint_id]; + + S.setZero(); + S(0,0) = -s1; + S(0,2) = 1.; + + S(1,0) = c1 * s2; + S(1,1) = c2; + + S(2,0) = c1 * c2; + S(2,1) = - s2; + + const Joint &joint = model.mJoints[joint_id]; + model.mCustomJoints[joint.custom_joint_index]->S = S; + + //assert (false && "Not yet implemented!"); + } +}; + +//============================================================================== +//Test Fixture +//============================================================================== + +struct CustomJointSingleBodyFixture { + CustomJointSingleBodyFixture () { + + reference_model.resize(NUMBER_OF_MODELS); + custom_model.resize(NUMBER_OF_MODELS); + + body.resize(NUMBER_OF_MODELS); + custom_joint.resize(NUMBER_OF_MODELS); + + reference_body_id.resize(NUMBER_OF_MODELS); + custom_body_id.resize(NUMBER_OF_MODELS); + + q.resize(NUMBER_OF_MODELS); + qdot.resize(NUMBER_OF_MODELS); + qddot.resize(NUMBER_OF_MODELS); + tau.resize(NUMBER_OF_MODELS); + + //======================================================== + //Test Model 0: 3dof rotational custom joint vs. standard. + //======================================================== + + custom_joint0 = CustomEulerZYXJoint(); + + Matrix3d inertia0 = Matrix3d::Identity(3,3); + Body body0 = Body (1., Vector3d (1.1, 1.2, 1.3), inertia0); + + Model reference0, custom0; + + unsigned int reference_body_id0 = + reference0.AddBody ( 0, + SpatialTransform(), + Joint(JointTypeEulerZYX), + body0); + + unsigned int custom_body_id0 = + custom0.AddBodyCustomJoint ( 0, + SpatialTransform(), + &custom_joint0, + body0); + + VectorNd q0 = VectorNd::Zero (reference0.q_size); + VectorNd qdot0 = VectorNd::Zero (reference0.qdot_size); + VectorNd qddot0 = VectorNd::Zero (reference0.qdot_size); + VectorNd tau0 = VectorNd::Zero (reference0.qdot_size); + + reference_model.at(0) = reference0; + custom_model.at(0) = custom0; + + reference_body_id.at(0) = (reference_body_id0); + custom_body_id.at(0) = (custom_body_id0); + + body.at(0) = (body0); + custom_joint.at(0) = (&custom_joint0); + + q.at(0) = (q0); + qdot.at(0) = (qdot0); + qddot.at(0) = (qddot0); + tau.at(0) = (tau0); + + //======================================================== + //Test Model 1: 1 dof rotational custom joint vs. standard. + //======================================================== + + custom_joint1 = CustomJointTypeRevoluteX(); + + Model reference1, custom1; + + unsigned int reference_body_id1 = + reference1.AddBody ( 0, + SpatialTransform(), + Joint(JointTypeRevoluteX), + body0); + + unsigned int custom_body_id1 = + custom1.AddBodyCustomJoint (0, + SpatialTransform(), + &custom_joint1, + body0); + + VectorNd q1 = VectorNd::Zero (reference1.q_size); + VectorNd qdot1 = VectorNd::Zero (reference1.qdot_size); + VectorNd qddot1 = VectorNd::Zero (reference1.qdot_size); + VectorNd tau1 = VectorNd::Zero (reference1.qdot_size); + + reference_model.at(1) = (reference1); + custom_model.at(1) = (custom1); + + reference_body_id.at(1) = (reference_body_id1); + custom_body_id.at(1) = (custom_body_id1); + + body.at(1) = (body0); + custom_joint.at(1) = (&custom_joint1); + + q.at(1) = (q1); + qdot.at(1) = (qdot1); + qddot.at(1) = (qddot1); + tau.at(1) = (tau1); + + } + + /* + ~CustomJointSingleBodyFixture () { + delete reference_model; + delete custom_model; + + delete body; + delete custom_joint; + + delete reference_body_id; + delete custom_body_id; + + delete q; + delete qdot; + delete qddot; + delete tau; + }*/ + + vector < Model > reference_model; + vector < Model > custom_model; + + vector < Body > body; + vector < CustomJoint* > custom_joint; + + vector < unsigned int > reference_body_id; + vector < unsigned int > custom_body_id; + + vector < VectorNd > q; + vector < VectorNd > qdot; + vector < VectorNd > qddot; + vector < VectorNd > tau; + CustomJointTypeRevoluteX custom_joint1; + CustomEulerZYXJoint custom_joint0; + +}; + +//============================================================================== +// +// Tests +// UpdateKinematicsCustom +// Jacobians +// InverseDynamics +// CompositeRigidBodyAlgorithm +// ForwardDynamics +// CalcMInvTimestau +// ForwardDynamicsContactsKokkevis +// +//============================================================================== + +TEST_FIXTURE ( CustomJointSingleBodyFixture, UpdateKinematics ) { + + VectorNd test; + + for(int idx =0; idx < NUMBER_OF_MODELS; ++idx){ + + int dof = reference_model.at(idx).dof_count; + for (unsigned int i = 0; i < dof ; i++) { + q.at(idx)[i] = i * 0.1; + qdot.at(idx)[i] = i * 0.15; + qddot.at(idx)[i] = i * 0.17; + } + + UpdateKinematics (reference_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + UpdateKinematics (custom_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).X_base[reference_body_id.at(idx)].E.data(), + custom_model.at(idx).X_base[ custom_body_id.at(idx)].E.data(), + 9, + TEST_PREC); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).v[reference_body_id.at(idx)].data(), + custom_model.at(idx).v[ custom_body_id.at(idx)].data(), + 6, + TEST_PREC); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).a[reference_body_id.at(idx)].data(), + custom_model.at(idx).a[ custom_body_id.at(idx)].data(), + 6, + TEST_PREC); + } +} + +TEST_FIXTURE (CustomJointSingleBodyFixture, UpdateKinematicsCustom) { + + for(int idx =0; idx < NUMBER_OF_MODELS; ++idx){ + int dof = reference_model.at(idx).dof_count; + for (unsigned int i = 0; i < dof; i++) { + q.at(idx)[i] = i * 9.133758561390194e-01; + qdot.at(idx)[i] = i * 6.323592462254095e-01; + qddot.at(idx)[i] = i * 9.754040499940952e-02; + } + + UpdateKinematicsCustom (reference_model.at(idx), + &q.at(idx), NULL, NULL); + UpdateKinematicsCustom (custom_model.at(idx), + &q.at(idx), NULL, NULL); + + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).X_base[reference_body_id.at(idx)].E.data(), + custom_model.at(idx).X_base[ custom_body_id.at(idx)].E.data(), + 9, + TEST_PREC); + + + //velocity + UpdateKinematicsCustom (reference_model.at(idx), + &q.at(idx), + &qdot.at(idx), + NULL); + UpdateKinematicsCustom (custom_model.at(idx), + &q.at(idx), + &qdot.at(idx), + NULL); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).v[reference_body_id.at(idx)].data(), + custom_model.at(idx).v[ custom_body_id.at(idx)].data(), + 6, + TEST_PREC); + + + //All + UpdateKinematicsCustom (reference_model.at(idx), + &q.at(idx), + &qdot.at(idx), + &qddot.at(idx)); + + UpdateKinematicsCustom (custom_model.at(idx), + &q.at(idx), + &qdot.at(idx), + &qddot.at(idx)); + + CHECK_ARRAY_CLOSE ( + reference_model.at(idx).a[reference_body_id.at(idx)].data(), + custom_model.at(idx).a[ custom_body_id.at(idx)].data(), + 6, + TEST_PREC); + } + + +} + +TEST_FIXTURE (CustomJointSingleBodyFixture, Jacobians) { + + for(int idx =0; idx < NUMBER_OF_MODELS; ++idx){ + int dof = reference_model.at(idx).dof_count; + + for (unsigned int i = 0; i < dof; i++) { + q.at(idx)[i] = i * 9.133758561390194e-01; + qdot.at(idx)[i] = i * 6.323592462254095e-01; + qddot.at(idx)[i] = i * 9.754040499940952e-02; + } + + //position + UpdateKinematics (reference_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + UpdateKinematics (custom_model.at(idx), + q.at(idx), + qdot.at(idx), + qddot.at(idx)); + + //Check the Spatial Jacobian + MatrixNd Gref = MatrixNd( + MatrixNd::Zero(6,reference_model.at(idx).dof_count)); + + MatrixNd Gcus = MatrixNd( + MatrixNd::Zero(6,reference_model.at(idx).dof_count)); + + CalcBodySpatialJacobian ( reference_model.at(idx), + q.at(idx), + reference_body_id.at(idx), + Gref); + + CalcBodySpatialJacobian ( custom_model.at(idx), + q.at(idx), + custom_body_id.at(idx), + Gcus); + + for(int i=0; i<6;++i){ + for(int j=0; j 1){ + + for (unsigned int i = 0; i < dof; i++) { + q.at(idx)[i] = (i+0.1) * 9.133758561390194e-01; + qdot.at(idx)[i] = (i+0.1) * 6.323592462254095e-01; + + tau.at(idx)[i] = (i+0.1) * 9.754040499940952e-02; + } + + VectorNd qddot_ref = VectorNd::Zero(dof); + VectorNd qddot_cus = VectorNd::Zero(dof); + + VectorNd qdot_plus_ref = VectorNd::Zero(dof); + VectorNd qdot_plus_cus = VectorNd::Zero(dof); + + Vector3d contact_point ( 0., 1., 0.); + + ConstraintSet constraint_set_ref; + ConstraintSet constraint_set_cus; + + //Reference + constraint_set_ref.AddContactConstraint( reference_body_id.at(idx), + contact_point, + Vector3d (1., 0., 0.), + "ground_x"); + + constraint_set_ref.AddContactConstraint( reference_body_id.at(idx), + contact_point, + Vector3d (0., 1., 0.), + "ground_y"); + + constraint_set_ref.Bind (reference_model.at(idx)); + + //Custom + constraint_set_cus.AddContactConstraint( custom_body_id.at(idx), + contact_point, + Vector3d (1., 0., 0.), + "ground_x"); + + constraint_set_cus.AddContactConstraint( custom_body_id.at(idx), + contact_point, + Vector3d (0., 1., 0.), + "ground_y"); + + constraint_set_cus.Bind (custom_model.at(idx)); + + ComputeConstraintImpulsesDirect(reference_model.at(idx), + q.at(idx), + qdot.at(idx), + constraint_set_ref, + qdot_plus_ref); + + ForwardDynamicsContactsKokkevis (reference_model.at(idx), + q.at(idx), + qdot_plus_ref, + tau.at(idx), + constraint_set_ref, + qddot_ref); + + ComputeConstraintImpulsesDirect(custom_model.at(idx), + q.at(idx), + qdot.at(idx), + constraint_set_cus, + qdot_plus_cus); + + ForwardDynamicsContactsKokkevis (custom_model.at(idx), + q.at(idx), + qdot_plus_cus, + tau.at(idx), + constraint_set_cus, + qddot_cus); + + VectorNd qdot_plus_error = qdot_plus_ref - qdot_plus_cus; + VectorNd qddot_error = qddot_ref - qddot_cus; + + CHECK_ARRAY_CLOSE (qdot_plus_ref.data(), + qdot_plus_cus.data(), + dof, + TEST_PREC); + + CHECK_ARRAY_CLOSE (qddot_ref.data(), + qddot_cus.data(), + dof, + TEST_PREC); + } + } + +} + + diff --git a/3rdparty/rbdl/tests/CustomJointTests.cc b/3rdparty/rbdl/tests/CustomJointTests.cc new file mode 100644 index 0000000..9f19344 --- /dev/null +++ b/3rdparty/rbdl/tests/CustomJointTests.cc @@ -0,0 +1,137 @@ +#include + +#include + +#include "Fixtures.h" +#include "Human36Fixture.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Constraints.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-12; + +struct CustomEulerZYXJoint : public CustomJoint { + CustomEulerZYXJoint () { + mDoFCount = 3; + S = MatrixNd::Zero (6,3); + }; + + virtual void jcalc (Model &model, + unsigned int joint_id, + const Math::VectorNd &q, + const Math::VectorNd &qdot + ) { + double q0 = q[model.mJoints[joint_id].q_index]; + double q1 = q[model.mJoints[joint_id].q_index + 1]; + double q2 = q[model.mJoints[joint_id].q_index + 2]; + + double s0 = sin (q0); + double c0 = cos (q0); + double s1 = sin (q1); + double c1 = cos (q1); + double s2 = sin (q2); + double c2 = cos (q2); + + model.X_J[joint_id].E = Matrix3d( + c0 * c1, s0 * c1, -s1, + c0 * s1 * s2 - s0 * c2, s0 * s1 * s2 + c0 * c2, c1 * s2, + c0 * s1 * c2 + s0 * s2, s0 * s1 * c2 - c0 * s2, c1 * c2 + ); + + S(0,0) = -s1; + S(0,2) = 1.; + + S(1,0) = c1 * s2; + S(1,1) = c2; + + S(2,0) = c1 * c2; + S(2,1) = - s2; + + double qdot0 = qdot[model.mJoints[joint_id].q_index]; + double qdot1 = qdot[model.mJoints[joint_id].q_index + 1]; + double qdot2 = qdot[model.mJoints[joint_id].q_index + 2]; + + model.v_J[joint_id] = S * Vector3d (qdot0, qdot1, qdot2); + + model.c_J[joint_id].set( + - c1 * qdot0 * qdot1, + -s1 * s2 * qdot0 * qdot1 + c1 * c2 * qdot0 * qdot2 - s2 * qdot1 * qdot2, + -s1 * c2 * qdot0 * qdot1 - c1 * s2 * qdot0 * qdot2 - c2 * qdot1 * qdot2, + 0., 0., 0. + ); + } + virtual void jcalc_X_lambda_S (Model &model, + unsigned int joint_id, + const Math::VectorNd &q + ) { + // TODO + assert (false && "Not yet implemented!"); + } +}; + +struct CustomJointFixture { + CustomJointFixture () { + custom_joint = new CustomEulerZYXJoint(); + + Matrix3d inertia = Matrix3d::Identity(3,3); + body = Body (1., Vector3d (1.1, 1.2, 1.3), inertia); + reference_body_id = reference_model.AddBody (0,SpatialTransform(), Joint(JointTypeEulerZYX), body); + custom_body_id = custom_model.AddBodyCustomJoint (0, SpatialTransform(), custom_joint, body); + + q = VectorNd::Zero (reference_model.q_size); + qdot = VectorNd::Zero (reference_model.qdot_size); + qddot = VectorNd::Zero (reference_model.qdot_size); + tau = VectorNd::Zero (reference_model.qdot_size); + } + + ~CustomJointFixture () { + delete custom_joint; + } + + Model reference_model; + Model custom_model; + + Body body; + CustomJoint* custom_joint; + + unsigned int reference_body_id; + unsigned int custom_body_id; + + VectorNd q; + VectorNd qdot; + VectorNd qddot; + VectorNd tau; +}; + +TEST_FIXTURE ( CustomJointFixture, UpdateKinematics ) { + for (unsigned int i = 0; i < 3; i++) { + q[i] = i * 0.1; + qdot[i] = i * 0.15; + qddot[i] = i * 0.17; + } + + UpdateKinematics (reference_model, q, qdot, qddot); + UpdateKinematics (custom_model, q, qdot, qddot); + + CHECK_ARRAY_EQUAL (reference_model.X_base[reference_body_id].E.data(), custom_model.X_base[custom_body_id].E.data(), 9); + + CHECK_ARRAY_EQUAL (reference_model.v[reference_body_id].data(), custom_model.v[custom_body_id].data(), 6); + + CHECK_ARRAY_EQUAL (reference_model.a[reference_body_id].data(), custom_model.a[custom_body_id].data(), 6); +} + +// TODO: implement test for UpdateKinematicsCustom +// TODO: implement test for Jacobians +// TODO: implement test for InverseDynamics +// TODO: implement test for CompositeRigidBodyAlgorithm +// TODO: implement test for ForwardDynamics +// TODO: implement test for CalcMInvTimestau +// TODO: implement test for ForwardDynamicsContacts diff --git a/3rdparty/rbdl/tests/DynamicsTests.cc b/3rdparty/rbdl/tests/DynamicsTests.cc new file mode 100644 index 0000000..2ab0a33 --- /dev/null +++ b/3rdparty/rbdl/tests/DynamicsTests.cc @@ -0,0 +1,715 @@ +#include + +#include +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Constraints.h" + +#include "Fixtures.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-13; + +struct DynamicsFixture { + DynamicsFixture () { + ClearLogOutput(); + model = new Model; + model->gravity = Vector3d (0., -9.81, 0.); + } + ~DynamicsFixture () { + delete model; + } + Model *model; +}; + +TEST_FIXTURE(DynamicsFixture, TestCalcDynamicSingleChain) { + Body body(1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint, body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a[i] << endl; + } + + CHECK_EQUAL (-4.905, QDDot[0]); +} + +TEST_FIXTURE(DynamicsFixture, TestCalcDynamicSpatialInertiaSingleChain) { + // This function checks the value for a non-trivial spatial inertia + Body body(1., Vector3d (1.5, 1., 1.), Vector3d (1., 2., 3.)); + + Joint joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint, body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a[i] << endl; + } + + CHECK_EQUAL (-2.3544, QDDot[0]); +} + +TEST_FIXTURE(DynamicsFixture, TestCalcDynamicDoubleChain) { + Body body_a (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_a ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + Body body_b (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_b ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + +// cout << "--- Double Chain ---" << endl; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a[i] << endl; + } + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE (-5.88600000000000E+00, QDDot[0], TEST_PREC); + CHECK_CLOSE ( 3.92400000000000E+00, QDDot[1], TEST_PREC); +} + +TEST_FIXTURE(DynamicsFixture, TestCalcDynamicTripleChain) { + Body body_a (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_a ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + Body body_b (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_b ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + Body body_c (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_c ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(2, Xtrans(Vector3d(1., 0., 0.)), joint_c, body_c); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + // cout << "--- Triple Chain ---" << endl; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a[i] << endl; + } + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE (-6.03692307692308E+00, QDDot[0], TEST_PREC); + CHECK_CLOSE ( 3.77307692307692E+00, QDDot[1], TEST_PREC); + CHECK_CLOSE ( 1.50923076923077E+00, QDDot[2], TEST_PREC); +} + +TEST_FIXTURE(DynamicsFixture, TestCalcDynamicDoubleChain3D) { + Body body_a (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_a ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + Body body_b (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + Joint joint_b ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + // cout << "--- Double Chain 3D ---" << endl; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a[i] << endl; + } + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE (-3.92400000000000E+00, QDDot[0], TEST_PREC); + CHECK_CLOSE ( 0.00000000000000E+00, QDDot[1], TEST_PREC); +} + +TEST_FIXTURE(DynamicsFixture, TestCalcDynamicSimpleTree3D) { + Body body_a (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + Joint joint_a ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + Body body_b1 (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + Joint joint_b1 ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b1, body_b1); + + Body body_c1 (1., Vector3d (0., 0., 1.), Vector3d (1., 1., 1.)); + Joint joint_c1 ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + model->AddBody(2, Xtrans(Vector3d(0., 1., 0.)), joint_c1, body_c1); + + Body body_b2 (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + Joint joint_b2 ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + model->AddBody(1, Xtrans(Vector3d(-0.5, 0., 0.)), joint_b2, body_b2); + + Body body_c2 (1., Vector3d (0., 0., 1.), Vector3d (1., 1., 1.)); + Joint joint_c2 ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + model->AddBody(4, Xtrans(Vector3d(0., -0.5, 0.)), joint_c2, body_c2); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + // cout << "--- SimpleTree ---" << endl; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a[i] << endl; + } + + // cout << LogOutput.str() << endl; + + CHECK_CLOSE (-1.60319066147860E+00, QDDot[0], TEST_PREC); + CHECK_CLOSE (-5.34396887159533E-01, QDDot[1], TEST_PREC); + CHECK_CLOSE ( 4.10340466926070E+00, QDDot[2], TEST_PREC); + CHECK_CLOSE ( 2.67198443579767E-01, QDDot[3], TEST_PREC); + CHECK_CLOSE ( 5.30579766536965E+00, QDDot[4], TEST_PREC); +} + +TEST (TestForwardDynamicsLagrangian) { + Model model; + Body base_body(1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + + model.AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Zero (model.dof_count); + VectorNd QDot = VectorNd::Zero (model.dof_count); + VectorNd Tau = VectorNd::Zero (model.dof_count); + + VectorNd QDDot_aba = VectorNd::Zero (model.dof_count); + VectorNd QDDot_lagrangian = VectorNd::Zero (model.dof_count); + + Q[0] = 1.1; + Q[1] = 1.2; + Q[2] = 1.3; + Q[3] = 0.1; + Q[4] = 0.2; + Q[5] = 0.3; + + QDot[0] = 1.1; + QDot[1] = -1.2; + QDot[2] = 1.3; + QDot[3] = -0.1; + QDot[4] = 0.2; + QDot[5] = -0.3; + + Tau[0] = 2.1; + Tau[1] = 2.2; + Tau[2] = 2.3; + Tau[3] = 1.1; + Tau[4] = 1.2; + Tau[5] = 1.3; + + ForwardDynamics(model, Q, QDot, Tau, QDDot_aba); + ForwardDynamicsLagrangian(model, Q, QDot, Tau, QDDot_lagrangian); + + CHECK_EQUAL (QDDot_aba.size(), QDDot_lagrangian.size()); + CHECK_ARRAY_CLOSE (QDDot_aba.data(), QDDot_lagrangian.data(), QDDot_aba.size(), TEST_PREC); +} + +/* + * A simple test for a model with 3 rotational dof. The reference value was + * computed with Featherstones spatial_v1 code. This test was written + * because my benchmark tool showed up inconsistencies, however this was + * due to the missing gravity term. But as the test now works, I just leave + * it here. + */ +TEST (TestForwardDynamics3DoFModel) { + Model model; + + model.gravity = Vector3d (0., -9.81, 0.); + + Body null_body (0., Vector3d(0., 0., 0.), Vector3d (0., 0., 0.)); + Body base_body (1., Vector3d(0., 0.5, 0.), Vector3d (1., 1., 1.)); + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + Joint joint_rot_y ( SpatialVector (0., 1., 0., 0., 0., 0.)); + Joint joint_rot_x ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + unsigned int base_id_rot_z, base_id_rot_y; + + // we can reuse both bodies and joints as they are copied + base_id_rot_z = model.AddBody (0, Xtrans (Vector3d(0., 0., 0.)), joint_rot_z, null_body); + base_id_rot_y = model.AddBody (base_id_rot_z, Xtrans (Vector3d(0., 0., 0.)), joint_rot_y, null_body); + model.AddBody (base_id_rot_y, Xtrans (Vector3d(0., 0., 0.)), joint_rot_x, base_body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model.dof_count, 0.); + + VectorNd QDDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDDot_ref = VectorNd::Constant ((size_t) model.dof_count, 0.); + + Q[0] = 1.; + + ClearLogOutput(); + + ForwardDynamics (model, Q, QDot, Tau, QDDot); + +// cout << LogOutput.str() << endl; + + QDDot_ref[0] = 3.301932144386186; + + CHECK_ARRAY_CLOSE (QDDot_ref.data(), QDDot.data(), QDDot.size(), TEST_PREC); +} + +/* + * Another simple 3 dof model test which showed some problems when + * computing forward dynamics with the Lagrangian formulation. A proplem + * occured as the CRBA does not update the kinematics of the model, hence + * invalid body transformations and joint axis were used in the CRBA. + * Running the CRBA after the InverseDynamics calculation fixes this. This + * test ensures that the error does not happen when calling ForwardLagrangian. + */ +TEST (TestForwardDynamics3DoFModelLagrangian) { + Model model; + + model.gravity = Vector3d (0., -9.81, 0.); + + Body null_body (0., Vector3d(0., 0., 0.), Vector3d (0., 0., 0.)); + Body base_body (1., Vector3d(0., 0.5, 0.), Vector3d (1., 1., 1.)); + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + Joint joint_rot_y ( SpatialVector (0., 1., 0., 0., 0., 0.)); + Joint joint_rot_x ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + unsigned int base_id_rot_z, base_id_rot_y; + + // we can reuse both bodies and joints as they are copied + base_id_rot_z = model.AddBody (0, Xtrans (Vector3d(0., 0., 0.)), joint_rot_z, null_body); + base_id_rot_y = model.AddBody (base_id_rot_z, Xtrans (Vector3d(0., 0., 0.)), joint_rot_y, null_body); + model.AddBody (base_id_rot_y, Xtrans (Vector3d(0., 0., 0.)), joint_rot_x, base_body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model.dof_count, 0.); + + VectorNd QDDot_ab = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDDot_lagrangian = VectorNd::Constant ((size_t) model.dof_count, 0.); + + Q[1] = 1.; + ClearLogOutput(); + + Q[0] = 0.; + Q[1] = 1.; + Q[2] = 0.; + ForwardDynamicsLagrangian (model, Q, QDot, Tau, QDDot_lagrangian); + Q[0] = 0.; + Q[1] = 0.; + Q[2] = 1.; + ForwardDynamicsLagrangian (model, Q, QDot, Tau, QDDot_lagrangian); + ForwardDynamics (model, Q, QDot, Tau, QDDot_ab); + +// cout << QDDot_lagrangian << endl; +// cout << LogOutput.str() << endl; + + CHECK_ARRAY_CLOSE (QDDot_ab.data(), QDDot_lagrangian.data(), QDDot_ab.size(), TEST_PREC); +} + +/* + * This is a test for a model where I detected incosistencies between the + * Lagragian method and the ABA. + */ +TEST (TestForwardDynamicsTwoLegModelLagrangian) { + Model *model = NULL; + + unsigned int hip_id, + upper_leg_right_id, + lower_leg_right_id, + foot_right_id, + upper_leg_left_id, + lower_leg_left_id, + foot_left_id; + Body hip_body, + upper_leg_right_body, + lower_leg_right_body, + foot_right_body, + upper_leg_left_body, + lower_leg_left_body, + foot_left_body; + + Joint joint_rot_z, joint_rot_y, joint_rot_x; + Joint joint_trans_z, joint_trans_y, joint_trans_x; + + ConstraintSet CS_right; + ConstraintSet CS_left; + ConstraintSet CS_both; + + model = new Model(); + + model->gravity = Vector3d (0., -9.81, 0.); + + joint_rot_z = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + joint_rot_y = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + joint_rot_x = Joint ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + joint_trans_z = Joint ( SpatialVector (0., 0., 0., 0., 0., 1.)); + joint_trans_y = Joint ( SpatialVector (0., 0., 0., 0., 1., 0.)); + joint_trans_x = Joint ( SpatialVector (0., 0., 0., 1., 0., 0.)); + + Body null_body (0., Vector3d (0., 0., 0.), Vector3d (0., 0., 0.)); + + // hip + hip_body = Body (1., Vector3d (0., 0., 0.), Vector3d (1., 1., 1.)); + + // lateral right + upper_leg_right_body = Body (1., Vector3d (0., -0.25, 0.), Vector3d (1., 1., 1.)); + lower_leg_right_body = Body (1., Vector3d (0., -0.25, 0.), Vector3d (1., 1., 1.)); + foot_right_body = Body (1., Vector3d (0.15, -0.1, 0.), Vector3d (1., 1., 1.)); + + // lateral left + upper_leg_left_body = Body (1., Vector3d (0., -0.25, 0.), Vector3d (1., 1., 1.)); + lower_leg_left_body = Body (1., Vector3d (0., -0.25, 0.), Vector3d (1., 1., 1.)); + foot_left_body = Body (1., Vector3d (0.15, -0.1, 0.), Vector3d (1., 1., 1.)); + + // temporary value to store most recent body id + unsigned int temp_id; + + // add hip to the model (planar, 3 DOF) + temp_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_trans_x, null_body); + temp_id = model->AddBody (temp_id, Xtrans (Vector3d (0., 0., 0.)), joint_trans_y, null_body); + hip_id = model->AddBody (temp_id, Xtrans (Vector3d (0., 0., 0.)), joint_rot_z, hip_body); + + // + // right leg + // + + // add right upper leg + temp_id = model->AddBody (hip_id, Xtrans (Vector3d(0., 0., 0.)), joint_rot_z, upper_leg_right_body); + upper_leg_right_id = temp_id; + + // add the right lower leg (only one DOF) + temp_id = model->AddBody (temp_id, Xtrans (Vector3d(0., -0.5, 0.)), joint_rot_z, lower_leg_right_body); + lower_leg_right_id = temp_id; + + // add the right foot (1 DOF) + temp_id = model->AddBody (temp_id, Xtrans (Vector3d(0., -0.5, 0.)), joint_rot_z, foot_right_body); + foot_right_id = temp_id; + + // + // left leg + // + + // add left upper leg + temp_id = model->AddBody (hip_id, Xtrans (Vector3d(0., 0., 0.)), joint_rot_z, upper_leg_left_body); + upper_leg_left_id = temp_id; + + // add the left lower leg (only one DOF) + temp_id = model->AddBody (temp_id, Xtrans (Vector3d(0., -0.5, 0.)), joint_rot_z, lower_leg_left_body); + lower_leg_left_id = temp_id; + + // add the left foot (1 DOF) + temp_id = model->AddBody (temp_id, Xtrans (Vector3d(0., -0.5, 0.)), joint_rot_z, foot_left_body); + foot_left_id = temp_id; + + LOG << "--- model created (" << model->dof_count << " DOF) ---" << endl; + + // contact data + CS_right.AddContactConstraint(foot_right_id, Vector3d (0., 0., 0.), Vector3d (1., 0., 0.), "foot_right_x"); + CS_right.AddContactConstraint(foot_right_id, Vector3d (0., 0., 0.), Vector3d (0., 1., 0.), "foot_right_y"); + CS_right.AddContactConstraint(foot_right_id, Vector3d (0., 0., 0.), Vector3d (0., 0., 1.), "foot_right_z"); + + CS_left.AddContactConstraint(foot_left_id, Vector3d (0., 0., 0.), Vector3d (1., 0., 0.), "foot_left_x"); + CS_left.AddContactConstraint(foot_left_id, Vector3d (0., 0., 0.), Vector3d (0., 1., 0.), "foot_left_y"); + CS_left.AddContactConstraint(foot_left_id, Vector3d (0., 0., 0.), Vector3d (0., 0., 1.), "foot_left_z"); + + CS_both.AddContactConstraint(foot_right_id, Vector3d (0., 0., 0.), Vector3d (1., 0., 0.), "foot_right_x"); + CS_both.AddContactConstraint(foot_right_id, Vector3d (0., 0., 0.), Vector3d (0., 1., 0.), "foot_right_y"); + CS_both.AddContactConstraint(foot_right_id, Vector3d (0., 0., 0.), Vector3d (0., 0., 1.), "foot_right_z"); + CS_both.AddContactConstraint(foot_left_id, Vector3d (0., 0., 0.), Vector3d (1., 0., 0.), "foot_left_x"); + CS_both.AddContactConstraint(foot_left_id, Vector3d (0., 0., 0.), Vector3d (0., 1., 0.), "foot_left_y"); + CS_both.AddContactConstraint(foot_left_id, Vector3d (0., 0., 0.), Vector3d (0., 0., 1.), "foot_left_z"); + + CS_right.Bind(*model); + CS_left.Bind(*model); + CS_both.Bind(*model); + + VectorNd Q(model->dof_count); + VectorNd QDot(model->dof_count); + VectorNd Tau(model->dof_count); + VectorNd QDDot(model->dof_count); + VectorNd QDDotABA(model->dof_count); + + Q[0] = 0.8; + Q[1] = -7.76326e-06; + Q[2] = -1.58205e-07; + Q[3] = 1.57391e-07; + Q[4] = -1.03029e-09; + Q[5] = 7.92143e-08; + Q[6] = 1.57391e-07; + Q[7] = -1.03029e-09; + Q[8] = 7.92143e-08; + + QDot[0] = -1.77845e-06; + QDot[1] = -0.00905283; + QDot[2] = -0.000184484; + QDot[3] = 0.000183536; + QDot[4] = -1.20144e-06; + QDot[5] = 9.23727e-05; + QDot[6] = 0.000183536; + QDot[7] = -1.20144e-06; + QDot[8] = 9.23727e-05; + + Tau[0] = 0; + Tau[1] = 0; + Tau[2] = 0; + Tau[3] = 0.1; + Tau[4] = 0.1; + Tau[5] = 0.1; + Tau[6] = 0.1; + Tau[7] = 0.1; + Tau[8] = 0.1; + + // QDDot = 6.31843e-07 -6.12442e-07 9.22595e-14 3.3712e-07 4.27368e-07 -7.91795e-07 3.3712e-07 4.27368e-07 -7.91795e-07 + // QDDAB = -0.00192794 -9.81419 -0.2 0.198972 -0.00130243 0.100141 0.198972 -0.00130243 0.100141 + + ForwardDynamics (*model, Q, QDot, Tau, QDDotABA); + ClearLogOutput(); + ForwardDynamicsLagrangian (*model, Q, QDot, Tau, QDDot); + +// cout << LogOutput.str() << endl; + + // run it again to make sure the calculations give the same results and + // no invalid state information lingering in the model structure is being used + ForwardDynamics (*model, Q, QDot, Tau, QDDotABA); + ClearLogOutput(); + ForwardDynamicsLagrangian (*model, Q, QDot, Tau, QDDot); + + CHECK_ARRAY_CLOSE (QDDotABA.data(), QDDot.data(), QDDotABA.size(), TEST_PREC); + + delete model; +} + +TEST_FIXTURE(FixedAndMovableJoint, TestForwardDynamicsFixedJoint) { + Q_fixed[0] = 1.1; + Q_fixed[1] = 2.2; + + QDot_fixed[0] = -3.2; + QDot_fixed[1] = -2.3; + + Tau_fixed[0] = 1.2; + Tau_fixed[1] = 2.1; + + Q = CreateDofVectorFromReducedVector (Q_fixed); + QDot = CreateDofVectorFromReducedVector (QDot_fixed); + + QDDot.setZero(); + + InverseDynamics (*model_movable, Q, QDot, QDDot, C_movable); + CompositeRigidBodyAlgorithm (*model_movable, Q, H_movable); + + H_fixed = CreateReducedInertiaMatrix (H_movable); + + C_fixed[0] = C_movable[0]; + C_fixed[1] = C_movable[2]; + + VectorNd QDDot_fixed_emulate(2); + CHECK (LinSolveGaussElimPivot (H_fixed, C_fixed * -1. + Tau_fixed, QDDot_fixed_emulate)); + + ForwardDynamics (*model_fixed, Q_fixed, QDot_fixed, Tau_fixed, QDDot_fixed); + + CHECK_ARRAY_CLOSE (QDDot_fixed_emulate.data(), QDDot_fixed.data(), 2, TEST_PREC); +} + +TEST_FIXTURE(FixedAndMovableJoint, TestInverseDynamicsFixedJoint) { + Q_fixed[0] = 1.1; + Q_fixed[1] = 2.2; + + QDot_fixed[0] = -3.2; + QDot_fixed[1] = -2.3; + + QDDot_fixed[0] = 1.2; + QDDot_fixed[1] = 2.1; + + Q = CreateDofVectorFromReducedVector (Q_fixed); + QDot = CreateDofVectorFromReducedVector (QDot_fixed); + QDDot = CreateDofVectorFromReducedVector (QDDot_fixed); + + InverseDynamics (*model_movable, Q, QDot, QDDot, Tau); + InverseDynamics (*model_fixed, Q_fixed, QDot_fixed, QDDot_fixed, Tau_fixed); + + VectorNd Tau_2dof (2); + Tau_2dof[0] = Tau[0]; + Tau_2dof[1] = Tau[2]; + + CHECK_ARRAY_CLOSE (Tau_2dof.data(), Tau_fixed.data(), 2, TEST_PREC); +} + +TEST_FIXTURE ( FloatingBase12DoF, TestForwardDynamicsLagrangianPrealloc ) { + for (unsigned int i = 0; i < model->dof_count; i++) { + Q[i] = static_cast(i + 1) * 0.1; + QDot[i] = static_cast(i + 1) * 1.1; + Tau[i] = static_cast(i + 1) * -1.2; + } + + ForwardDynamicsLagrangian (*model, + Q, + QDot, + Tau, + QDDot, + Math::LinearSolverPartialPivLU, + NULL, + NULL, + NULL + ); + + MatrixNd H (MatrixNd::Zero(model->dof_count, model->dof_count)); + VectorNd C (VectorNd::Zero(model->dof_count)); + VectorNd QDDot_prealloc (VectorNd::Zero (model->dof_count)); + ForwardDynamicsLagrangian (*model, + Q, + QDot, + Tau, + QDDot_prealloc, + Math::LinearSolverPartialPivLU, + NULL, + &H, + &C + ); + + CHECK_ARRAY_EQUAL (QDDot.data(), QDDot_prealloc.data(), model->dof_count); +} + +TEST_FIXTURE ( FixedBase3DoF, SolveMInvTimesTau) { + for (unsigned int i = 0; i < model->dof_count; i++) { + Q[i] = rand() / static_cast(RAND_MAX); + Tau[i] = rand() / static_cast(RAND_MAX); + } + + MatrixNd M (MatrixNd::Zero(model->dof_count, model->dof_count)); + CompositeRigidBodyAlgorithm (*model, Q, M); + + VectorNd qddot_solve_llt = M.llt().solve(Tau); + + VectorNd qddot_minv (Q); + CalcMInvTimesTau (*model, Q, Tau, qddot_minv); + + CHECK_ARRAY_CLOSE (qddot_solve_llt.data(), qddot_minv.data(), model->dof_count, TEST_PREC); +} + +TEST_FIXTURE ( FixedBase3DoF, SolveMInvTimesTauReuse) { + for (unsigned int i = 0; i < model->dof_count; i++) { + Q[i] = rand() / static_cast(RAND_MAX); + Tau[i] = rand() / static_cast(RAND_MAX); + } + + MatrixNd M (MatrixNd::Zero(model->dof_count, model->dof_count)); + CompositeRigidBodyAlgorithm (*model, Q, M); + + VectorNd qddot_solve_llt = M.llt().solve(Tau); + + VectorNd qddot_minv (Q); + CalcMInvTimesTau (*model, Q, Tau, qddot_minv); + + for (unsigned int j = 0; j < 1; j++) { + for (unsigned int i = 0; i < model->dof_count; i++) { + Tau[i] = rand() / static_cast(RAND_MAX); + } + + CompositeRigidBodyAlgorithm (*model, Q, M); + qddot_solve_llt = M.llt().solve(Tau); + + CalcMInvTimesTau (*model, Q, Tau, qddot_minv, false); + + CHECK_ARRAY_CLOSE (qddot_solve_llt.data(), qddot_minv.data(), model->dof_count, TEST_PREC); + } +} diff --git a/3rdparty/rbdl/tests/Fixtures.h b/3rdparty/rbdl/tests/Fixtures.h new file mode 100644 index 0000000..983247b --- /dev/null +++ b/3rdparty/rbdl/tests/Fixtures.h @@ -0,0 +1,698 @@ +#include "rbdl/rbdl.h" + +struct FixedBase3DoF { + FixedBase3DoF () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + /* Basically a model like this, where X are the Center of Masses + * and the CoM of the last (3rd) body comes out of the Y=X=0 plane. + * + * Z + * *---* + * | + * | + * Z | + * O---* + * Y + */ + + body_a = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + joint_a = Joint( SpatialVector(0., 0., 1., 0., 0., 0.)); + + body_a_id = model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + body_b = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + joint_b = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + body_b_id = model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + body_c = Body (1., Vector3d (0., 0., 1.), Vector3d (1., 1., 1.)); + joint_c = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_c_id = model->AddBody(2, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + point_position = Vector3d::Zero (3); + point_acceleration = Vector3d::Zero (3); + + ref_body_id = 0; + + ClearLogOutput(); + } + ~FixedBase3DoF () { + delete model; + } + + RigidBodyDynamics::Model *model; + + unsigned int body_a_id, body_b_id, body_c_id, ref_body_id; + RigidBodyDynamics::Body body_a, body_b, body_c; + RigidBodyDynamics::Joint joint_a, joint_b, joint_c; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + RigidBodyDynamics::Math::VectorNd Tau; + + RigidBodyDynamics::Math::Vector3d point_position, point_acceleration; +}; + +struct FixedBase6DoF { + FixedBase6DoF () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + model->gravity = Vector3d (0., -9.81, 0.); + + /* 3 DoF (rot.) joint at base + * 3 DoF (rot.) joint child origin + * + * X Contact point (ref child) + * | + * Base | + * / body | + * O-------* + * \ + * Child body + */ + + // base body (3 DoF) + base_rot_z = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_base_rot_z = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + base_rot_z_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_base_rot_z, base_rot_z); + + base_rot_y = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_base_rot_y = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + base_rot_y_id = model->AppendBody (Xtrans (Vector3d (0., 0., 0.)), joint_base_rot_y, base_rot_y); + + base_rot_x = Body ( + 1., + Vector3d (0.5, 0., 0.), + Vector3d (1., 1., 1.) + ); + joint_base_rot_x = Joint ( SpatialVector (1., 0., 0., 0., 0., 0.)); + base_rot_x_id = model->AddBody (base_rot_y_id, Xtrans (Vector3d (0., 0., 0.)), joint_base_rot_x, base_rot_x); + + // child body (3 DoF) + child_rot_z = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_child_rot_z = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + child_rot_z_id = model->AddBody (base_rot_x_id, Xtrans (Vector3d (1., 0., 0.)), joint_child_rot_z, child_rot_z); + + child_rot_y = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_child_rot_y = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + child_rot_y_id = model->AddBody (child_rot_z_id, Xtrans (Vector3d (0., 0., 0.)), joint_child_rot_y, child_rot_y); + + child_rot_x = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + joint_child_rot_x = Joint ( SpatialVector (1., 0., 0., 0., 0., 0.)); + child_rot_x_id = model->AddBody (child_rot_y_id, Xtrans (Vector3d (0., 0., 0.)), joint_child_rot_x, child_rot_x); + + Q = VectorNd::Constant (model->mBodies.size() - 1, 0.); + QDot = VectorNd::Constant (model->mBodies.size() - 1, 0.); + QDDot = VectorNd::Constant (model->mBodies.size() - 1, 0.); + Tau = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + contact_body_id = child_rot_x_id; + contact_point = Vector3d (0.5, 0.5, 0.); + contact_normal = Vector3d (0., 1., 0.); + + ClearLogOutput(); + } + + ~FixedBase6DoF () { + delete model; + } + RigidBodyDynamics::Model *model; + + unsigned int base_rot_z_id, base_rot_y_id, base_rot_x_id, + child_rot_z_id, child_rot_y_id, child_rot_x_id, + base_body_id; + + RigidBodyDynamics::Body base_rot_z, base_rot_y, base_rot_x, + child_rot_z, child_rot_y, child_rot_x; + + RigidBodyDynamics::Joint joint_base_rot_z, joint_base_rot_y, joint_base_rot_x, + joint_child_rot_z, joint_child_rot_y, joint_child_rot_x; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + RigidBodyDynamics::Math::VectorNd Tau; + + unsigned int contact_body_id; + RigidBodyDynamics::Math::Vector3d contact_point; + RigidBodyDynamics::Math::Vector3d contact_normal; + RigidBodyDynamics::ConstraintSet constraint_set; +}; + +struct FloatingBase12DoF { + FloatingBase12DoF () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + model->gravity = Vector3d (0., -9.81, 0.); + + /* 3 DoF (rot.) joint at base + * 3 DoF (rot.) joint child origin + * + * X Contact point (ref child) + * | + * Base | + * / body | + * O-------* + * \ + * Child body + */ + + base_rot_x = Body ( + 1., + Vector3d (0.5, 0., 0.), + Vector3d (1., 1., 1.) + ); + base_rot_x_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_rot_x); + + // child body 1 (3 DoF) + child_rot_z = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_child_rot_z = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + child_rot_z_id = model->AddBody (base_rot_x_id, Xtrans (Vector3d (1., 0., 0.)), joint_child_rot_z, child_rot_z); + + child_rot_y = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_child_rot_y = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + child_rot_y_id = model->AddBody (child_rot_z_id, Xtrans (Vector3d (0., 0., 0.)), joint_child_rot_y, child_rot_y); + + child_rot_x = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + joint_child_rot_x = Joint ( SpatialVector (1., 0., 0., 0., 0., 0.)); + child_rot_x_id = model->AddBody (child_rot_y_id, Xtrans (Vector3d (0., 0., 0.)), joint_child_rot_x, child_rot_x); + + // child body (3 DoF) + child_2_rot_z = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_child_2_rot_z = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + child_2_rot_z_id = model->AddBody (child_rot_x_id, Xtrans (Vector3d (1., 0., 0.)), joint_child_2_rot_z, child_2_rot_z); + + child_2_rot_y = Body ( + 0., + Vector3d (0., 0., 0.), + Vector3d (0., 0., 0.) + ); + joint_child_2_rot_y = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + child_2_rot_y_id = model->AddBody (child_2_rot_z_id, Xtrans (Vector3d (0., 0., 0.)), joint_child_2_rot_y, child_2_rot_y); + + child_2_rot_x = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + joint_child_2_rot_x = Joint ( SpatialVector (1., 0., 0., 0., 0., 0.)); + child_2_rot_x_id = model->AddBody (child_2_rot_y_id, Xtrans (Vector3d (0., 0., 0.)), joint_child_2_rot_x, child_2_rot_x); + + Q = VectorNd::Constant (model->dof_count, 0.); + QDot = VectorNd::Constant (model->dof_count, 0.); + QDDot = VectorNd::Constant (model->dof_count, 0.); + Tau = VectorNd::Constant (model->dof_count, 0.); + + ClearLogOutput(); + } + + ~FloatingBase12DoF () { + delete model; + } + RigidBodyDynamics::Model *model; + + unsigned int base_rot_z_id, base_rot_y_id, base_rot_x_id, + child_rot_z_id, child_rot_y_id, child_rot_x_id, + child_2_rot_z_id, child_2_rot_y_id,child_2_rot_x_id, + base_body_id; + + RigidBodyDynamics::Body base_rot_z, base_rot_y, base_rot_x, + child_rot_z, child_rot_y, child_rot_x, + child_2_rot_z, child_2_rot_y, child_2_rot_x; + + RigidBodyDynamics::Joint joint_base_rot_z, joint_base_rot_y, joint_base_rot_x, + joint_child_rot_z, joint_child_rot_y, joint_child_rot_x, + joint_child_2_rot_z, joint_child_2_rot_y, joint_child_2_rot_x; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + RigidBodyDynamics::Math::VectorNd Tau; +}; + +struct SimpleFixture { + SimpleFixture () { + ClearLogOutput(); + model = new RigidBodyDynamics::Model; + model->gravity = RigidBodyDynamics::Math::Vector3d (0., -9.81, 0.); + } + ~SimpleFixture () { + delete model; + } + void ResizeVectors () { + Q = RigidBodyDynamics::Math::VectorNd::Zero (model->dof_count); + QDot = RigidBodyDynamics::Math::VectorNd::Zero (model->dof_count); + QDDot = RigidBodyDynamics::Math::VectorNd::Zero (model->dof_count); + Tau = RigidBodyDynamics::Math::VectorNd::Zero (model->dof_count); + } + + RigidBodyDynamics::Model *model; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + RigidBodyDynamics::Math::VectorNd Tau; +}; + +struct FixedJoint2DoF { + FixedJoint2DoF () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + /* Basically a model like this, where X are the Center of Masses + * and the CoM of the last (3rd) body comes out of the Y=X=0 plane. + * + * Z + * *---* + * | + * | + * Z | + * O---* + * Y + */ + + body_a = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + joint_a = Joint( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_a_id = model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + body_b = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + joint_b = Joint (JointTypeFixed); + + body_b_id = model->AddBody(1, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + body_c = Body (1., Vector3d (0., 0., 1.), Vector3d (1., 1., 1.)); + joint_c = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_c_id = model->AddBody(2, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + + point_position = Vector3d::Zero (3); + point_acceleration = Vector3d::Zero (3); + + ref_body_id = 0; + + ClearLogOutput(); + } + ~FixedJoint2DoF () { + delete model; + } + + RigidBodyDynamics::Model *model; + + unsigned int body_a_id, body_b_id, body_c_id, ref_body_id; + RigidBodyDynamics::Body body_a, body_b, body_c; + RigidBodyDynamics::Joint joint_a, joint_b, joint_c; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + + RigidBodyDynamics::Math::Vector3d point_position, point_acceleration; +}; + +/** \brief Fixture that contains two models of which one has one joint fixed. +*/ +struct FixedAndMovableJoint { + FixedAndMovableJoint () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model_movable = new Model; + + /* Basically a model like this, where X are the Center of Masses + * and the CoM of the last (3rd) body comes out of the Y=X=0 plane. + * + * Z + * *---* + * | + * | + * Z | + * O---* + * Y + */ + + body_a = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + joint_a = Joint( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_a_id = model_movable->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + body_b = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + joint_b = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + body_b_id = model_movable->AddBody(body_a_id, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + body_c = Body (1., Vector3d (0., 0., 1.), Vector3d (1., 1., 1.)); + joint_c = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_c_id = model_movable->AddBody(body_b_id, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + Q = VectorNd::Constant ((size_t) model_movable->dof_count, 0.); + QDot = VectorNd::Constant ((size_t) model_movable->dof_count, 0.); + QDDot = VectorNd::Constant ((size_t) model_movable->dof_count, 0.); + Tau = VectorNd::Constant ((size_t) model_movable->dof_count, 0.); + C_movable = VectorNd::Zero ((size_t) model_movable->dof_count); + H_movable = MatrixNd::Zero ((size_t) model_movable->dof_count, (size_t) model_movable->dof_count); + + // Assemble the fixed joint model + model_fixed = new Model; + + body_a_fixed_id = model_fixed->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + Joint joint_fixed (JointTypeFixed); + body_b_fixed_id = model_fixed->AddBody(body_a_fixed_id, Xtrans(Vector3d(1., 0., 0.)), joint_fixed, body_b); + body_c_fixed_id = model_fixed->AddBody(body_b_fixed_id, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + Q_fixed = VectorNd::Constant ((size_t) model_fixed->dof_count, 0.); + QDot_fixed = VectorNd::Constant ((size_t) model_fixed->dof_count, 0.); + QDDot_fixed = VectorNd::Constant ((size_t) model_fixed->dof_count, 0.); + Tau_fixed = VectorNd::Constant ((size_t) model_fixed->dof_count, 0.); + C_fixed = VectorNd::Zero ((size_t) model_fixed->dof_count); + H_fixed = MatrixNd::Zero ((size_t) model_fixed->dof_count, (size_t) model_fixed->dof_count); + + point_position = Vector3d::Zero (3); + point_acceleration = Vector3d::Zero (3); + + ref_body_id = 0; + + ClearLogOutput(); + } + + ~FixedAndMovableJoint () { + delete model_movable; + delete model_fixed; + } + RigidBodyDynamics::Math::VectorNd CreateDofVectorFromReducedVector (const RigidBodyDynamics::Math::VectorNd &q_fixed) { + assert (q_fixed.size() == model_fixed->dof_count); + + RigidBodyDynamics::Math::VectorNd q_movable (model_movable->dof_count); + + q_movable[0] = q_fixed[0]; + q_movable[1] = 0.; + q_movable[2] = q_fixed[1]; + + return q_movable; + } + + RigidBodyDynamics::Math::MatrixNd CreateReducedInertiaMatrix(const RigidBodyDynamics::Math::MatrixNd &H_movable) { + assert (H_movable.rows() == model_movable->dof_count); + assert (H_movable.cols() == model_movable->dof_count); + RigidBodyDynamics::Math::MatrixNd H (model_fixed->dof_count, model_fixed->dof_count); + + H (0,0) = H_movable(0,0); H (0,1) = H_movable(0,2); + H (1,0) = H_movable(2,0); H (1,1) = H_movable(2,2); + + return H; + } + + RigidBodyDynamics::Model *model_fixed; + RigidBodyDynamics::Model *model_movable; + + unsigned int body_a_id, body_b_id, body_c_id, ref_body_id; + unsigned int body_a_fixed_id, body_b_fixed_id, body_c_fixed_id; + + RigidBodyDynamics::Body body_a, body_b, body_c; + RigidBodyDynamics::Joint joint_a, joint_b, joint_c; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + RigidBodyDynamics::Math::VectorNd Tau; + RigidBodyDynamics::Math::VectorNd C_movable; + RigidBodyDynamics::Math::MatrixNd H_movable; + + RigidBodyDynamics::Math::VectorNd Q_fixed; + RigidBodyDynamics::Math::VectorNd QDot_fixed; + RigidBodyDynamics::Math::VectorNd QDDot_fixed; + RigidBodyDynamics::Math::VectorNd Tau_fixed; + RigidBodyDynamics::Math::VectorNd C_fixed; + RigidBodyDynamics::Math::MatrixNd H_fixed; + + RigidBodyDynamics::Math::Vector3d point_position, point_acceleration; +}; + +/** Model with two moving bodies and one fixed body +*/ +struct RotZRotZYXFixed { + RotZRotZYXFixed() { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + Joint joint_rot_zyx ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + + Body body_a(1., RigidBodyDynamics::Math::Vector3d (1., 0.4, 0.4), RigidBodyDynamics::Math::Vector3d (1., 1., 1.)); + Body body_b(2., RigidBodyDynamics::Math::Vector3d (1., 0.4, 0.4), RigidBodyDynamics::Math::Vector3d (1., 1., 1.)); + Body body_fixed(10., RigidBodyDynamics::Math::Vector3d (1., 0.4, 0.4), RigidBodyDynamics::Math::Vector3d (1., 1., 1.)); + + fixture_transform_a = Xtrans (RigidBodyDynamics::Math::Vector3d(1., 2., 3.)); + fixture_transform_b = Xtrans (RigidBodyDynamics::Math::Vector3d(4., 5., 6.)); + fixture_transform_fixed = Xtrans (RigidBodyDynamics::Math::Vector3d(-1., -2., -3.)); + + body_a_id = model->AddBody (0, fixture_transform_a, joint_rot_z, body_a); + body_b_id = model->AppendBody (fixture_transform_b, joint_rot_zyx, body_b); + body_fixed_id = model->AppendBody (fixture_transform_fixed, Joint(JointTypeFixed), body_fixed); + + ClearLogOutput(); + } + ~RotZRotZYXFixed() { + delete model; + } + + RigidBodyDynamics::Model *model; + + unsigned int body_a_id, body_b_id, body_fixed_id; + + RigidBodyDynamics::Math::SpatialTransform fixture_transform_a; + RigidBodyDynamics::Math::SpatialTransform fixture_transform_b; + RigidBodyDynamics::Math::SpatialTransform fixture_transform_fixed; +}; + +struct TwoArms12DoF { + TwoArms12DoF() { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + /* Basically a model like this, where X are the Center of Masses + * and the CoM of the last (3rd) body comes out of the Y=X=0 plane. + * + * *----O----* + * | | + * | | + * * * + * | | + * | | + * + */ + + Body body_upper = Body (1., Vector3d (0., -0.2, 0.), Vector3d (1.1, 1.3, 1.5)); + Body body_lower = Body (0.5, Vector3d(0., -0.15, 0.), Vector3d (0.3, 0.5, 0.2)); + + Joint joint_zyx = Joint ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + + right_upper_arm = model->AppendBody (Xtrans (Vector3d (0., 0., -0.3)), joint_zyx, body_upper, "RightUpper"); + // model->AppendBody (Xtrans (Vector3d (0., -0.4, 0.)), joint_zyx, body_lower, "RightLower"); + left_upper_arm = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.3)), joint_zyx, body_upper, "LeftUpper"); + // model->AppendBody (Xtrans (Vector3d (0., -0.4, 0.)), joint_zyx, body_lower, "LeftLower"); + + q = VectorNd::Constant ((size_t) model->dof_count, 0.); + qdot = VectorNd::Constant ((size_t) model->dof_count, 0.); + qddot = VectorNd::Constant ((size_t) model->dof_count, 0.); + tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + ClearLogOutput(); + } + ~TwoArms12DoF() { + delete model; + } + + RigidBodyDynamics::Model *model; + + RigidBodyDynamics::Math::VectorNd q; + RigidBodyDynamics::Math::VectorNd qdot; + RigidBodyDynamics::Math::VectorNd qddot; + RigidBodyDynamics::Math::VectorNd tau; + + unsigned int right_upper_arm, left_upper_arm; + +}; + +struct FixedBase6DoF12DoFFloatingBase { + FixedBase6DoF12DoFFloatingBase () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + model->gravity = Vector3d (0., -9.81, 0.); + + /* 3 DoF (rot.) joint at base + * 3 DoF (rot.) joint child origin + * + * X Contact point (ref child) + * | + * Base | + * / body | + * O-------* + * \ + * Child body + */ + + // base body (3 DoF) + base = Body ( + 1., + Vector3d (0.5, 0., 0.), + Vector3d (1., 1., 1.) + ); + base_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + // child body 1 (3 DoF) + child = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + joint_rotzyx = Joint ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + child_id = model->AddBody (base_id, Xtrans (Vector3d (1., 0., 0.)), joint_rotzyx, child); + + // child body (3 DoF) + child_2 = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + child_2_id = model->AddBody (child_id, Xtrans (Vector3d (1., 0., 0.)), joint_rotzyx, child_2); + + Q = VectorNd::Constant (model->dof_count, 0.); + QDot = VectorNd::Constant (model->dof_count, 0.); + QDDot = VectorNd::Constant (model->dof_count, 0.); + Tau = VectorNd::Constant (model->dof_count, 0.); + + contact_body_id = child_id; + contact_point = Vector3d (0.5, 0.5, 0.); + contact_normal = Vector3d (0., 1., 0.); + + ClearLogOutput(); + } + + ~FixedBase6DoF12DoFFloatingBase () { + delete model; + } + RigidBodyDynamics::Model *model; + + unsigned int base_id, child_id, child_2_id; + + RigidBodyDynamics::Body base, child, child_2; + + RigidBodyDynamics::Joint joint_rotzyx; + + RigidBodyDynamics::Math::VectorNd Q; + RigidBodyDynamics::Math::VectorNd QDot; + RigidBodyDynamics::Math::VectorNd QDDot; + RigidBodyDynamics::Math::VectorNd Tau; + + unsigned int contact_body_id; + RigidBodyDynamics::Math::Vector3d contact_point; + RigidBodyDynamics::Math::Vector3d contact_normal; + RigidBodyDynamics::ConstraintSet constraint_set; +}; diff --git a/3rdparty/rbdl/tests/FloatingBaseTests.cc b/3rdparty/rbdl/tests/FloatingBaseTests.cc new file mode 100644 index 0000000..b77b310 --- /dev/null +++ b/3rdparty/rbdl/tests/FloatingBaseTests.cc @@ -0,0 +1,550 @@ +#include + +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +struct FloatingBaseFixture { + FloatingBaseFixture () { + ClearLogOutput(); + model = new Model; + model->gravity = Vector3d (0., -9.81, 0.); + + base = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + + } + ~FloatingBaseFixture () { + delete model; + } + Model *model; + Body base; + unsigned int base_body_id; + + VectorNd q, qdot, qddot, tau; +}; + +TEST_FIXTURE ( FloatingBaseFixture, TestCalcPointTransformation ) { + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + q = VectorNd::Constant(model->dof_count, 0.); + qdot = VectorNd::Constant(model->dof_count, 0.); + qddot = VectorNd::Constant(model->dof_count, 0.); + tau = VectorNd::Constant(model->dof_count, 0.); + + q[1] = 1.; + ForwardDynamics (*model, q, qdot, tau, qddot); + + Vector3d test_point; + + test_point = CalcBaseToBodyCoordinates (*model, q, base_body_id, Vector3d (0., 0., 0.), false); + CHECK_ARRAY_CLOSE (Vector3d (0., -1., 0.).data(), test_point.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(FloatingBaseFixture, TestCalcDynamicFloatingBaseDoubleImplicit) { + // floating base + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + // body_a + Body body_a (1., Vector3d (1., 0., 0), Vector3d (1., 1., 1.)); + Joint joint_a ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(base_body_id, Xtrans(Vector3d(2., 0., 0.)), joint_a, body_a); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Zero ((size_t) model->dof_count); + VectorNd QDot = VectorNd::Zero ((size_t) model->dof_count); + VectorNd QDDot = VectorNd::Zero ((size_t) model->dof_count); + VectorNd Tau = VectorNd::Zero ((size_t) model->dof_count); + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + unsigned int i; + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a.at(i) << endl; + } + + // std::cout << LogOutput.str() << std::endl; + + CHECK_CLOSE ( 0.0000, QDDot[0], TEST_PREC); + CHECK_CLOSE (-9.8100, QDDot[1], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[2], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[3], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[4], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[5], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[6], TEST_PREC); + + // We rotate the base... let's see what happens... + Q[3] = 0.8; + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a.at(i) << endl; + } + + // std::cout << LogOutput.str() << std::endl; + + CHECK_CLOSE ( 0.0000, QDDot[0], TEST_PREC); + CHECK_CLOSE (-9.8100, QDDot[1], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[2], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[3], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[4], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[5], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[6], TEST_PREC); + + // We apply a torqe let's see what happens... + Q[3] = 0.; + /* + rot_B[0] = 0.0; + X_B = XtransRotZYXEuler(pos_B, rot_B); + */ + + Tau[6] = 1.; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + for (i = 0; i < QDDot.size(); i++) { + LOG << "QDDot[" << i << "] = " << QDDot[i] << endl; + } + + for (i = 0; i < model->a.size(); i++) { + LOG << "a[" << i << "] = " << model->a.at(i) << endl; + } + + // std::cout << LogOutput.str() << std::endl; + + CHECK_CLOSE ( 0.0000, QDDot[0], TEST_PREC); + CHECK_CLOSE (-8.8100, QDDot[1], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[2], TEST_PREC); + CHECK_CLOSE (-1.0000, QDDot[3], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[4], TEST_PREC); + CHECK_CLOSE ( 0.0000, QDDot[5], TEST_PREC); + CHECK_CLOSE ( 2.0000, QDDot[6], TEST_PREC); +} + +TEST_FIXTURE(FloatingBaseFixture, TestCalcPointVelocityFloatingBaseSimple) { + // floating base + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + VectorNd Q = VectorNd::Zero (model->dof_count); + VectorNd QDot = VectorNd::Zero (model->dof_count); + VectorNd QDDot = VectorNd::Zero (model->dof_count); + VectorNd Tau = VectorNd::Zero (model->dof_count); + + unsigned int ref_body_id = base_body_id; + + // first we calculate the velocity when moving along the X axis + QDot[0] = 1.; + Vector3d point_position(1., 0., 0.); + Vector3d point_velocity; + + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + CHECK_CLOSE(1., point_velocity[0], TEST_PREC); + CHECK_CLOSE(0., point_velocity[1], TEST_PREC); + CHECK_CLOSE(0., point_velocity[2], TEST_PREC); + + LOG << "Point velocity = " << point_velocity << endl; + // cout << LogOutput.str() << endl; + + ClearLogOutput(); + + // Now we calculate the velocity when rotating around the Z axis + QDot[0] = 0.; + QDot[3] = 1.; + + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + CHECK_CLOSE(0., point_velocity[0], TEST_PREC); + CHECK_CLOSE(1., point_velocity[1], TEST_PREC); + CHECK_CLOSE(0., point_velocity[2], TEST_PREC); + + LOG << "Point velocity = " << point_velocity << endl; + // cout << LogOutput.str() << endl; + + // Now we calculate the velocity when rotating around the Z axis and the + // base is rotated around the z axis by 90 degrees + ClearLogOutput(); + Q[3] = M_PI * 0.5; + QDot[3] = 1.; + + point_velocity = CalcPointVelocity(*model, Q, QDot, ref_body_id, point_position); + + CHECK_CLOSE(-1., point_velocity[0], TEST_PREC); + CHECK_CLOSE(0., point_velocity[1], TEST_PREC); + CHECK_CLOSE(0., point_velocity[2], TEST_PREC); + + LOG << "Point velocity = " << point_velocity << endl; + // cout << LogOutput.str() << endl; +} + +TEST_FIXTURE(FloatingBaseFixture, TestCalcPointVelocityCustom) { + // floating base + base = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + VectorNd q = VectorNd::Zero (model->dof_count); + VectorNd qdot = VectorNd::Zero (model->dof_count); + VectorNd qddot = VectorNd::Zero (model->dof_count); + VectorNd tau = VectorNd::Zero (model->dof_count); + + unsigned int ref_body_id = base_body_id; + + q[0] = 0.1; + q[1] = 1.1; + q[2] = 1.2; + q[3] = 1.3; + q[4] = 1.5; + q[5] = 1.7; + + qdot[0] = 0.1; + qdot[1] = 1.1; + qdot[2] = 1.2; + qdot[3] = 1.3; + qdot[4] = 1.5; + qdot[5] = 1.7; + + // first we calculate the velocity when rotating around the Z axis + Vector3d point_body_position (1., 0., 0.); + Vector3d point_base_position; + Vector3d point_base_velocity; + Vector3d point_base_velocity_reference; + + ForwardDynamics(*model, q, qdot, tau, qddot); + + point_base_velocity = CalcPointVelocity (*model, q, qdot, ref_body_id, point_body_position); + + point_base_velocity_reference = Vector3d ( + -3.888503432977729e-01, + -3.171179347202455e-01, + 1.093894197498446e+00 + ); + + CHECK_ARRAY_CLOSE (point_base_velocity_reference.data(), point_base_velocity.data(), 3, TEST_PREC); +} + +/** \brief Compares computation of acceleration values for zero qddot + * + * Ensures that computation of position, velocity, and acceleration of a + * point produce the same values as in an equivalent model that was + * created with the HuMAnS toolbox + * http://www.inrialpes.fr/bipop/software/humans/ . + * Here we omit the term of the generalized acceleration by setting qddot + * to zero. + */ +TEST_FIXTURE(FloatingBaseFixture, TestCalcPointAccelerationNoQDDot) { + // floating base + base = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + VectorNd q = VectorNd::Zero (model->dof_count); + VectorNd qdot = VectorNd::Zero (model->dof_count); + VectorNd qddot = VectorNd::Zero (model->dof_count); + VectorNd tau = VectorNd::Zero (model->dof_count); + + unsigned int ref_body_id = base_body_id; + + q[0] = 0.1; + q[1] = 1.1; + q[2] = 1.2; + q[3] = 1.3; + q[4] = 1.5; + q[5] = 1.7; + + qdot[0] = 0.1; + qdot[1] = 1.1; + qdot[2] = 1.2; + qdot[3] = 1.3; + qdot[4] = 1.5; + qdot[5] = 1.7; + + // first we calculate the velocity when rotating around the Z axis + Vector3d point_body_position (-1.9, -1.8, 0.); + Vector3d point_world_position; + Vector3d point_world_velocity; + Vector3d point_world_acceleration; + + // call ForwardDynamics to update the model + ForwardDynamics(*model, q, qdot, tau, qddot); + qddot = VectorNd::Zero (qddot.size()); + + qdot = qdot; + + point_world_position = CalcBodyToBaseCoordinates (*model, q, ref_body_id, point_body_position, false); + point_world_velocity = CalcPointVelocity (*model, q, qdot, ref_body_id, point_body_position); + + // we set the generalized acceleration to zero + + ClearLogOutput(); + + point_world_acceleration = CalcPointAcceleration (*model, q, qdot, qddot, ref_body_id, point_body_position); + + Vector3d humans_point_position ( + -6.357089363622626e-01, -6.831041744630977e-01, 2.968974805916970e+00 + ); + Vector3d humans_point_velocity ( + 3.091226260907569e-01, 3.891012095550828e+00, 4.100277995030419e+00 + ); + Vector3d humans_point_acceleration ( + -5.302760158847160e+00, 6.541369639625232e+00, -4.795115077652286e+00 + ); + + // cout << LogOutput.str() << endl; + // + // cout << "q = " << q << endl; + // cout << "qdot = " << qdot << endl; + // cout << "qddot = " << qddot << endl; + // + // cout << "body_coords = " << point_body_position << endl; + // cout << "world_pos = " << point_world_position << endl; + // cout << "world_vel = " << point_world_velocity << endl; + // cout << "world_accel = " << point_world_acceleration << endl; + + + CHECK_ARRAY_CLOSE (humans_point_position.data(), point_world_position.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (humans_point_velocity.data(), point_world_velocity.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (humans_point_acceleration.data(), point_world_acceleration.data(), 3, TEST_PREC); +} + +/** \brief Compares computation of acceleration values for zero q and qdot + * + * Ensures that computation of position, velocity, and acceleration of a + * point produce the same values as in an equivalent model that was + * created with the HuMAnS toolbox + * http://www.inrialpes.fr/bipop/software/humans/ . + * + * Here we set q and qdot to zero and only take into account values that + * are dependent on qddot. + */ +TEST_FIXTURE(FloatingBaseFixture, TestCalcPointAccelerationOnlyQDDot) { + // floating base + base = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + VectorNd q = VectorNd::Zero (model->dof_count); + VectorNd qdot = VectorNd::Zero (model->dof_count); + VectorNd qddot = VectorNd::Zero (model->dof_count); + VectorNd tau = VectorNd::Zero (model->dof_count); + + unsigned int ref_body_id = base_body_id; + + // first we calculate the velocity when rotating around the Z axis + Vector3d point_body_position (-1.9, -1.8, 0.); + Vector3d point_world_position; + Vector3d point_world_velocity; + Vector3d point_world_acceleration; + + ForwardDynamics(*model, q, qdot, tau, qddot); + + qddot = VectorNd::Zero (qddot.size()); + + qddot[0] = 0.1; + qddot[1] = 1.1; + qddot[2] = 1.2; + qddot[3] = 1.3; + qddot[4] = 1.5; + qddot[5] = 1.7; + + // cout << "ref_body_id = " << ref_body_id << endl; + // cout << "point_body_position = " << point_body_position << endl; + point_world_position = CalcBodyToBaseCoordinates (*model, q, ref_body_id, point_body_position, false); + point_world_velocity = CalcPointVelocity (*model, q, qdot, ref_body_id, point_body_position); + + ClearLogOutput(); + + point_world_acceleration = CalcPointAcceleration (*model, q, qdot, qddot, ref_body_id, point_body_position); + + Vector3d humans_point_position ( + -1.900000000000000e+00, -1.800000000000000e+00, 0.000000000000000e+00 + ); + Vector3d humans_point_velocity ( + 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 + ); + Vector3d humans_point_acceleration ( + 2.440000000000000e+00, -1.370000000000000e+00, 9.899999999999999e-01 + ); + + // cout << LogOutput.str() << endl; + // + // cout << "q = " << q << endl; + // cout << "qdot = " << qdot << endl; + // cout << "qddot = " << qddot << endl; + // + // cout << "body_coords = " << point_body_position << endl; + // cout << "world_pos = " << point_world_position << endl; + // cout << "world_vel = " << point_world_velocity << endl; + // cout << "world_accel = " << point_world_acceleration << endl; + + CHECK_ARRAY_CLOSE (humans_point_position.data(), point_world_position.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (humans_point_velocity.data(), point_world_velocity.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (humans_point_acceleration.data(), point_world_acceleration.data(), 3, TEST_PREC); +} + +/** \brief Compares computation of acceleration values for zero q and qdot + * + * Ensures that computation of position, velocity, and acceleration of a + * point produce the same values as in an equivalent model that was + * created with the HuMAnS toolbox + * http://www.inrialpes.fr/bipop/software/humans/ . + * + * Here we set q and qdot to zero and only take into account values that + * are dependent on qddot. + */ +TEST_FIXTURE(FloatingBaseFixture, TestCalcPointAccelerationFull) { + // floating base + base = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + base_body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base); + + VectorNd q = VectorNd::Zero (model->dof_count); + VectorNd qdot = VectorNd::Zero (model->dof_count); + VectorNd qddot = VectorNd::Zero (model->dof_count); + VectorNd tau = VectorNd::Zero (model->dof_count); + + unsigned int ref_body_id = base_body_id; + + // first we calculate the velocity when rotating around the Z axis + Vector3d point_body_position (-1.9, -1.8, 0.); + Vector3d point_world_position; + Vector3d point_world_velocity; + Vector3d point_world_acceleration; + + q[0] = 0.1; + q[1] = 1.1; + q[2] = 1.2; + q[3] = 1.3; + q[4] = 1.5; + q[5] = 1.7; + + qdot[0] = 0.1; + qdot[1] = 1.1; + qdot[2] = 1.2; + qdot[3] = 1.3; + qdot[4] = 1.5; + qdot[5] = 1.7; + + ForwardDynamics(*model, q, qdot, tau, qddot); + + qddot[0] = 0.1; + qddot[1] = 1.1; + qddot[2] = 1.2; + qddot[3] = 1.3; + qddot[4] = 1.5; + qddot[5] = 1.7; + + // cout << "ref_body_id = " << ref_body_id << endl; + // cout << "point_body_position = " << point_body_position << endl; + point_world_position = CalcBodyToBaseCoordinates (*model, q, ref_body_id, point_body_position, false); + point_world_velocity = CalcPointVelocity (*model, q, qdot, ref_body_id, point_body_position); + + ClearLogOutput(); + + point_world_acceleration = CalcPointAcceleration (*model, q, qdot, qddot, ref_body_id, point_body_position); + + Vector3d humans_point_position ( + -6.357089363622626e-01, -6.831041744630977e-01, 2.968974805916970e+00 + ); + Vector3d humans_point_velocity ( + 3.091226260907569e-01, 3.891012095550828e+00, 4.100277995030419e+00 + ); + Vector3d humans_point_acceleration ( + -4.993637532756404e+00, 1.043238173517606e+01, -6.948370826218673e-01 + ); + + // cout << LogOutput.str() << endl; + // + // cout << "q = " << q << endl; + // cout << "qdot = " << qdot << endl; + // cout << "qddot = " << qddot << endl; + // + // cout << "body_coords = " << point_body_position << endl; + // cout << "world_pos = " << point_world_position << endl; + // cout << "world_vel = " << point_world_velocity << endl; + // cout << "world_accel = " << point_world_acceleration << endl; + + CHECK_ARRAY_CLOSE (humans_point_position.data(), point_world_position.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (humans_point_velocity.data(), point_world_velocity.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (humans_point_acceleration.data(), point_world_acceleration.data(), 3, TEST_PREC); +} + + diff --git a/3rdparty/rbdl/tests/Human36Fixture.h b/3rdparty/rbdl/tests/Human36Fixture.h new file mode 100644 index 0000000..088cb7f --- /dev/null +++ b/3rdparty/rbdl/tests/Human36Fixture.h @@ -0,0 +1,461 @@ +#ifndef RBDL_HUMAN36_FIXTURE +#define RBDL_HUMAN36_FIXTURE + +#include "rbdl/rbdl.h" + +struct Human36 { + RigidBodyDynamics::Model *model; + RigidBodyDynamics::Model *model_emulated; + RigidBodyDynamics::Model *model_3dof; + + RigidBodyDynamics::Math::VectorNd q; + RigidBodyDynamics::Math::VectorNd qdot; + RigidBodyDynamics::Math::VectorNd qddot; + RigidBodyDynamics::Math::VectorNd tau; + + RigidBodyDynamics::Math::VectorNd qddot_emulated; + RigidBodyDynamics::Math::VectorNd qddot_3dof; + + RigidBodyDynamics::ConstraintSet constraints_1B1C_emulated; + RigidBodyDynamics::ConstraintSet constraints_1B4C_emulated; + RigidBodyDynamics::ConstraintSet constraints_4B4C_emulated; + + RigidBodyDynamics::ConstraintSet constraints_1B1C_3dof; + RigidBodyDynamics::ConstraintSet constraints_1B4C_3dof; + RigidBodyDynamics::ConstraintSet constraints_4B4C_3dof; + + enum SegmentName { + SegmentPelvis = 0, + SegmentThigh, + SegmentShank, + SegmentFoot, + SegmentMiddleTrunk, + SegmentUpperTrunk, + SegmentUpperArm, + SegmentLowerArm, + SegmentHand, + SegmentHead, + SegmentNameLast + }; + + enum BodyName { + BodyPelvis, + BodyThighRight, + BodyShankRight, + BodyFootRight, + BodyThighLeft, + BodyShankLeft, + BodyFootLeft, + BodyMiddleTrunk, + BodyUpperTrunk, + BodyUpperArmRight, + BodyLowerArmRight, + BodyHandRight, + BodyUpperArmLeft, + BodyLowerArmLeft, + BodyHandLeft, + BodyHead, + BodyNameLast + }; + + enum DofNames { + PelvisTX, + PelvisTY, + PelvisTZ, + PelvisRY, + PelvisRX, + PelvisRZ, + HipRightRY, + HipRightRX, + HipRightRZ, + KneeRightRY, + AnkleRightRY, + AnkleRightRZ, + HipLeftRY, + HipLeftRX, + HipLeftRZ, + KneeLeftRY, + AnkleLeftRY, + AnkleLeftRZ, + LumbarRY, + LumbarRX, + LumbarRZ, + ShoulderRightRY, + ShoulderRightRX, + ShoulderRightRZ, + ElbowRightRY, + WristRightRY, + WristRightRZ, + ShoulderLeftRY, + ShoulderLeftRX, + ShoulderLeftRZ, + ElbowLeftRY, + WristLeftRY, + WristLeftRZ, + NeckRY, + NeckRX, + NeckRZ, + DofNameCount + }; + + double SegmentLengths[SegmentNameLast]; + double SegmentMass[SegmentNameLast]; + double SegmentCOM[SegmentNameLast][3]; + double SegmentRadiiOfGyration[SegmentNameLast][3]; + + unsigned int body_id_emulated[BodyNameLast]; + unsigned int body_id_3dof[BodyNameLast]; + + void initParameters () { + SegmentLengths[SegmentPelvis ] = 0.1457; + SegmentLengths[SegmentThigh ] = 0.4222; + SegmentLengths[SegmentShank ] = 0.4403; + SegmentLengths[SegmentFoot ] = 0.1037; + SegmentLengths[SegmentMiddleTrunk] = 0.2155; + SegmentLengths[SegmentUpperTrunk ] = 0.2421; + SegmentLengths[SegmentUpperArm ] = 0.2817; + SegmentLengths[SegmentLowerArm ] = 0.2689; + SegmentLengths[SegmentHand ] = 0.0862; + SegmentLengths[SegmentHead ] = 0.2429; + + SegmentMass[SegmentPelvis ] = 0.8154; + SegmentMass[SegmentThigh ] = 10.3368; + SegmentMass[SegmentShank ] = 3.1609; + SegmentMass[SegmentFoot ] = 1.001; + SegmentMass[SegmentMiddleTrunk] = 16.33; + SegmentMass[SegmentUpperTrunk ] = 15.96; + SegmentMass[SegmentUpperArm ] = 1.9783; + SegmentMass[SegmentLowerArm ] = 1.1826; + SegmentMass[SegmentHand ] = 0.4453; + SegmentMass[SegmentHead ] = 5.0662; + + SegmentCOM[SegmentPelvis ][0] = 0.; + SegmentCOM[SegmentPelvis ][1] = 0.; + SegmentCOM[SegmentPelvis ][2] = 0.0891; + + SegmentCOM[SegmentThigh ][0] = 0.; + SegmentCOM[SegmentThigh ][1] = 0.; + SegmentCOM[SegmentThigh ][2] = -0.1729; + + SegmentCOM[SegmentShank ][0] = 0.; + SegmentCOM[SegmentShank ][1] = 0.; + SegmentCOM[SegmentShank ][2] = -0.1963; + + SegmentCOM[SegmentFoot ][0] = 0.1254; + SegmentCOM[SegmentFoot ][1] = 0.; + SegmentCOM[SegmentFoot ][2] = -0.0516; + + SegmentCOM[SegmentMiddleTrunk][0] = 0.; + SegmentCOM[SegmentMiddleTrunk][1] = 0.; + SegmentCOM[SegmentMiddleTrunk][2] = 0.1185; + + SegmentCOM[SegmentUpperTrunk ][0] = 0.; + SegmentCOM[SegmentUpperTrunk ][1] = 0.; + SegmentCOM[SegmentUpperTrunk ][2] = 0.1195; + + SegmentCOM[SegmentUpperArm ][0] = 0.; + SegmentCOM[SegmentUpperArm ][1] = 0.; + SegmentCOM[SegmentUpperArm ][2] = -0.1626; + + SegmentCOM[SegmentLowerArm ][0] = 0.; + SegmentCOM[SegmentLowerArm ][1] = 0.; + SegmentCOM[SegmentLowerArm ][2] = -0.1230; + + SegmentCOM[SegmentHand ][0] = 0.; + SegmentCOM[SegmentHand ][1] = 0.; + SegmentCOM[SegmentHand ][2] = -0.0680; + + SegmentCOM[SegmentHead ][0] = 0.; + SegmentCOM[SegmentHead ][1] = 0.; + SegmentCOM[SegmentHead ][2] = 1.1214; + + SegmentRadiiOfGyration[SegmentPelvis ][0] = 0.0897; + SegmentRadiiOfGyration[SegmentPelvis ][1] = 0.0855; + SegmentRadiiOfGyration[SegmentPelvis ][2] = 0.0803; + + SegmentRadiiOfGyration[SegmentThigh ][0] = 0.1389; + SegmentRadiiOfGyration[SegmentThigh ][1] = 0.0629; + SegmentRadiiOfGyration[SegmentThigh ][2] = 0.1389; + + SegmentRadiiOfGyration[SegmentShank ][0] = 0.1123; + SegmentRadiiOfGyration[SegmentShank ][1] = 0.0454; + SegmentRadiiOfGyration[SegmentShank ][2] = 0.1096; + + SegmentRadiiOfGyration[SegmentFoot ][0] = 0.0267; + SegmentRadiiOfGyration[SegmentFoot ][1] = 0.0129; + SegmentRadiiOfGyration[SegmentFoot ][2] = 0.0254; + + SegmentRadiiOfGyration[SegmentMiddleTrunk][0] = 0.0970; + SegmentRadiiOfGyration[SegmentMiddleTrunk][1] = 0.1009; + SegmentRadiiOfGyration[SegmentMiddleTrunk][2] = 0.0825; + + SegmentRadiiOfGyration[SegmentUpperTrunk ][0] = 0.1273; + SegmentRadiiOfGyration[SegmentUpperTrunk ][1] = 0.1172; + SegmentRadiiOfGyration[SegmentUpperTrunk ][2] = 0.0807; + + SegmentRadiiOfGyration[SegmentUpperArm ][0] = 0.0803; + SegmentRadiiOfGyration[SegmentUpperArm ][1] = 0.0758; + SegmentRadiiOfGyration[SegmentUpperArm ][2] = 0.0445; + + SegmentRadiiOfGyration[SegmentLowerArm ][0] = 0.0742; + SegmentRadiiOfGyration[SegmentLowerArm ][1] = 0.0713; + SegmentRadiiOfGyration[SegmentLowerArm ][2] = 0.0325; + + SegmentRadiiOfGyration[SegmentHand ][0] = 0.0541; + SegmentRadiiOfGyration[SegmentHand ][1] = 0.0442; + SegmentRadiiOfGyration[SegmentHand ][2] = 0.0346; + + SegmentRadiiOfGyration[SegmentHead ][0] = 0.0736; + SegmentRadiiOfGyration[SegmentHead ][1] = 0.0634; + SegmentRadiiOfGyration[SegmentHead ][2] = 0.0765; + }; + + RigidBodyDynamics::Body create_body (SegmentName segment) { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + Matrix3d inertia_C (Matrix3d::Zero()); + inertia_C(0,0) = pow(SegmentRadiiOfGyration[segment][0] * SegmentLengths[segment], 2) * SegmentMass[segment]; + inertia_C(1,1) = pow(SegmentRadiiOfGyration[segment][1] * SegmentLengths[segment], 2) * SegmentMass[segment]; + inertia_C(2,2) = pow(SegmentRadiiOfGyration[segment][2] * SegmentLengths[segment], 2) * SegmentMass[segment]; + + return RigidBodyDynamics::Body ( + SegmentMass[segment], + RigidBodyDynamics::Math::Vector3d ( + SegmentCOM[segment][0], + SegmentCOM[segment][1], + SegmentCOM[segment][2] + ), + inertia_C); + } + + void generate () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + Body pelvis_body = create_body (SegmentPelvis); + Body thigh_body = create_body (SegmentThigh); + Body shank_body = create_body (SegmentShank); + Body foot_body = create_body (SegmentFoot); + Body middle_trunk_body = create_body (SegmentMiddleTrunk); + Body upper_trunk_body = create_body (SegmentUpperTrunk); + Body upperarm_body = create_body (SegmentUpperArm); + Body lowerarm_body = create_body (SegmentLowerArm); + Body hand_body = create_body (SegmentHand); + Body head_body = create_body (SegmentHead); + + Matrix3d zero_matrix (Matrix3d::Zero(3,3)); + Body null_body (0., Vector3d (0., 0., 0.), zero_matrix); + + Joint free_flyer ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + + Joint rot_yxz_emulated ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + + Joint trans_xyz = Joint(JointTypeTranslationXYZ); + + Joint rot_yxz_3dof = Joint(JointTypeEulerYXZ); + + Joint rot_yz ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + + Joint rot_y ( + SpatialVector (0., 1., 0., 0., 0., 0.) + ); + + Joint fixed (JointTypeFixed); + + // Generate emulated model + model_emulated->gravity = Vector3d (0., 0., -9.81); + + body_id_emulated[BodyPelvis] = model_emulated->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), free_flyer, pelvis_body, "pelvis"); + + // right leg + body_id_emulated[BodyThighRight] = model_emulated->AddBody (body_id_emulated[BodyPelvis], Xtrans(Vector3d(0., -0.0872, 0.)), rot_yxz_emulated, thigh_body, "thigh_r"); + body_id_emulated[BodyShankRight] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentThigh])), rot_y, shank_body, "shank_r"); + body_id_emulated[BodyFootRight] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentShank])), rot_yz, foot_body, "foot_r"); + + // left leg + body_id_emulated[BodyThighLeft] = model_emulated->AddBody (body_id_emulated[BodyPelvis], Xtrans(Vector3d(0., 0.0872, 0.)), rot_yxz_emulated, thigh_body, "thigh_l"); + body_id_emulated[BodyShankLeft] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentThigh])), rot_y, shank_body, "shank_l"); + body_id_emulated[BodyFootLeft] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentShank])), rot_yz, foot_body, "foot_l"); + + // trunk + body_id_emulated[BodyMiddleTrunk] = model_emulated->AddBody (body_id_emulated[BodyPelvis], Xtrans(Vector3d(0., 0., SegmentLengths[SegmentPelvis])), rot_yxz_emulated, middle_trunk_body, "middletrunk"); + body_id_emulated[BodyUpperTrunk] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., SegmentLengths[SegmentMiddleTrunk])), fixed, upper_trunk_body, "uppertrunk"); + + // right arm + body_id_emulated[BodyUpperArmRight] = model_emulated->AddBody (body_id_emulated[BodyUpperTrunk], Xtrans(Vector3d(0., -0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz_emulated, upperarm_body, "upperarm_r"); + body_id_emulated[BodyLowerArmRight] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentUpperArm])), rot_y, lowerarm_body, "lowerarm_r"); + body_id_emulated[BodyHandRight] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentLowerArm])), rot_yz, hand_body, "hand_r"); + + // left arm + body_id_emulated[BodyUpperArmLeft] = model_emulated->AddBody (body_id_emulated[BodyUpperTrunk], Xtrans(Vector3d(0., 0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz_emulated, upperarm_body, "upperarm_l"); + body_id_emulated[BodyLowerArmLeft] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentUpperArm])), rot_y, lowerarm_body, "lowerarm_l"); + body_id_emulated[BodyHandLeft] = model_emulated->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentLowerArm])), rot_yz, hand_body, "hand_l"); + + // head + body_id_emulated[BodyHead] = model_emulated->AddBody (body_id_emulated[BodyUpperTrunk], Xtrans(Vector3d(0., 0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz_emulated, upperarm_body, "head"); + + // Generate 3dof model + model_3dof->gravity = Vector3d (0., 0., -9.81); + + unsigned int pelvis_trans = model_3dof->AddBody (0, Xtrans(Vector3d (0., 0., 0.)), trans_xyz, null_body, "pelvis_trans_xyz"); + + body_id_3dof[BodyPelvis] = model_3dof->AddBody (pelvis_trans, Xtrans (Vector3d (0., 0., 0.)), rot_yxz_3dof, pelvis_body, "pelvis"); + // body_id_3dof[BodyPelvis] = model_3dof->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), free_flyer, pelvis_body, "pelvis"); + + // right leg + body_id_3dof[BodyThighRight] = model_3dof->AddBody (body_id_3dof[BodyPelvis], Xtrans(Vector3d(0., -0.0872, 0.)), rot_yxz_3dof, thigh_body, "thigh_r"); + body_id_3dof[BodyShankRight] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentThigh])), rot_y, shank_body, "shank_r"); + body_id_3dof[BodyFootRight] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentShank])), rot_yz, foot_body, "foot_r"); + + // left leg + body_id_3dof[BodyThighLeft] = model_3dof->AddBody (body_id_3dof[BodyPelvis], Xtrans(Vector3d(0., 0.0872, 0.)), rot_yxz_3dof, thigh_body, "thigh_l"); + body_id_3dof[BodyShankLeft] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentThigh])), rot_y, shank_body, "shank_l"); + body_id_3dof[BodyFootLeft] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentShank])), rot_yz, foot_body, "foot_l"); + + // trunk + body_id_3dof[BodyMiddleTrunk] = model_3dof->AddBody (body_id_3dof[BodyPelvis], Xtrans(Vector3d(0., 0., SegmentLengths[SegmentPelvis])), rot_yxz_3dof, middle_trunk_body, "middletrunk"); + body_id_3dof[BodyUpperTrunk] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., SegmentLengths[SegmentMiddleTrunk])), fixed, upper_trunk_body, "uppertrunk"); + + // right arm + body_id_3dof[BodyUpperArmRight] = model_3dof->AddBody (body_id_3dof[BodyUpperTrunk], Xtrans(Vector3d(0., -0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz_3dof, upperarm_body, "upperarm_r"); + body_id_3dof[BodyLowerArmRight] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentUpperArm])), rot_y, lowerarm_body, "lowerarm_r"); + body_id_3dof[BodyHandRight] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentLowerArm])), rot_yz, hand_body, "hand_r"); + + // left arm + body_id_3dof[BodyUpperArmLeft] = model_3dof->AddBody (body_id_3dof[BodyUpperTrunk], Xtrans(Vector3d(0., 0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz_3dof, upperarm_body, "upperarm_l"); + body_id_3dof[BodyLowerArmLeft] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentUpperArm])), rot_y, lowerarm_body, "lowerarm_l"); + body_id_3dof[BodyHandLeft] = model_3dof->AppendBody (Xtrans(Vector3d(0., 0., -SegmentLengths[SegmentLowerArm])), rot_yz, hand_body, "hand_l"); + + // head + body_id_3dof[BodyHead] = model_3dof->AddBody (body_id_3dof[BodyUpperTrunk], Xtrans(Vector3d(0., 0.1900, SegmentLengths[SegmentUpperTrunk])), rot_yxz_3dof, upperarm_body, "head"); + } + + void initConstraintSets () { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + unsigned int foot_r_emulated = model_emulated->GetBodyId ("foot_r"); + unsigned int foot_l_emulated = model_emulated->GetBodyId ("foot_l"); + unsigned int hand_r_emulated = model_emulated->GetBodyId ("hand_r"); + unsigned int hand_l_emulated = model_emulated->GetBodyId ("hand_l"); + + constraints_1B1C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_1B1C_emulated.Bind (*model_emulated); + + constraints_1B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_1B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_1B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_1B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_1B4C_emulated.Bind (*model_emulated); + + constraints_4B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_emulated.AddContactConstraint (foot_r_emulated, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + constraints_4B4C_emulated.AddContactConstraint (foot_l_emulated, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_emulated.AddContactConstraint (foot_l_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_emulated.AddContactConstraint (foot_l_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_emulated.AddContactConstraint (foot_l_emulated, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + constraints_4B4C_emulated.AddContactConstraint (hand_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_emulated.AddContactConstraint (hand_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_emulated.AddContactConstraint (hand_r_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_emulated.AddContactConstraint (hand_r_emulated, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + constraints_4B4C_emulated.AddContactConstraint (hand_l_emulated, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_emulated.AddContactConstraint (hand_l_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_emulated.AddContactConstraint (hand_l_emulated, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_emulated.AddContactConstraint (hand_l_emulated, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_emulated.Bind (*model); + + unsigned int foot_r_3dof = model_3dof->GetBodyId ("foot_r"); + unsigned int foot_l_3dof = model_3dof->GetBodyId ("foot_l"); + unsigned int hand_r_3dof = model_3dof->GetBodyId ("hand_r"); + unsigned int hand_l_3dof = model_3dof->GetBodyId ("hand_l"); + + constraints_1B1C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_1B1C_3dof.Bind (*model_3dof); + + constraints_1B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_1B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_1B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_1B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_1B4C_3dof.Bind (*model_3dof); + + constraints_4B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_3dof.AddContactConstraint (foot_r_3dof, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + constraints_4B4C_3dof.AddContactConstraint (foot_l_3dof, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_3dof.AddContactConstraint (foot_l_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_3dof.AddContactConstraint (foot_l_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_3dof.AddContactConstraint (foot_l_3dof, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + constraints_4B4C_3dof.AddContactConstraint (hand_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_3dof.AddContactConstraint (hand_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_3dof.AddContactConstraint (hand_r_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_3dof.AddContactConstraint (hand_r_3dof, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + + constraints_4B4C_3dof.AddContactConstraint (hand_l_3dof, Vector3d (0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_3dof.AddContactConstraint (hand_l_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 1., 0.)); + constraints_4B4C_3dof.AddContactConstraint (hand_l_3dof, Vector3d (0.1, 0., -0.05), Vector3d (0., 0., 1.)); + constraints_4B4C_3dof.AddContactConstraint (hand_l_3dof, Vector3d (-0.1, 0., -0.05), Vector3d (1., 0., 0.)); + constraints_4B4C_3dof.Bind (*model_3dof); + } + + void randomizeStates () { + for (int i = 0; i < q.size(); i++) { + q[i] = 0.4 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + qddot_emulated = qddot; + qddot_3dof = qddot; + } + + Human36 () { + ClearLogOutput(); + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + initParameters(); + model_emulated = new RigidBodyDynamics::Model(); + model_3dof = new RigidBodyDynamics::Model(); + model = model_emulated; + generate(); + initConstraintSets(); + + q = VectorNd::Zero (model_emulated->q_size); + qdot = VectorNd::Zero (model_emulated->qdot_size); + qddot = VectorNd::Zero (model_emulated->qdot_size); + tau = VectorNd::Zero (model_emulated->qdot_size); + + qddot_emulated = VectorNd::Zero (model_emulated->qdot_size); + qddot_3dof= VectorNd::Zero (model_emulated->qdot_size); + }; + ~Human36 () { + delete model_emulated; + delete model_3dof; + } + +}; + +#endif diff --git a/3rdparty/rbdl/tests/ImpulsesTests.cc b/3rdparty/rbdl/tests/ImpulsesTests.cc new file mode 100644 index 0000000..2543a5f --- /dev/null +++ b/3rdparty/rbdl/tests/ImpulsesTests.cc @@ -0,0 +1,279 @@ +#include + +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Constraints.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Kinematics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +struct ImpulsesFixture { + ImpulsesFixture () { + ClearLogOutput(); + model = new Model; + + model->gravity = Vector3d (0., -9.81, 0.); + + // base body + base = Body ( + 1., + Vector3d (0., 1., 0.), + Vector3d (1., 1., 1.) + ); + joint_rotzyx = Joint ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + base_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_rotzyx, base); + + // child body (3 DoF) + child = Body ( + 1., + Vector3d (0., 1., 0.), + Vector3d (1., 1., 1.) + ); + child_id = model->AddBody (base_id, Xtrans (Vector3d (1., 0., 0.)), joint_rotzyx, child); + + Q = VectorNd::Zero(model->dof_count); + QDot = VectorNd::Zero(model->dof_count); + QDDot = VectorNd::Zero(model->dof_count); + Tau = VectorNd::Zero(model->dof_count); + + contact_body_id = child_id; + contact_point = Vector3d (0., 1., 0.); + contact_normal = Vector3d (0., 1., 0.); + + ClearLogOutput(); + } + + ~ImpulsesFixture () { + delete model; + } + Model *model; + + unsigned int base_id, child_id; + Body base, child; + Joint joint_rotzyx; + + VectorNd Q; + VectorNd QDot; + VectorNd QDDot; + VectorNd Tau; + + unsigned int contact_body_id; + Vector3d contact_point; + Vector3d contact_normal; + ConstraintSet constraint_set; +}; + +TEST_FIXTURE(ImpulsesFixture, TestContactImpulse) { + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), NULL, 0.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 1., 0.), NULL, 0.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 0., 1.), NULL, 0.); + + constraint_set.Bind (*model); + + constraint_set.v_plus[0] = 0.; + constraint_set.v_plus[1] = 0.; + constraint_set.v_plus[2] = 0.; + + QDot[0] = 0.1; + QDot[1] = -0.2; + QDot[2] = 0.1; + + Vector3d point_velocity; + { + SUPPRESS_LOGGING; + point_velocity = CalcPointVelocity (*model, Q, QDot, contact_body_id, contact_point, true); + } + + // cout << "Point Velocity = " << point_velocity << endl; + + VectorNd qdot_post (QDot.size()); + ComputeConstraintImpulsesDirect (*model, Q, QDot, constraint_set, qdot_post); + // cout << LogOutput.str() << endl; + // cout << "QdotPost = " << qdot_post << endl; + + { + SUPPRESS_LOGGING; + point_velocity = CalcPointVelocity (*model, Q, qdot_post, contact_body_id, contact_point, true); + } + + // cout << "Point Velocity = " << point_velocity << endl; + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.).data(), point_velocity.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(ImpulsesFixture, TestContactImpulseRotated) { + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), NULL, 0.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 1., 0.), NULL, 0.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 0., 1.), NULL, 0.); + + constraint_set.Bind (*model); + + constraint_set.v_plus[0] = 0.; + constraint_set.v_plus[1] = 0.; + constraint_set.v_plus[2] = 0.; + + Q[0] = 0.2; + Q[1] = -0.5; + Q[2] = 0.1; + Q[3] = -0.4; + Q[4] = -0.1; + Q[5] = 0.4; + + QDot[0] = 0.1; + QDot[1] = -0.2; + QDot[2] = 0.1; + + Vector3d point_velocity; + { + SUPPRESS_LOGGING; + point_velocity = CalcPointVelocity (*model, Q, QDot, contact_body_id, contact_point, true); + } + + // cout << "Point Velocity = " << point_velocity << endl; + VectorNd qdot_post (QDot.size()); + ComputeConstraintImpulsesDirect (*model, Q, QDot, constraint_set, qdot_post); + // cout << LogOutput.str() << endl; + // cout << "QdotPost = " << qdot_post << endl; + + { + SUPPRESS_LOGGING; + point_velocity = CalcPointVelocity (*model, Q, qdot_post, contact_body_id, contact_point, true); + } + + // cout << "Point Velocity = " << point_velocity << endl; + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.).data(), point_velocity.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(ImpulsesFixture, TestContactImpulseRotatedCollisionVelocity) { + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), NULL, 1.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 1., 0.), NULL, 2.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 0., 1.), NULL, 3.); + + constraint_set.Bind (*model); + + constraint_set.v_plus[0] = 1.; + constraint_set.v_plus[1] = 2.; + constraint_set.v_plus[2] = 3.; + + Q[0] = 0.2; + Q[1] = -0.5; + Q[2] = 0.1; + Q[3] = -0.4; + Q[4] = -0.1; + Q[5] = 0.4; + + QDot[0] = 0.1; + QDot[1] = -0.2; + QDot[2] = 0.1; + + Vector3d point_velocity; + { + SUPPRESS_LOGGING; + point_velocity = CalcPointVelocity (*model, Q, QDot, contact_body_id, contact_point, true); + } + + // cout << "Point Velocity = " << point_velocity << endl; + + VectorNd qdot_post (QDot.size()); + ComputeConstraintImpulsesDirect (*model, Q, QDot, constraint_set, qdot_post); + + // cout << LogOutput.str() << endl; + // cout << "QdotPost = " << qdot_post << endl; + + { + SUPPRESS_LOGGING; + point_velocity = CalcPointVelocity (*model, Q, qdot_post, contact_body_id, contact_point, true); + } + + // cout << "Point Velocity = " << point_velocity << endl; + CHECK_ARRAY_CLOSE (Vector3d (1., 2., 3.).data(), point_velocity.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(ImpulsesFixture, TestContactImpulseRangeSpaceSparse) { + Q[0] = 0.2; + Q[1] = -0.5; + Q[2] = 0.1; + Q[3] = -0.4; + Q[4] = -0.1; + Q[5] = 0.4; + + QDot[0] = 0.1; + QDot[1] = -0.2; + QDot[2] = 0.1; + + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), NULL, 1.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 1., 0.), NULL, 2.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 0., 1.), NULL, 3.); + + constraint_set.Bind (*model); + + constraint_set.v_plus[0] = 1.; + constraint_set.v_plus[1] = 2.; + constraint_set.v_plus[2] = 3.; + + ConstraintSet constraint_set_rangespace; + constraint_set_rangespace = constraint_set.Copy(); + constraint_set_rangespace.Bind (*model); + + VectorNd qdot_post_direct (QDot.size()); + ComputeConstraintImpulsesDirect (*model, Q, QDot, constraint_set, qdot_post_direct); + + VectorNd qdot_post_rangespace (QDot.size()); + ComputeConstraintImpulsesRangeSpaceSparse (*model, Q, QDot, constraint_set_rangespace, qdot_post_rangespace); + + Vector3d point_velocity_direct = CalcPointVelocity (*model, Q, qdot_post_direct, contact_body_id, contact_point, true); + Vector3d point_velocity_rangespace = CalcPointVelocity (*model, Q, qdot_post_rangespace, contact_body_id, contact_point, true); + + CHECK_ARRAY_CLOSE (qdot_post_direct.data(), qdot_post_rangespace.data(), qdot_post_direct.rows(), TEST_PREC); + CHECK_ARRAY_CLOSE (Vector3d (1., 2., 3.).data(), point_velocity_rangespace.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(ImpulsesFixture, TestContactImpulseNullSpace) { + Q[0] = 0.2; + Q[1] = -0.5; + Q[2] = 0.1; + Q[3] = -0.4; + Q[4] = -0.1; + Q[5] = 0.4; + + QDot[0] = 0.1; + QDot[1] = -0.2; + QDot[2] = 0.1; + + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (1., 0., 0.), NULL, 1.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 1., 0.), NULL, 2.); + constraint_set.AddContactConstraint(contact_body_id, contact_point, Vector3d (0., 0., 1.), NULL, 3.); + + constraint_set.Bind (*model); + + constraint_set.v_plus[0] = 1.; + constraint_set.v_plus[1] = 2.; + constraint_set.v_plus[2] = 3.; + + ConstraintSet constraint_set_nullspace; + constraint_set_nullspace = constraint_set.Copy(); + constraint_set_nullspace.Bind (*model); + + VectorNd qdot_post_direct (QDot.size()); + ComputeConstraintImpulsesDirect (*model, Q, QDot, constraint_set, qdot_post_direct); + + VectorNd qdot_post_nullspace (QDot.size()); + ComputeConstraintImpulsesNullSpace (*model, Q, QDot, constraint_set, qdot_post_nullspace); + + Vector3d point_velocity_direct = CalcPointVelocity (*model, Q, qdot_post_direct, contact_body_id, contact_point, true); + Vector3d point_velocity_nullspace = CalcPointVelocity (*model, Q, qdot_post_nullspace, contact_body_id, contact_point, true); + + CHECK_ARRAY_CLOSE (qdot_post_direct.data(), qdot_post_nullspace.data(), qdot_post_direct.rows(), TEST_PREC); + CHECK_ARRAY_CLOSE (Vector3d (1., 2., 3.).data(), point_velocity_nullspace.data(), 3, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/InverseDynamicsTests.cc b/3rdparty/rbdl/tests/InverseDynamicsTests.cc new file mode 100644 index 0000000..943bd8d --- /dev/null +++ b/3rdparty/rbdl/tests/InverseDynamicsTests.cc @@ -0,0 +1,76 @@ +#include + +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Dynamics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +struct InverseDynamicsFixture { + InverseDynamicsFixture () { + ClearLogOutput(); + model = new Model; + model->gravity = Vector3d (0., -9.81, 0.); + } + ~InverseDynamicsFixture () { + delete model; + } + Model *model; +}; + +#ifndef USE_SLOW_SPATIAL_ALGEBRA +TEST_FIXTURE(InverseDynamicsFixture, TestInverseForwardDynamicsFloatingBase) { + Body base_body(1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + + model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_body); + + // Initialization of the input vectors + VectorNd Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + VectorNd TauInv = VectorNd::Constant ((size_t) model->dof_count, 0.); + + Q[0] = 1.1; + Q[1] = 1.2; + Q[2] = 1.3; + Q[3] = 0.1; + Q[4] = 0.2; + Q[5] = 0.3; + + QDot[0] = 1.1; + QDot[1] = -1.2; + QDot[2] = 1.3; + QDot[3] = -0.1; + QDot[4] = 0.2; + QDot[5] = -0.3; + + Tau[0] = 2.1; + Tau[1] = 2.2; + Tau[2] = 2.3; + Tau[3] = 1.1; + Tau[4] = 1.2; + Tau[5] = 1.3; + + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + InverseDynamics(*model, Q, QDot, QDDot, TauInv); + + CHECK_ARRAY_CLOSE (Tau.data(), TauInv.data(), Tau.size(), TEST_PREC); +} +#endif diff --git a/3rdparty/rbdl/tests/InverseKinematicsTests.cc b/3rdparty/rbdl/tests/InverseKinematicsTests.cc new file mode 100644 index 0000000..a50065a --- /dev/null +++ b/3rdparty/rbdl/tests/InverseKinematicsTests.cc @@ -0,0 +1,272 @@ +#include + +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/rbdl_utils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" + +#include "Human36Fixture.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-12; + +void print_ik_set (const InverseKinematicsConstraintSet &cs) { + int label_width = 18; + cout.width (label_width); + cout << "lambda: " << cs.lambda << endl; + cout.width (label_width); + cout << "num_steps: " << cs.num_steps << endl; + cout.width (label_width); + cout << "max_steps: " << cs.max_steps << endl; + cout.width (label_width); + cout << "step_tol: " << cs.step_tol << endl; + cout.width (label_width); + cout << "constraint_tol: " << cs.constraint_tol << endl; + cout.width (label_width); + cout << "error_norm: " << cs.error_norm << endl; +} + +/// Checks whether a single point in a 3-link chain can be reached +TEST_FIXTURE ( Human36, ChainSinglePointConstraint ) { + q[HipRightRY] = 0.3; + q[HipRightRX] = 0.3; + q[HipRightRZ] = 0.3; + q[KneeRightRY] = 0.3; + q[AnkleRightRY] = 0.3; + q[AnkleRightRZ] = 0.3; + + Vector3d local_point (1., 0., 0.); + + UpdateKinematicsCustom (*model, &q, NULL, NULL); + Vector3d target_position = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyFootRight], local_point); + + q.setZero(); + + InverseKinematicsConstraintSet cs; + cs.AddPointConstraint (body_id_emulated[BodyFootRight], local_point, target_position); + + VectorNd qres (q); + + bool result = InverseKinematics (*model, q, cs, qres); + if (!result) { + print_ik_set (cs); + } + + CHECK (result); + + CHECK_CLOSE (0., cs.error_norm, TEST_PREC); +} + + +TEST_FIXTURE ( Human36, ManyPointConstraints ) { + + randomizeStates(); + + Vector3d local_point1 (1., 0., 0.); + Vector3d local_point2 (-1., 0., 0.); + Vector3d local_point3 (0., 1., 0.); + Vector3d local_point4 (1., 0., 1.); + Vector3d local_point5 (0.,0.,-1.); + + UpdateKinematicsCustom (*model, &q, NULL, NULL); + Vector3d target_position1 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyFootRight], local_point1); + Vector3d target_position2 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyFootLeft], local_point2); + Vector3d target_position3 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyHandRight], local_point3); + Vector3d target_position4 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyHandLeft], local_point4); + Vector3d target_position5 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyHead], local_point5); + + q.setZero(); + UpdateKinematicsCustom (*model, &q, NULL, NULL); + + InverseKinematicsConstraintSet cs; + cs.AddPointConstraint (body_id_emulated[BodyFootRight], local_point1, target_position1); + cs.AddPointConstraint (body_id_emulated[BodyFootLeft], local_point2, target_position2); + cs.AddPointConstraint (body_id_emulated[BodyHandRight], local_point3, target_position3); + cs.AddPointConstraint (body_id_emulated[BodyHandLeft], local_point4, target_position4); + cs.AddPointConstraint (body_id_emulated[BodyHead], local_point5, target_position5); + VectorNd qres (q); + + bool result = InverseKinematics (*model, q, cs, qres); + + CHECK (result); + + CHECK_CLOSE (0., cs.error_norm, TEST_PREC); + + UpdateKinematicsCustom (*model, &qres, NULL, NULL); + Vector3d result_position1 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyFootRight], local_point1); + Vector3d result_position2 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyFootLeft], local_point2); + Vector3d result_position3 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyHandRight], local_point3); + Vector3d result_position4 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyHandLeft], local_point4); + Vector3d result_position5 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyHead], local_point5); + + CHECK_ARRAY_CLOSE (target_position1.data(), result_position1.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position2.data(), result_position2.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position3.data(), result_position3.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position4.data(), result_position4.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position5.data(), result_position5.data(), 3, TEST_PREC); +} + +/// Checks whether the end of a 3-link chain can aligned with a given +// orientation. +TEST_FIXTURE ( Human36, ChainSingleBodyOrientation ) { + q[HipRightRY] = 0.3; + q[HipRightRX] = 0.3; + q[HipRightRZ] = 0.3; + q[KneeRightRY] = 0.3; + q[AnkleRightRY] = 0.3; + q[AnkleRightRZ] = 0.3; + + UpdateKinematicsCustom (*model, &q, NULL, NULL); + Matrix3d target_orientation = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyFootRight], false); + + InverseKinematicsConstraintSet cs; + cs.AddOrientationConstraint (body_id_emulated[BodyFootRight], target_orientation); + + VectorNd qres (q); + q.setZero(); + + bool result = InverseKinematics (*model, q, cs, qres); + + CHECK (result); +} + +TEST_FIXTURE ( Human36, ManyBodyOrientations ) { + + randomizeStates(); + + UpdateKinematicsCustom (*model, &q, NULL, NULL); + Matrix3d target_orientation1 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyFootRight], false); + Matrix3d target_orientation2 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyFootLeft], false); + Matrix3d target_orientation3 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyHandRight], false); + Matrix3d target_orientation4 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyHandLeft], false); + Matrix3d target_orientation5 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyHead], false); + + q.setZero(); + + InverseKinematicsConstraintSet cs; + cs.AddOrientationConstraint (body_id_emulated[BodyFootRight], target_orientation1); + cs.AddOrientationConstraint (body_id_emulated[BodyFootLeft], target_orientation2); + cs.AddOrientationConstraint (body_id_emulated[BodyHandRight], target_orientation3); + cs.AddOrientationConstraint (body_id_emulated[BodyHandLeft], target_orientation4); + cs.AddOrientationConstraint (body_id_emulated[BodyHead], target_orientation5); + + VectorNd qres (q); + + bool result = InverseKinematics (*model, q, cs, qres); + + CHECK (result); + + CHECK_CLOSE (0., cs.error_norm, TEST_PREC); + + UpdateKinematicsCustom (*model, &qres, NULL, NULL); + Matrix3d result_orientation1 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyFootRight], false); + Matrix3d result_orientation2 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyFootLeft], false); + Matrix3d result_orientation3 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyHandRight], false); + Matrix3d result_orientation4 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyHandLeft], false); + Matrix3d result_orientation5 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyHead], false); + + CHECK_ARRAY_CLOSE (target_orientation1.data(), result_orientation1.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation2.data(), result_orientation2.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation3.data(), result_orientation3.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation4.data(), result_orientation4.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation5.data(), result_orientation5.data(), 9, TEST_PREC); +} + +TEST_FIXTURE ( Human36, ChainSingleBodyFullConstraint ) { + q[HipRightRY] = 0.3; + q[HipRightRX] = 0.3; + q[HipRightRZ] = 0.3; + q[KneeRightRY] = 0.3; + q[AnkleRightRY] = 0.3; + q[AnkleRightRZ] = 0.3; + Vector3d local_point (1., 0., 0.); + + UpdateKinematicsCustom (*model, &q, NULL, NULL); + Matrix3d target_orientation = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyFootRight], false); + Vector3d target_position = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyFootRight], local_point); + + InverseKinematicsConstraintSet cs; + cs.AddFullConstraint(body_id_emulated[BodyFootRight],local_point,target_position, target_orientation); + + VectorNd qres (q); + q.setZero(); + + bool result = InverseKinematics (*model, q, cs, qres); + + CHECK (result); +} + +TEST_FIXTURE ( Human36, ManyBodyFullConstraints ) { + + randomizeStates(); + + Vector3d local_point1 (1., 0., 0.); + Vector3d local_point2 (-1., 0., 0.); + Vector3d local_point3 (0., 1., 0.); + Vector3d local_point4 (1., 0., 1.); + Vector3d local_point5 (0.,0.,-1.); + + UpdateKinematicsCustom (*model, &q, NULL, NULL); + + Vector3d target_position1 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyFootRight], local_point1); + Vector3d target_position2 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyFootLeft], local_point2); + Vector3d target_position3 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyHandRight], local_point3); + Vector3d target_position4 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyHandLeft], local_point4); + Vector3d target_position5 = CalcBodyToBaseCoordinates (*model, q, body_id_emulated[BodyHead], local_point5); + + Matrix3d target_orientation1 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyFootRight], false); + Matrix3d target_orientation2 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyFootLeft], false); + Matrix3d target_orientation3 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyHandRight], false); + Matrix3d target_orientation4 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyHandLeft], false); + Matrix3d target_orientation5 = CalcBodyWorldOrientation (*model, q, body_id_emulated[BodyHead], false); + + InverseKinematicsConstraintSet cs; + cs.AddFullConstraint (body_id_emulated[BodyFootRight], local_point1, target_position1, target_orientation1); + cs.AddFullConstraint (body_id_emulated[BodyFootLeft], local_point2, target_position2, target_orientation2); + cs.AddFullConstraint (body_id_emulated[BodyHandRight], local_point3, target_position3, target_orientation3); + cs.AddFullConstraint (body_id_emulated[BodyHandLeft], local_point4, target_position4, target_orientation4); + cs.AddFullConstraint (body_id_emulated[BodyHead], local_point5, target_position5, target_orientation5); + cs.step_tol = 1e-12; + + q.setZero(); + + VectorNd qres (q); + + bool result = InverseKinematics (*model, q, cs, qres); + + CHECK (result); + + CHECK_CLOSE (0., cs.error_norm, cs.step_tol); + + UpdateKinematicsCustom (*model, &qres, NULL, NULL); + Matrix3d result_orientation1 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyFootRight], false); + Matrix3d result_orientation2 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyFootLeft], false); + Matrix3d result_orientation3 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyHandRight], false); + Matrix3d result_orientation4 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyHandLeft], false); + Matrix3d result_orientation5 = CalcBodyWorldOrientation (*model, qres, body_id_emulated[BodyHead], false); + + Vector3d result_position1 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyFootRight], local_point1); + Vector3d result_position2 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyFootLeft], local_point2); + Vector3d result_position3 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyHandRight], local_point3); + Vector3d result_position4 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyHandLeft], local_point4); + Vector3d result_position5 = CalcBodyToBaseCoordinates (*model, qres, body_id_emulated[BodyHead], local_point5); + + CHECK_ARRAY_CLOSE (target_position1.data(), result_position1.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position2.data(), result_position2.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position3.data(), result_position3.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position4.data(), result_position4.data(), 3, TEST_PREC); + CHECK_ARRAY_CLOSE (target_position5.data(), result_position5.data(), 3, TEST_PREC); + + CHECK_ARRAY_CLOSE (target_orientation1.data(), result_orientation1.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation2.data(), result_orientation2.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation3.data(), result_orientation3.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation4.data(), result_orientation4.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (target_orientation5.data(), result_orientation5.data(), 9, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/KinematicsTests.cc b/3rdparty/rbdl/tests/KinematicsTests.cc new file mode 100644 index 0000000..e939431 --- /dev/null +++ b/3rdparty/rbdl/tests/KinematicsTests.cc @@ -0,0 +1,680 @@ +#include + +#include + +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" + +#include "Human36Fixture.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-12; + +struct KinematicsFixture { + KinematicsFixture () { + ClearLogOutput(); + model = new Model; + + /* Basically a model like this, where X are the Center of Masses + * and the CoM of the last (3rd) body comes out of the Y=X=0 plane. + * + * X + * * + * _/ + * _/ (-Z) + * Z / + * *---* + * | + * | + * Z | + * O---* + * Y + */ + + body_a = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + joint_a = Joint( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_a_id = model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_a, body_a); + + body_b = Body (1., Vector3d (0., 1., 0.), Vector3d (1., 1., 1.)); + joint_b = Joint ( SpatialVector (0., 1., 0., 0., 0., 0.)); + + body_b_id = model->AddBody(body_a_id, Xtrans(Vector3d(1., 0., 0.)), joint_b, body_b); + + body_c = Body (1., Vector3d (0., 0., 1.), Vector3d (1., 1., 1.)); + joint_c = Joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + body_c_id = model->AddBody(body_b_id, Xtrans(Vector3d(0., 1., 0.)), joint_c, body_c); + + body_d = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + joint_c = Joint ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + body_d_id = model->AddBody(body_c_id, Xtrans(Vector3d(0., 0., -1.)), joint_c, body_d); + + Q = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + QDDot = VectorNd::Constant ((size_t) model->dof_count, 0.); + Tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + ClearLogOutput(); + } + + ~KinematicsFixture () { + delete model; + } + Model *model; + + unsigned int body_a_id, body_b_id, body_c_id, body_d_id; + Body body_a, body_b, body_c, body_d; + Joint joint_a, joint_b, joint_c, joint_d; + + VectorNd Q; + VectorNd QDot; + VectorNd QDDot; + VectorNd Tau; +}; + +struct KinematicsFixture6DoF { + KinematicsFixture6DoF () { + ClearLogOutput(); + model = new Model; + + model->gravity = Vector3d (0., -9.81, 0.); + + /* + * + * X Contact point (ref child) + * | + * Base | + * / body | + * O-------* + * \ + * Child body + */ + + // base body (3 DoF) + base = Body ( + 1., + Vector3d (0.5, 0., 0.), + Vector3d (1., 1., 1.) + ); + joint_rotzyx = Joint ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + base_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_rotzyx, base); + + // child body (3 DoF) + child = Body ( + 1., + Vector3d (0., 0.5, 0.), + Vector3d (1., 1., 1.) + ); + child_id = model->AddBody (base_id, Xtrans (Vector3d (1., 0., 0.)), joint_rotzyx, child); + + Q = VectorNd::Constant (model->mBodies.size() - 1, 0.); + QDot = VectorNd::Constant (model->mBodies.size() - 1, 0.); + QDDot = VectorNd::Constant (model->mBodies.size() - 1, 0.); + Tau = VectorNd::Constant (model->mBodies.size() - 1, 0.); + + ClearLogOutput(); + } + + ~KinematicsFixture6DoF () { + delete model; + } + Model *model; + + unsigned int base_id, child_id; + Body base, child; + Joint joint_rotzyx; + + VectorNd Q; + VectorNd QDot; + VectorNd QDDot; + VectorNd Tau; +}; + + + +TEST_FIXTURE(KinematicsFixture, TestPositionNeutral) { + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + Vector3d body_position; + + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_a_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_b_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 1., 0.), CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 1., -1.), CalcBodyToBaseCoordinates(*model, Q, body_d_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); +} + +TEST_FIXTURE(KinematicsFixture, TestPositionBaseRotated90Deg) { + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + + Q[0] = 0.5 * M_PI; + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + Vector3d body_position; + + // cout << LogOutput.str() << endl; + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_a_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (0., 1., 0.), CalcBodyToBaseCoordinates(*model, Q, body_b_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (-1., 1., 0.),CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (-1., 1., -1.), CalcBodyToBaseCoordinates(*model, Q, body_d_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); +} + +TEST_FIXTURE(KinematicsFixture, TestPositionBaseRotatedNeg45Deg) { + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + + Q[0] = -0.25 * M_PI; + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + Vector3d body_position; + + // cout << LogOutput.str() << endl; + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_a_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (0.707106781186547, -0.707106781186547, 0.), CalcBodyToBaseCoordinates(*model, Q, body_b_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (sqrt(2.0), 0., 0.),CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (sqrt(2.0), 0., -1.), CalcBodyToBaseCoordinates(*model, Q, body_d_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); +} + +TEST_FIXTURE(KinematicsFixture, TestPositionBodyBRotated90Deg) { + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + Q[1] = 0.5 * M_PI; + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + Vector3d body_position; + + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_a_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_b_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 1., 0.),CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (0., 1., 0.),CalcBodyToBaseCoordinates(*model, Q, body_d_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); +} + +TEST_FIXTURE(KinematicsFixture, TestPositionBodyBRotatedNeg45Deg) { + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + Q[1] = -0.25 * M_PI; + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + Vector3d body_position; + + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_a_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 0., 0.), CalcBodyToBaseCoordinates(*model, Q, body_b_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1., 1., 0.),CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); + CHECK_ARRAY_CLOSE (Vector3d (1 + 0.707106781186547, 1., -0.707106781186547), CalcBodyToBaseCoordinates(*model, Q, body_d_id, Vector3d (0., 0., 0.), true), 3, TEST_PREC ); +} + +TEST_FIXTURE(KinematicsFixture, TestCalcBodyToBaseCoordinates) { + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + CHECK_ARRAY_CLOSE ( + Vector3d (1., 2., 0.), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 1., 0.)), + 3, TEST_PREC + ); +} + +TEST_FIXTURE(KinematicsFixture, TestCalcBodyToBaseCoordinatesRotated) { + Q[2] = 0.5 * M_PI; + + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + CHECK_ARRAY_CLOSE ( + Vector3d (1., 1., 0.).data(), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), false).data(), + 3, TEST_PREC + ); + + CHECK_ARRAY_CLOSE ( + Vector3d (0., 1., 0.).data(), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 1., 0.), false).data(), + 3, TEST_PREC + ); + + // Rotate the other way round + Q[2] = -0.5 * M_PI; + + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + CHECK_ARRAY_CLOSE ( + Vector3d (1., 1., 0.), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), false), + 3, TEST_PREC + ); + + CHECK_ARRAY_CLOSE ( + Vector3d (2., 1., 0.), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 1., 0.), false), + 3, TEST_PREC + ); + + // Rotate around the base + Q[0] = 0.5 * M_PI; + Q[2] = 0.; + + // We call ForwardDynamics() as it updates the spatial transformation + // matrices + ForwardDynamics(*model, Q, QDot, Tau, QDDot); + + CHECK_ARRAY_CLOSE ( + Vector3d (-1., 1., 0.), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 0., 0.), false), + 3, TEST_PREC + ); + + CHECK_ARRAY_CLOSE ( + Vector3d (-2., 1., 0.), + CalcBodyToBaseCoordinates(*model, Q, body_c_id, Vector3d (0., 1., 0.), false), + 3, TEST_PREC + ); + + // cout << LogOutput.str() << endl; +} + +TEST(TestCalcPointJacobian) { + Model model; + Body base_body (1., Vector3d (0., 0., 0.), Vector3d (1., 1., 1.)); + + unsigned int base_body_id = model.AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + base_body); + + VectorNd Q = VectorNd::Constant ((size_t) model.dof_count, 0.); + VectorNd QDot = VectorNd::Constant ((size_t) model.dof_count, 0.); + MatrixNd G = MatrixNd::Constant (3, model.dof_count, 0.); + Vector3d point_position (1.1, 1.2, 2.1); + Vector3d point_velocity_ref; + Vector3d point_velocity; + + Q[0] = 1.1; + Q[1] = 1.2; + Q[2] = 1.3; + Q[3] = 0.7; + Q[4] = 0.8; + Q[5] = 0.9; + + QDot[0] = -1.1; + QDot[1] = 2.2; + QDot[2] = 1.3; + QDot[3] = -2.7; + QDot[4] = 1.8; + QDot[5] = -2.9; + + // Compute the reference velocity + point_velocity_ref = CalcPointVelocity (model, Q, QDot, base_body_id, point_position); + + G.setZero(); + CalcPointJacobian (model, Q, base_body_id, point_position, G); + + point_velocity = G * QDot; + + CHECK_ARRAY_CLOSE ( + point_velocity_ref.data(), + point_velocity.data(), + 3, TEST_PREC + ); +} + +TEST_FIXTURE(KinematicsFixture, TestInverseKinematicSimple) { + std::vector body_ids; + std::vector body_points; + std::vector target_pos; + + Q[0] = 0.2; + Q[1] = 0.1; + Q[2] = 0.1; + + VectorNd Qres = VectorNd::Zero ((size_t) model->dof_count); + + unsigned int body_id = body_d_id; + Vector3d body_point = Vector3d (1., 0., 0.); + Vector3d target (1.3, 0., 0.); + + body_ids.push_back (body_d_id); + body_points.push_back (body_point); + target_pos.push_back (target); + + ClearLogOutput(); + bool res = InverseKinematics (*model, Q, body_ids, body_points, target_pos, Qres); + // cout << LogOutput.str() << endl; + CHECK_EQUAL (true, res); + + UpdateKinematicsCustom (*model, &Qres, NULL, NULL); + + Vector3d effector; + effector = CalcBodyToBaseCoordinates(*model, Qres, body_id, body_point, false); + + CHECK_ARRAY_CLOSE (target.data(), effector.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(KinematicsFixture6DoF, TestInverseKinematicUnreachable) { + std::vector body_ids; + std::vector body_points; + std::vector target_pos; + + Q[0] = 0.2; + Q[1] = 0.1; + Q[2] = 0.1; + + VectorNd Qres = VectorNd::Zero ((size_t) model->dof_count); + + unsigned int body_id = child_id; + Vector3d body_point = Vector3d (1., 0., 0.); + Vector3d target (2.2, 0., 0.); + + body_ids.push_back (body_id); + body_points.push_back (body_point); + target_pos.push_back (target); + + ClearLogOutput(); + bool res = InverseKinematics (*model, Q, body_ids, body_points, target_pos, Qres, 1.0e-8, 0.9, 1000); + // cout << LogOutput.str() << endl; + CHECK_EQUAL (true, res); + + UpdateKinematicsCustom (*model, &Qres, NULL, NULL); + + Vector3d effector; + effector = CalcBodyToBaseCoordinates(*model, Qres, body_id, body_point, false); + + CHECK_ARRAY_CLOSE (Vector3d (2.0, 0., 0.).data(), effector.data(), 3, 1.0e-7); +} + +TEST_FIXTURE(KinematicsFixture6DoF, TestInverseKinematicTwoPoints) { + std::vector body_ids; + std::vector body_points; + std::vector target_pos; + + Q[0] = 0.2; + Q[1] = 0.1; + Q[2] = 0.1; + + VectorNd Qres = VectorNd::Zero ((size_t) model->dof_count); + + unsigned int body_id = child_id; + Vector3d body_point = Vector3d (1., 0., 0.); + Vector3d target (2., 0., 0.); + + body_ids.push_back (body_id); + body_points.push_back (body_point); + target_pos.push_back (target); + + body_ids.push_back (base_id); + body_points.push_back (Vector3d (0.6, 1.0, 0.)); + target_pos.push_back (Vector3d (0.5, 1.1, 0.)); + + ClearLogOutput(); + bool res = InverseKinematics (*model, Q, body_ids, body_points, target_pos, Qres, 1.0e-3, 0.9, 200); + CHECK_EQUAL (true, res); + + // cout << LogOutput.str() << endl; + UpdateKinematicsCustom (*model, &Qres, NULL, NULL); + + Vector3d effector; + + // testing with very low precision + effector = CalcBodyToBaseCoordinates(*model, Qres, body_ids[0], body_points[0], false); + CHECK_ARRAY_CLOSE (target_pos[0].data(), effector.data(), 3, 1.0e-1); + + effector = CalcBodyToBaseCoordinates(*model, Qres, body_ids[1], body_points[1], false); + CHECK_ARRAY_CLOSE (target_pos[1].data(), effector.data(), 3, 1.0e-1); +} + +TEST ( FixedJointBodyCalcBodyToBase ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), Joint(JointTypeFixed), fixed_body); + + VectorNd Q_zero = VectorNd::Zero (model.dof_count); + Vector3d base_coords = CalcBodyToBaseCoordinates (model, Q_zero, fixed_body_id, Vector3d (1., 1., 0.1)); + + CHECK_ARRAY_CLOSE (Vector3d (1., 2., 0.1).data(), base_coords.data(), 3, TEST_PREC); +} + +TEST ( FixedJointBodyCalcBodyToBaseRotated ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector(0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(1., 0., 0.)), Joint(JointTypeFixed), fixed_body); + + VectorNd Q = VectorNd::Zero (model.dof_count); + + ClearLogOutput(); + Q[0] = M_PI * 0.5; + Vector3d base_coords = CalcBodyToBaseCoordinates (model, Q, fixed_body_id, Vector3d (1., 0., 0.)); + // cout << LogOutput.str() << endl; + + CHECK_ARRAY_CLOSE (Vector3d (0., 2., 0.).data(), base_coords.data(), 3, TEST_PREC); +} + +TEST ( FixedJointBodyCalcBaseToBody ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), Joint(JointTypeFixed), fixed_body); + + VectorNd Q_zero = VectorNd::Zero (model.dof_count); + Vector3d base_coords = CalcBaseToBodyCoordinates (model, Q_zero, fixed_body_id, Vector3d (1., 2., 0.1)); + + CHECK_ARRAY_CLOSE (Vector3d (1., 1., 0.1).data(), base_coords.data(), 3, TEST_PREC); +} + +TEST ( FixedJointBodyCalcBaseToBodyRotated ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(1., 0., 0.)), Joint(JointTypeFixed), fixed_body); + + VectorNd Q = VectorNd::Zero (model.dof_count); + + ClearLogOutput(); + Q[0] = M_PI * 0.5; + Vector3d base_coords = CalcBaseToBodyCoordinates (model, Q, fixed_body_id, Vector3d (0., 2., 0.)); + // cout << LogOutput.str() << endl; + + CHECK_ARRAY_CLOSE (Vector3d (1., 0., 0.).data(), base_coords.data(), 3, TEST_PREC); +} + +TEST ( FixedJointBodyWorldOrientation ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + + SpatialTransform transform = Xrotz(0.25) * Xtrans (Vector3d (1., 2., 3.)); + unsigned int fixed_body_id = model.AppendBody (transform, Joint(JointTypeFixed), fixed_body); + + VectorNd Q_zero = VectorNd::Zero (model.dof_count); + Matrix3d orientation = CalcBodyWorldOrientation (model, Q_zero, fixed_body_id); + + Matrix3d reference = transform.E; + + CHECK_ARRAY_CLOSE (reference.data(), orientation.data(), 9, TEST_PREC); +} + +TEST ( FixedJointCalcPointJacobian ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + + SpatialTransform transform = Xrotz(0.25) * Xtrans (Vector3d (1., 2., 3.)); + unsigned int fixed_body_id = model.AppendBody (transform, Joint(JointTypeFixed), fixed_body); + + VectorNd Q = VectorNd::Zero (model.dof_count); + VectorNd QDot = VectorNd::Zero (model.dof_count); + + Q[0] = 1.1; + QDot[0] = 1.2; + + Vector3d point_position (1., 0., 0.); + + MatrixNd G = MatrixNd::Zero (3, model.dof_count); + CalcPointJacobian (model, Q, fixed_body_id, point_position, G); + Vector3d point_velocity_jacobian = G * QDot; + Vector3d point_velocity_reference = CalcPointVelocity (model, Q, QDot, fixed_body_id, point_position); + + CHECK_ARRAY_CLOSE (point_velocity_reference.data(), point_velocity_jacobian.data(), 3, TEST_PREC); +} + +TEST_FIXTURE ( Human36, SpatialJacobianSimple ) { + randomizeStates(); + + unsigned int foot_r_id = model->GetBodyId ("foot_r"); + MatrixNd G (MatrixNd::Zero (6, model->dof_count)); + + CalcBodySpatialJacobian (*model, q, foot_r_id, G); + + UpdateKinematicsCustom (*model, &q, &qdot, NULL); + SpatialVector v_body = SpatialVector(G * qdot); + + CHECK_ARRAY_CLOSE (model->v[foot_r_id].data(), v_body.data(), 6, TEST_PREC); +} + +TEST_FIXTURE ( Human36, SpatialJacobianFixedBody ) { + randomizeStates(); + + unsigned int uppertrunk_id = model->GetBodyId ("uppertrunk"); + MatrixNd G (MatrixNd::Zero (6, model->dof_count)); + + CalcBodySpatialJacobian (*model, q, uppertrunk_id, G); + + unsigned int fixed_body_id = uppertrunk_id - model->fixed_body_discriminator; + unsigned int movable_parent = model->mFixedBodies[fixed_body_id].mMovableParent; + + UpdateKinematicsCustom (*model, &q, &qdot, NULL); + SpatialVector v_body = SpatialVector(G * qdot); + + SpatialVector v_fixed_body = model->mFixedBodies[fixed_body_id].mParentTransform.apply (model->v[movable_parent]); + + CHECK_ARRAY_CLOSE (v_fixed_body.data(), v_body.data(), 6, TEST_PREC); +} + +TEST_FIXTURE ( Human36, CalcPointJacobian6D ) { + randomizeStates(); + + unsigned int foot_r_id = model->GetBodyId ("foot_r"); + Vector3d point_local (1.1, 2.2, 3.3); + + // Compute the 6-D velocity using the 6-D Jacobian + MatrixNd G (MatrixNd::Zero (6, model->dof_count)); + CalcPointJacobian6D (*model, q, foot_r_id, point_local, G); + SpatialVector v_foot_0_jac = SpatialVector (G * qdot); + + // Compute the 6-D velocity by transforming the body velocity to the + // reference point and aligning it with the base coordinate system + Vector3d r_point = CalcBodyToBaseCoordinates (*model, q, foot_r_id, point_local); + SpatialTransform X_foot (Matrix3d::Identity(), r_point); + UpdateKinematicsCustom (*model, &q, &qdot, NULL); + SpatialVector v_foot_0_ref = X_foot.apply(model->X_base[foot_r_id].inverse().apply(model->v[foot_r_id])); + + CHECK_ARRAY_CLOSE (v_foot_0_ref.data(), v_foot_0_jac.data(), 6, TEST_PREC); +} + +TEST_FIXTURE ( Human36, CalcPointVelocity6D ) { + randomizeStates(); + + unsigned int foot_r_id = model->GetBodyId ("foot_r"); + Vector3d point_local (1.1, 2.2, 3.3); + + // Compute the 6-D velocity + SpatialVector v_foot_0 = CalcPointVelocity6D (*model, q, qdot, foot_r_id, point_local); + + // Compute the 6-D velocity by transforming the body velocity to the + // reference point and aligning it with the base coordinate system + Vector3d r_point = CalcBodyToBaseCoordinates (*model, q, foot_r_id, point_local); + SpatialTransform X_foot (Matrix3d::Identity(), r_point); + UpdateKinematicsCustom (*model, &q, &qdot, NULL); + SpatialVector v_foot_0_ref = X_foot.apply(model->X_base[foot_r_id].inverse().apply(model->v[foot_r_id])); + + CHECK_ARRAY_CLOSE (v_foot_0_ref.data(), v_foot_0.data(), 6, TEST_PREC); +} + +TEST_FIXTURE ( Human36, CalcPointAcceleration6D ) { + randomizeStates(); + + unsigned int foot_r_id = model->GetBodyId ("foot_r"); + Vector3d point_local (1.1, 2.2, 3.3); + + // Compute the 6-D acceleration + SpatialVector a_foot_0 = CalcPointAcceleration6D (*model, q, qdot, qddot, foot_r_id, point_local); + + // Compute the 6-D acceleration by adding the coriolis term to the + // acceleration of the body and transforming the result to the + // point and align it with the base coordinate system. + Vector3d r_point = CalcBodyToBaseCoordinates (*model, q, foot_r_id, point_local); + Vector3d v_foot_0 = CalcPointVelocity (*model, q, qdot, foot_r_id, point_local); + SpatialVector rdot (0., 0., 0., v_foot_0[0], v_foot_0[1], v_foot_0[2]); + + SpatialTransform X_foot (Matrix3d::Identity(), r_point); + UpdateKinematicsCustom (*model, &q, &qdot, NULL); + SpatialVector a_foot_0_ref = X_foot.apply( + model->X_base[foot_r_id].inverse().apply(model->a[foot_r_id]) + - crossm(rdot, + model->X_base[foot_r_id].inverse().apply(model->v[foot_r_id]) + ) + ); + + CHECK_ARRAY_CLOSE (a_foot_0_ref.data(), a_foot_0.data(), 6, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/LoopConstraintsTests.cc b/3rdparty/rbdl/tests/LoopConstraintsTests.cc new file mode 100644 index 0000000..d8baf49 --- /dev/null +++ b/3rdparty/rbdl/tests/LoopConstraintsTests.cc @@ -0,0 +1,2146 @@ +#include +#include "rbdl/rbdl.h" +#include + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-11; + +// Reduce an angle to the (-pi, pi] range. +static double inRange(double angle) { + while(angle > M_PI) { + angle -= 2. * M_PI; + } + while(angle <= -M_PI) { + angle += 2. * M_PI; + } + return angle; +} + +struct FourBarLinkage { + + FourBarLinkage() + : model() + , cs() + , q() + , qd() + , qdd() + , tau() + , l1(2.) + , l2(2.) + , m1(2.) + , m2(2.) + , idB1(0) + , idB2(0) + , idB3(0) + , idB4(0) + , idB5(0) + , X_p(Xtrans(Vector3d(l2, 0., 0.))) + , X_s(Xtrans(Vector3d(0., 0., 0.))) { + + Body link1 = Body(m1, Vector3d(0.5 * l1, 0., 0.) + , Vector3d(0., 0., m1 * l1 * l1 / 3.)); + Body link2 = Body(m2, Vector3d(0.5 * l2, 0., 0.) + , Vector3d(0., 0., m2 * l2 * l2 / 3.)); + Vector3d vector3d_zero = Vector3d::Zero(); + Body virtual_body(0., vector3d_zero, vector3d_zero); + Joint joint_rev_z(JointTypeRevoluteZ); + + idB1 = model.AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_rev_z, link1); + idB2 = model.AddBody(idB1, Xtrans(Vector3d(l1, 0., 0.)), joint_rev_z, link2); + idB3 = model.AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_rev_z, link1); + idB4 = model.AddBody(idB3, Xtrans(Vector3d(l1, 0., 0.)), joint_rev_z, link2); + idB5 = model.AddBody(idB4, Xtrans(Vector3d(l2, 0., 0.)), joint_rev_z + , virtual_body); + + cs.AddLoopConstraint(idB2, idB5, X_p, X_s, SpatialVector(0,0,0,1,0,0), 0.1); + cs.AddLoopConstraint(idB2, idB5, X_p, X_s, SpatialVector(0,0,0,0,1,0), 0.1); + cs.AddLoopConstraint(idB2, idB5, X_p, X_s, SpatialVector(0,0,1,0,0,0), 0.1); + + cs.Bind(model); + + q = VectorNd::Zero(model.dof_count); + qd = VectorNd::Zero(model.dof_count); + qdd = VectorNd::Zero(model.dof_count); + tau = VectorNd::Zero(model.dof_count); + + } + + Model model; + ConstraintSet cs; + + VectorNd q; + VectorNd qd; + VectorNd qdd; + VectorNd tau; + + double l1; + double l2; + double m1; + double m2; + + unsigned int idB1; + unsigned int idB2; + unsigned int idB3; + unsigned int idB4; + unsigned int idB5; + + SpatialTransform X_p; + SpatialTransform X_s; + +}; + +struct FloatingFourBarLinkage { + + FloatingFourBarLinkage() + : model() + , cs() + , q() + , qd() + , qdd() + , tau() + , l1(2.) + , l2(2.) + , m1(2.) + , m2(2.) + , idB0(0) + , idB1(0) + , idB2(0) + , idB3(0) + , idB4(0) + , idB5(0) + , X_p(Xtrans(Vector3d(l2, 0., 0.))) + , X_s(Xtrans(Vector3d(0., 0., 0.))) { + + Body link1 = Body(m1, Vector3d(0.5 * l1, 0., 0.) + , Vector3d(0., 0., m1 * l1 * l1 / 3.)); + Body link2 = Body(m2, Vector3d(0.5 * l2, 0., 0.) + , Vector3d(0., 0., m2 * l2 * l2 / 3.)); + Vector3d vector3d_zero = Vector3d::Zero(); + Body virtual_body(0., vector3d_zero, vector3d_zero); + Joint joint_trans(JointTypeTranslationXYZ); + Joint joint_rev_z(JointTypeRevoluteZ); + + idB0 = model.AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint_trans + , virtual_body); + idB1 = model.AddBody(idB0, Xtrans(Vector3d(0., 0., 0.)), joint_rev_z, link1); + idB2 = model.AddBody(idB1, Xtrans(Vector3d(l1, 0., 0.)), joint_rev_z, link2); + idB3 = model.AddBody(idB0, Xtrans(Vector3d(0., 0., 0.)), joint_rev_z, link1); + idB4 = model.AddBody(idB3, Xtrans(Vector3d(l1, 0., 0.)), joint_rev_z, link2); + idB5 = model.AddBody(idB4, Xtrans(Vector3d(l2, 0., 0.)), joint_rev_z + , virtual_body); + + cs.AddContactConstraint(idB0, Vector3d::Zero(), Vector3d(1,0,0)); + cs.AddContactConstraint(idB0, Vector3d::Zero(), Vector3d(0,1,0)); + cs.AddContactConstraint(idB0, Vector3d::Zero(), Vector3d(0,0,1)); + cs.AddLoopConstraint(idB2, idB5, X_p, X_s, SpatialVector(0,0,0,1,0,0), 0.1); + cs.AddLoopConstraint(idB2, idB5, X_p, X_s, SpatialVector(0,0,0,0,1,0), 0.1); + cs.AddLoopConstraint(idB2, idB5, X_p, X_s, SpatialVector(0,0,1,0,0,0), 0.1); + + cs.Bind(model); + + q = VectorNd::Zero(model.dof_count); + qd = VectorNd::Zero(model.dof_count); + qdd = VectorNd::Zero(model.dof_count); + tau = VectorNd::Zero(model.dof_count); + + } + + Model model; + ConstraintSet cs; + + VectorNd q; + VectorNd qd; + VectorNd qdd; + VectorNd tau; + + double l1; + double l2; + double m1; + double m2; + + unsigned int idB0; + unsigned int idB1; + unsigned int idB2; + unsigned int idB3; + unsigned int idB4; + unsigned int idB5; + + SpatialTransform X_p; + SpatialTransform X_s; + +}; + +struct SliderCrank3D { + + SliderCrank3D() + : model() + , cs() + , q() + , qd() + , id_p(0) + , id_s(0) + , X_p() + , X_s() { + + double slider_mass = 5.; + double slider_height = 0.1; + double crank_link1_mass = 3.; + double crank_link1_length = 1.; + double crank_link2_mass = 1.; + double crank_link2_radius = 0.2; + double crank_link2_length = 3.; + double crank_link1_height = crank_link2_length - crank_link1_length + + slider_height; + + Body slider(slider_mass, Vector3d::Zero(), Vector3d(1., 1., 1.)); + Body crankLink1(crank_link1_mass + , Vector3d(0.5 * crank_link1_length, 0., 0.) + , Vector3d(0., 0. + , crank_link1_mass * crank_link1_length * crank_link1_length / 3.)); + Body crankLink2(crank_link2_mass + , Vector3d(0.5 * crank_link2_length, 0., 0.) + , Vector3d(crank_link2_mass * crank_link2_radius * crank_link2_radius / 2. + , crank_link2_mass * (3. * crank_link2_radius * crank_link2_radius + + crank_link2_length * crank_link2_length) / 12. + , crank_link2_mass * (3. * crank_link2_radius * crank_link2_radius + + crank_link2_length * crank_link2_length) / 12.)); + + Joint joint_rev_z(JointTypeRevoluteZ); + Joint joint_sphere(JointTypeEulerZYX); + Joint joint_prs_x(SpatialVector(0.,0.,0.,1.,0.,0.)); + + id_p = model.AddBody(0 + , SpatialTransform() + , joint_prs_x, slider); + unsigned int id_b1 = model.AddBody(0 + , Xroty(-0.5*M_PI) * Xtrans(Vector3d(0., 0., crank_link1_height)) + , joint_rev_z, crankLink1); + id_s = model.AddBody(id_b1 + , Xroty(M_PI) * Xtrans(Vector3d(crank_link1_length, 0., 0.)) + , joint_sphere, crankLink2); + + X_p = Xtrans(Vector3d(0., 0., slider_height)); + X_s = SpatialTransform(roty(-0.5 * M_PI), Vector3d(crank_link2_length, 0, 0)); + + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,0,1,0,0), 0.1); + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,0,0,1,0), 0.1); + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,0,0,0,1), 0.1); + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,1,0,0,0), 0.1); + + cs.Bind(model); + + q = VectorNd::Zero(model.dof_count); + qd = VectorNd::Zero(model.dof_count); + qdd = VectorNd::Zero(model.dof_count); + tau = VectorNd::Zero(model.dof_count); + + Matrix3d rot_ps + = (CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E).transpose() + * CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + assert(rot_ps(0,0) - 1. < TEST_PREC); + assert(rot_ps(1,1) - 1. < TEST_PREC); + assert(rot_ps(2,2) - 1. < TEST_PREC); + assert(rot_ps(0,1) < TEST_PREC); + assert(rot_ps(0,2) < TEST_PREC); + assert(rot_ps(1,0) < TEST_PREC); + assert(rot_ps(1,2) < TEST_PREC); + assert(rot_ps(2,0) < TEST_PREC); + assert(rot_ps(2,1) < TEST_PREC); + assert((CalcBodyToBaseCoordinates(model, q, id_p, X_p.r) + - CalcBodyToBaseCoordinates(model, q, id_s, X_s.r)).norm() < TEST_PREC); + + } + + Model model; + ConstraintSet cs; + + VectorNd q; + VectorNd qd; + VectorNd qdd; + VectorNd tau; + + unsigned int id_p; + unsigned int id_s; + SpatialTransform X_p; + SpatialTransform X_s; + +}; + +struct SliderCrank3DSphericalJoint { + + SliderCrank3DSphericalJoint() + : model() + , cs() + , q() + , qd() + , id_p(0) + , id_s(0) + , X_p() + , X_s() { + + double slider_mass = 5.; + double slider_height = 0.1; + double crank_link1_mass = 3.; + double crank_link1_length = 1.; + double crank_link2_mass = 1.; + double crank_link2_radius = 0.2; + double crank_link2_length = 3.; + double crank_link1_height = crank_link2_length - crank_link1_length + + slider_height; + + Body slider(slider_mass, Vector3d::Zero(), Vector3d(1., 1., 1.)); + Body crankLink1(crank_link1_mass + , Vector3d(0.5 * crank_link1_length, 0., 0.) + , Vector3d(0., 0. + , crank_link1_mass * crank_link1_length * crank_link1_length / 3.)); + Body crankLink2(crank_link2_mass + , Vector3d(0.5 * crank_link2_length, 0., 0.) + , Vector3d(crank_link2_mass * crank_link2_radius * crank_link2_radius / 2. + , crank_link2_mass * (3. * crank_link2_radius * crank_link2_radius + + crank_link2_length * crank_link2_length) / 12. + , crank_link2_mass * (3. * crank_link2_radius * crank_link2_radius + + crank_link2_length * crank_link2_length) / 12.)); + + Joint joint_rev_z(JointTypeRevoluteZ); + Joint joint_sphere(JointTypeSpherical); + Joint joint_prs_x(SpatialVector(0.,0.,0.,1.,0.,0.)); + + id_p = model.AddBody(0 + , SpatialTransform() + , joint_prs_x, slider); + unsigned int id_b1 = model.AddBody(0 + , Xroty(-0.5*M_PI) * Xtrans(Vector3d(0., 0., crank_link1_height)) + , joint_rev_z, crankLink1); + id_s = model.AddBody(id_b1 + , Xroty(M_PI) * Xtrans(Vector3d(crank_link1_length, 0., 0.)) + , joint_sphere, crankLink2); + + X_p = Xtrans(Vector3d(0., 0., slider_height)); + X_s = SpatialTransform(roty(-0.5 * M_PI), Vector3d(crank_link2_length, 0, 0)); + + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,0,1,0,0), 0.1); + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,0,0,1,0), 0.1); + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,0,0,0,1), 0.1); + cs.AddLoopConstraint(id_p, id_s, X_p, X_s, SpatialVector(0,0,1,0,0,0), 0.1); + + cs.Bind(model); + + q = VectorNd::Zero(model.q_size); + qd = VectorNd::Zero(model.dof_count); + qdd = VectorNd::Zero(model.dof_count); + tau = VectorNd::Zero(model.dof_count); + + Matrix3d rot_ps + = (CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E).transpose() + * CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + assert(rot_ps(0,0) - 1. < TEST_PREC); + assert(rot_ps(1,1) - 1. < TEST_PREC); + assert(rot_ps(2,2) - 1. < TEST_PREC); + assert(rot_ps(0,1) < TEST_PREC); + assert(rot_ps(0,2) < TEST_PREC); + assert(rot_ps(1,0) < TEST_PREC); + assert(rot_ps(1,2) < TEST_PREC); + assert(rot_ps(2,0) < TEST_PREC); + assert(rot_ps(2,1) < TEST_PREC); + assert((CalcBodyToBaseCoordinates(model, q, id_p, X_p.r) + - CalcBodyToBaseCoordinates(model, q, id_s, X_s.r)).norm() < TEST_PREC); + + } + + Model model; + ConstraintSet cs; + + VectorNd q; + VectorNd qd; + VectorNd qdd; + VectorNd tau; + + unsigned int id_p; + unsigned int id_s; + SpatialTransform X_p; + SpatialTransform X_s; + +}; + +TEST_FIXTURE(FourBarLinkage, TestFourBarLinkageConstraintErrors) { + VectorNd err = VectorNd::Zero(cs.size()); + Vector3d pos1; + Vector3d pos2; + Vector3d posErr; + Matrix3d rot_p; + double angleErr; + + // Test in zero position. + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.; + q[4] = 0.; + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_CLOSE(0., err[0], TEST_PREC); + CHECK_CLOSE(0., err[1], TEST_PREC); + CHECK_CLOSE(0., err[2], TEST_PREC); + + // Test in non-zero position. + q[0] = M_PI * 3 / 4; + q[1] = -M_PI; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = 0.; + angleErr = sin(-0.5 * M_PI); + + pos1 = CalcBodyToBaseCoordinates(model, q, idB2, X_p.r); + pos2 = CalcBodyToBaseCoordinates(model, q, idB5, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, idB2).transpose() * X_p.E; + posErr = rot_p.transpose() * (pos2 - pos1); + + assert(std::fabs(posErr[1]) < TEST_PREC); + assert(std::fabs(posErr[2]) < TEST_PREC); + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_CLOSE(posErr[0], err[0], TEST_PREC); + CHECK_CLOSE(0., err[1], TEST_PREC); + CHECK_CLOSE(angleErr, err[2], TEST_PREC); + + // Test in non-zero position. + q[0] = 0.; + q[1] = 0.; + q[2] = M_PI + 0.1; + q[3] = 0.; + q[4] = 0.; + angleErr = sin(-q[0] - q[1] + q[2] + q[3] + q[4]); + + pos1 = CalcBodyToBaseCoordinates(model, q, idB2, X_p.r); + pos2 = CalcBodyToBaseCoordinates(model, q, idB5, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, idB2).transpose() * X_p.E; + posErr = rot_p.transpose() * (pos2 - pos1); + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_CLOSE(posErr[0], err[0], TEST_PREC); + CHECK_CLOSE(posErr[1], err[1], TEST_PREC); + CHECK_CLOSE(angleErr, err[2], TEST_PREC); + + // Test in non-zero position. + q[0] = 0.8; + q[1] = -0.4; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = 0.; + angleErr = sin(-q[0] - q[1] + q[2] + q[3] + q[4]); + + pos1 = CalcBodyToBaseCoordinates(model, q, idB2, X_p.r); + pos2 = CalcBodyToBaseCoordinates(model, q, idB5, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, idB2).transpose() * X_p.E; + posErr = rot_p.transpose() * (pos2 - pos1); + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_CLOSE(posErr[0], err[0], TEST_PREC); + CHECK_CLOSE(posErr[1], err[1], TEST_PREC); + CHECK_CLOSE(angleErr, err[2], TEST_PREC); +} + +TEST_FIXTURE(FourBarLinkage, TestFourBarLinkageConstraintJacobian) { + MatrixNd G(MatrixNd::Zero(cs.size(), q.size())); + VectorNd err(VectorNd::Zero(cs.size())); + VectorNd errRef(VectorNd::Zero(cs.size())); + + // Zero Q configuration, both arms of the 4-bar laying on the x-axis + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.; + q[4] = 0.; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = -1.; + qd[1] = -1.; + qd[2] = -1.; + qd[3] = -1.; + qd[4] = 0.; + assert((CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + - CalcPointVelocity6D(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + CalcConstraintsJacobian(model, q, cs, G); + + err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Both arms of the 4-bar laying on the y-axis + q[0] = 0.5 * M_PI; + q[1] = 0.; + q[2] = 0.5 * M_PI; + q[3] = 0.; + q[4] = 0.; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = -1.; + qd[1] = -1.; + qd[2] = -1.; + qd[3] = -1.; + qd[4] = 0.; + assert((CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + - CalcPointVelocity6D(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + CalcConstraintsJacobian(model, q, cs, G); + + err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Arms symmetric wrt y axis. + q[0] = M_PI * 3 / 4; + q[1] = -0.5 * M_PI; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = q[0] + q[1] - q[2] - q[3]; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = -1.; + qd[1] = -1.; + qd[2] = -2.; + qd[3] = +1.; + qd[4] = -1.; + assert((CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + - CalcPointVelocity6D(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + CalcConstraintsJacobian(model, q, cs, G); + + err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FourBarLinkage, TestFourBarLinkageConstraintsVelocityErrors) { + VectorNd errd(VectorNd::Zero(cs.size())); + VectorNd errdRef(VectorNd::Zero(cs.size())); + MatrixNd G(cs.size(), model.dof_count); + + // Arms symmetric wrt y axis. + q[0] = M_PI * 3 / 4; + q[1] = -0.5 * M_PI; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = q[0] + q[1] - q[2] - q[3]; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = -1.; + qd[1] = -1.; + qd[2] = -2.; + qd[3] = +1.; + qd[4] = -1.; + + CalcConstraintsVelocityError(model, q, qd, cs, errd); + + CHECK_ARRAY_CLOSE(errdRef, errd, cs.size(), TEST_PREC); + + // Invalid velocities. + qd[0] = -1.; + qd[1] = -1.; + qd[2] = 0.; + qd[3] = 0.; + qd[4] = 0.; + + CalcConstraintsVelocityError(model, q, qd, cs, errd); + CalcConstraintsJacobian(model, q, cs, G); + errdRef = G * qd; + CHECK_ARRAY_CLOSE(errdRef, errd, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FourBarLinkage, TestFourBarLinkageQAssembly) { + VectorNd weights(q.size()); + VectorNd err(cs.size()); + VectorNd errRef(VectorNd::Zero(cs.size())); + + weights[0] = 1.; + weights[1] = 0.; + weights[2] = 1.; + weights[3] = 0.; + weights[4] = 0.; + + VectorNd qRef = VectorNd::Zero(q.size()); + qRef[0] = M_PI * 3 / 4; + qRef[1] = -0.5 * M_PI; + qRef[2] = M_PI - qRef[0]; + qRef[3] = -qRef[1]; + qRef[4] = qRef[0] + qRef[1] - qRef[2] - qRef[3]; + assert(qRef[0] + qRef[1] - qRef[2] - qRef[3] - qRef[4] == 0.); + + bool success; + + // Feasible initial guess. + VectorNd qInit = VectorNd::Zero(q.size()); + qInit = qRef; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[0] + q[1]), inRange(q[2] + q[3] + q[4]), TEST_PREC); + CHECK_CLOSE(qInit[0], q[0], TEST_PREC); + CHECK_CLOSE(qInit[2], q[2], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Perturbed initial guess. + qInit[0] = qRef[0]; + qInit[1] = qRef[1]; + qInit[2] = qRef[2]; + qInit[3] = qRef[3]; + qInit[4] = qRef[4] + 0.05; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK(success); + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[0] + q[1]), inRange(q[2] + q[3] + q[4]), TEST_PREC); + CHECK_CLOSE(qInit[0], q[0], TEST_PREC); + CHECK_CLOSE(qInit[2], q[2], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Perturbed initial guess. + qInit[0] = qRef[0] - 0.2; + qInit[1] = qRef[1] - 0.; + qInit[2] = qRef[2] + 0.1; + qInit[3] = qRef[3] - 0.03; + qInit[4] = qRef[4] + 0.05; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK(success); + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[0] + q[1]), inRange(q[2] + q[3] + q[4]), TEST_PREC); + CHECK_CLOSE(qInit[0], q[0], TEST_PREC); + CHECK_CLOSE(qInit[2], q[2], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Perturbed initial guess. + qInit[0] = qRef[0] + 0.01; + qInit[1] = qRef[1] + 0.02; + qInit[2] = qRef[2] - 0.03; + qInit[3] = qRef[3] - 0.02; + qInit[4] = qRef[4] + 0.01; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK(success); + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[0] + q[1]), inRange(q[2] + q[3] + q[4]), TEST_PREC); + CHECK_CLOSE(qInit[0], q[0], TEST_PREC); + CHECK_CLOSE(qInit[2], q[2], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FourBarLinkage, TestFourBarLinkageQDotAssembly) { + VectorNd weights(q.size()); + + weights[0] = 1.; + weights[1] = 0.; + weights[2] = 1.; + weights[3] = 0.; + weights[4] = 0.; + + q[0] = M_PI * 3 / 4; + q[1] = -0.5 * M_PI; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = q[0] + q[1] - q[2] - q[3]; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + VectorNd qdInit = VectorNd::Zero(q.size()); + qdInit[0] = 0.01; + qdInit[1] = 0.5; + qdInit[2] = -0.7; + qdInit[3] = -0.5; + qdInit[4] = 0.3; + + CalcAssemblyQDot(model, q, qdInit, cs, qd, weights); + MatrixNd G(MatrixNd::Zero(cs.size(), q.size())); + VectorNd err(VectorNd::Zero(cs.size())); + VectorNd errRef(VectorNd::Zero(cs.size())); + CalcConstraintsJacobian(model, q, cs, G); + err = G * qd; + + CHECK_ARRAY_CLOSE(CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + , CalcPointVelocity6D(model, q, qd, idB5, X_s.r), 6, TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + CHECK_CLOSE(qdInit[0], qd[0], TEST_PREC); + CHECK_CLOSE(qdInit[2], qd[2], TEST_PREC); +} + +TEST_FIXTURE(FourBarLinkage, TestFourBarLinkageForwardDynamics) { + VectorNd qddDirect; + VectorNd qddNullSpace; + + cs.SetSolver(LinearSolverColPivHouseholderQR); + +#ifndef RBDL_USE_SIMPLE_MATH + // SimpleMath has no solver that can solve the system in this configuration. + // Configuration 1. + + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.; + q[4] = 0.; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = 0.; + qd[4] = 0.; + assert(qd[0] + qd[1] - qd[2] - qd[3] - qd[4] == 0.); + assert((CalcPointVelocity(model, q, qd, idB2, X_p.r) + - CalcPointVelocity(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + tau[0] = 1.; + tau[1] = -2.; + tau[2] = 3.; + tau[3] = -5.; + tau[4] = 7.; + + qddDirect = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qddDirect); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddDirect, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddDirect, idB5, X_s.r) + , 6, TEST_PREC); + + qddNullSpace = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qddNullSpace); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB5, X_s.r) + , 6, TEST_PREC); +#endif + + // Configuration 2. + + q[0] = M_PI * 3 / 4; + q[1] = -0.5 * M_PI; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = q[0] + q[1] - q[2] - q[3]; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = -1.; + qd[1] = -1.; + qd[2] = -2.; + qd[3] = +1.; + qd[4] = -1.; + assert(qd[0] + qd[1] - qd[2] - qd[3] - qd[4] == 0.); + assert((CalcPointVelocity(model, q, qd, idB2, X_p.r) + - CalcPointVelocity(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + tau[0] = 1.; + tau[1] = -2.; + tau[2] = 3.; + tau[3] = -5.; + tau[4] = 7.; + + qddDirect = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qddDirect); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddDirect, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddDirect, idB5, X_s.r) + , 6, TEST_PREC); + + qddNullSpace = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qddNullSpace); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB5, X_s.r) + , 6, TEST_PREC); + + // Note: + // The Range Space Sparse method can't be used because the H matrix has a 0 on + // the diagonal and the LTL factorization tries to divide by 0. + + // Note: + // LinearSolverPartialPivLU does not work because the A matrix in the dynamic + // system is not invertible. + + // Note: + // LinearSolverHouseholderQR sometimes does not work well when the system is + // in a singular configuration. +} + +TEST_FIXTURE(FourBarLinkage, FourBarLinkageImpulse) { + VectorNd qdPlusDirect(qd.size()); + VectorNd qdPlusRangeSpaceSparse(qd.size()); + VectorNd qdPlusNullSpace(qd.size()); + VectorNd errd(cs.size()); + + q[0] = M_PI * 3 / 4; + q[1] = -0.5 * M_PI; + q[2] = M_PI - q[0]; + q[3] = -q[1]; + q[4] = q[0] + q[1] - q[2] - q[3]; + assert(q[0] + q[1] - q[2] - q[3] - q[4] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + cs.v_plus[0] = 1.; + cs.v_plus[1] = 2.; + cs.v_plus[2] = 3.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errd); + + CHECK_ARRAY_CLOSE(cs.v_plus, errd, cs.size(), TEST_PREC); + + cs.v_plus[0] = 0.; + cs.v_plus[1] = 0.; + cs.v_plus[2] = 0.; + + qd[0] = 1.; + qd[1] = 2.; + qd[2] = 3.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errd); + + CHECK_ARRAY_CLOSE(cs.v_plus, errd, cs.size(), TEST_PREC); +} + + + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DConstraintErrors) { + VectorNd err(VectorNd::Zero(cs.size())); + VectorNd errRef(VectorNd::Zero(cs.size())); + Vector3d pos_p; + Vector3d pos_s; + Matrix3d rot_p; + Matrix3d rot_s; + Matrix3d rot_ps; + Vector3d rotationVec; + + // Test in zero position. + CalcConstraintsPositionError(model, q, cs, err); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Test in another configurations. + + q[0] = 0.4; + q[1] = 0.25 * M_PI; + q[2] = -0.25 * M_PI; + q[3] = 0.01; + q[4] = 0.01; + + CalcConstraintsPositionError(model, q, cs, err); + + pos_p = CalcBodyToBaseCoordinates(model, q, id_p, X_p.r); + pos_s = CalcBodyToBaseCoordinates(model, q, id_s, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E; + rot_s = CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + rot_ps = rot_p.transpose() * rot_s; + rotationVec = - 0.5 * Vector3d + ( rot_ps(1,2) - rot_ps(2,1) + , rot_ps(2,0) - rot_ps(0,2) + , rot_ps(0,1) - rot_ps(1,0)); + errRef.block<3,1>(0,0) = pos_s - pos_p; + errRef[3] = rotationVec[2]; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DConstraintJacobian) { + MatrixNd G(MatrixNd::Zero(cs.size(), q.size())); + + // Test in zero position. + + G.setZero(); + CalcConstraintsJacobian(model, q, cs, G); + + VectorNd errRef(VectorNd::Zero(cs.size())); + VectorNd err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DConstraintsVelocityErrors) { + VectorNd errd(VectorNd::Zero(cs.size())); + VectorNd errdRef(VectorNd::Zero(cs.size())); + MatrixNd G(cs.size(), model.dof_count); + VectorNd qWeights(q.size()); + VectorNd qInit(q.size()); + bool success; + + // Compute assembled configuration. + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + qInit[2] = -0.25 * M_PI; + qInit[3] = 0.1; + qInit[4] = 0.1; + + success = CalcAssemblyQ(model, qInit, cs, q, qWeights, TEST_PREC); + assert(success); + + // Some random velocity. + qd[0] = -0.2; + qd[1] = 0.1 * M_PI; + qd[2] = -0.1 * M_PI; + qd[3] = 0.; + qd[4] = 0.1 * M_PI; + + CalcConstraintsVelocityError(model, q, qd, cs, errd); + CalcConstraintsJacobian(model, q, cs, G); + errdRef = G * qd; + + CHECK_ARRAY_CLOSE(errdRef, errd, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DAssemblyQ) { + VectorNd weights(q.size()); + VectorNd qInit(q.size()); + + Vector3d pos_p; + Vector3d pos_s; + Matrix3d rot_p; + Matrix3d rot_s; + Matrix3d rot_ps; + + bool success; + + weights[0] = 1.; + weights[1] = 1.; + weights[2] = 1.; + weights[3] = 1.; + weights[4] = 1.; + + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + qInit[2] = -0.25 * M_PI; + qInit[3] = 0.1; + qInit[4] = 0.1; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, TEST_PREC); + pos_p = CalcBodyToBaseCoordinates(model, q, id_p, X_p.r); + pos_s = CalcBodyToBaseCoordinates(model, q, id_s, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E; + rot_s = CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + rot_ps = rot_p.transpose() * rot_s; + + CHECK(success); + CHECK_ARRAY_CLOSE(pos_p, pos_s, 3, TEST_PREC); + CHECK_CLOSE(0., rot_ps(0,1) - rot_ps(1,0), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DAssemblyQDot) { + VectorNd qWeights(q.size()); + VectorNd qdWeights(q.size()); + VectorNd qInit(q.size()); + VectorNd qdInit(q.size()); + + SpatialVector vel_p; + SpatialVector vel_s; + + bool success; + + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + qInit[2] = -0.25 * M_PI; + qInit[3] = 0.1; + qInit[4] = 0.1; + + qdWeights[0] = 1.; + qdWeights[1] = 0.; + qdWeights[2] = 0.; + qdWeights[3] = 0.; + qdWeights[4] = 0.; + + qdInit[0] = -0.2; + qdInit[1] = 0.1 * M_PI; + qdInit[2] = -0.1 * M_PI; + qdInit[3] = 0.; + qdInit[4] = 0.1 * M_PI; + + success = CalcAssemblyQ(model, qInit, cs, q, qWeights, TEST_PREC); + assert(success); + + CalcAssemblyQDot(model, q, qdInit, cs, qd, qdWeights); + + vel_p = CalcPointVelocity6D(model, q, qd, id_p, X_p.r); + vel_s = CalcPointVelocity6D(model, q, qd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(vel_p[i], vel_s[i], TEST_PREC); + } + CHECK_CLOSE(qdInit[0], qd[0], TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DForwardDynamics) { + VectorNd qWeights(q.size()); + VectorNd qdWeights(q.size()); + VectorNd qInit(q.size()); + VectorNd qdInit(q.size()); + + SpatialVector acc_p; + SpatialVector acc_s; + + bool success; + +#ifndef RBDL_USE_SIMPLE_MATH + // The SimpleMath solver cannot solve the system close to a singular + // configuration. + // Test with zero q and qdot. + + tau[0] = 0.12; + tau[1] = -0.3; + tau[2] = 0.05; + tau[3] = 0.7; + tau[4] = -0.1; + + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsRangeSpaceSparse(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } +#endif + + // Compute non-zero assembly q and qdot; + + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + qInit[2] = -0.25 * M_PI; + qInit[3] = 0.1; + qInit[4] = 0.1; + + qdWeights[0] = 1.; + qdWeights[1] = 0.; + qdWeights[2] = 0.; + qdWeights[3] = 0.; + qdWeights[4] = 0.; + + qdInit[0] = -0.2; + qdInit[1] = 0.1 * M_PI; + qdInit[2] = -0.1 * M_PI; + qdInit[3] = 0.; + qdInit[4] = 0.1 * M_PI; + + qdInit.setZero(); + + success = CalcAssemblyQ(model, qInit, cs, q, qWeights, TEST_PREC); + assert(success); + CalcAssemblyQDot(model, q, qdInit, cs, qd, qdWeights); + + Matrix3d rot_ps + = (CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E).transpose() + * CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + assert((CalcBodyToBaseCoordinates(model, q, id_p, X_p.r) + - CalcBodyToBaseCoordinates(model, q, id_p, X_p.r)).norm() < TEST_PREC); + assert(rot_ps(0,1) - rot_ps(0,1) < TEST_PREC); + assert((CalcPointVelocity6D(model, q, qd, id_p, X_p.r) + -CalcPointVelocity6D(model, q, qd, id_p, X_p.r)).norm() < TEST_PREC); + + // Test with non-zero q and qdot. + + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsRangeSpaceSparse(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } +} + +TEST_FIXTURE(SliderCrank3D, TestSliderCrank3DImpulse) { + VectorNd qdPlusDirect(qd.size()); + VectorNd qdPlusRangeSpaceSparse(qd.size()); + VectorNd qdPlusNullSpace(qd.size()); + VectorNd errdDirect(cs.size()); + VectorNd errdSpaceSparse(cs.size()); + VectorNd errdNullSpace(cs.size()); + + VectorNd qWeights(q.size()); + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + VectorNd qInit(q.size()); + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + qInit[2] = -0.25 * M_PI; + qInit[3] = 0.1; + qInit[4] = 0.1; + + bool success = CalcAssemblyQ(model, qInit, cs, q, qWeights, TEST_PREC); + assert(success); + + cs.v_plus[0] = 1.; + cs.v_plus[1] = 2.; + cs.v_plus[2] = 3.; + cs.v_plus[3] = 4.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errdDirect); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdDirect, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesRangeSpaceSparse(model, q, qd, cs + , qdPlusRangeSpaceSparse); + CalcConstraintsVelocityError(model, q, qdPlusRangeSpaceSparse, cs + , errdSpaceSparse); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdSpaceSparse, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesNullSpace(model, q, qd, cs, qdPlusNullSpace); + CalcConstraintsVelocityError(model, q, qdPlusNullSpace, cs, errdNullSpace); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdNullSpace, cs.size(), TEST_PREC); + + cs.v_plus[0] = 0.; + cs.v_plus[1] = 0.; + cs.v_plus[2] = 0.; + cs.v_plus[3] = 0.; + + qd[0] = 1.; + qd[1] = 2.; + qd[2] = 3.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errdDirect); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdDirect, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesRangeSpaceSparse(model, q, qd, cs + , qdPlusRangeSpaceSparse); + CalcConstraintsVelocityError(model, q, qdPlusRangeSpaceSparse, cs + , errdSpaceSparse); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdSpaceSparse, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesNullSpace(model, q, qd, cs, qdPlusNullSpace); + CalcConstraintsVelocityError(model, q, qdPlusNullSpace, cs, errdNullSpace); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdNullSpace, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage + , TestFloatingFourBarLinkageConstraintErrors) { + + VectorNd err = VectorNd::Zero(cs.size()); + Vector3d pos0; + Vector3d pos1; + Vector3d pos2; + Vector3d posErr; + Matrix3d rot_p; + double angleErr; + + // Test in zero position. + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.; + q[4] = 0.; + q[5] = 0.; + q[6] = 0.; + q[7] = 0.; + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_CLOSE(0., err[0], TEST_PREC); + CHECK_CLOSE(0., err[1], TEST_PREC); + CHECK_CLOSE(0., err[2], TEST_PREC); + CHECK_CLOSE(0., err[3], TEST_PREC); + CHECK_CLOSE(0., err[4], TEST_PREC); + CHECK_CLOSE(0., err[5], TEST_PREC); + + // Test in non-zero position. + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = M_PI * 3 / 4; + q[4] = -M_PI; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = 0.; + angleErr = sin(-0.5 * M_PI); + + pos0 = CalcBodyToBaseCoordinates(model, q, idB0, Vector3d::Zero()); + pos1 = CalcBodyToBaseCoordinates(model, q, idB2, X_p.r); + pos2 = CalcBodyToBaseCoordinates(model, q, idB5, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, idB2).transpose() * X_p.E; + posErr = rot_p.transpose() * (pos2 - pos1); + + assert(std::fabs(posErr[1]) < TEST_PREC); + assert(std::fabs(posErr[2]) < TEST_PREC); + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_ARRAY_CLOSE(Vector3d(1.,2.,3.), pos0, 3, TEST_PREC); + CHECK_CLOSE(posErr[0], err[3], TEST_PREC); + CHECK_CLOSE(0., err[4], TEST_PREC); + CHECK_CLOSE(angleErr, err[5], TEST_PREC); + + // Test in non-zero position. + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = 0.; + q[4] = 0.; + q[5] = M_PI + 0.1; + q[6] = 0.; + q[7] = 0.; + angleErr = sin(-q[3] - q[4] + q[5] + q[6] + q[7]); + + pos0 = CalcBodyToBaseCoordinates(model, q, idB0, Vector3d::Zero()); + pos1 = CalcBodyToBaseCoordinates(model, q, idB2, X_p.r); + pos2 = CalcBodyToBaseCoordinates(model, q, idB5, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, idB2).transpose() * X_p.E; + posErr = rot_p.transpose() * (pos2 - pos1); + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_ARRAY_CLOSE(Vector3d(1.,2.,3.), pos0, 3, TEST_PREC); + CHECK_CLOSE(posErr[0], err[3], TEST_PREC); + CHECK_CLOSE(posErr[1], err[4], TEST_PREC); + CHECK_CLOSE(angleErr, err[5], TEST_PREC); + + // Test in non-zero position. + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = 0.8; + q[4] = -0.4; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = 0.; + angleErr = sin(-q[3] - q[4] + q[5] + q[6] + q[7]); + + pos0 = CalcBodyToBaseCoordinates(model, q, idB0, Vector3d::Zero()); + pos1 = CalcBodyToBaseCoordinates(model, q, idB2, X_p.r); + pos2 = CalcBodyToBaseCoordinates(model, q, idB5, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, idB2).transpose() * X_p.E; + posErr = rot_p.transpose() * (pos2 - pos1); + + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_ARRAY_CLOSE(Vector3d(1.,2.,3.), pos0, 3, TEST_PREC); + CHECK_CLOSE(posErr[0], err[3], TEST_PREC); + CHECK_CLOSE(posErr[1], err[4], TEST_PREC); + CHECK_CLOSE(angleErr, err[5], TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage + , TestFloatingFourBarLinkageConstraintJacobian) { + + MatrixNd G(MatrixNd::Zero(cs.size(), q.size())); + VectorNd err(VectorNd::Zero(cs.size())); + VectorNd errRef(VectorNd::Zero(cs.size())); + + // Zero Q configuration, both arms of the 4-bar laying on the x-axis + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.; + q[4] = 0.; + q[5] = 0.; + q[6] = 0.; + q[7] = 0.; + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = -1.; + qd[4] = -1.; + qd[5] = -1.; + qd[6] = -1.; + qd[7] = 0.; + assert((CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + - CalcPointVelocity6D(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + CalcConstraintsJacobian(model, q, cs, G); + + err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Both arms of the 4-bar laying on the y-axis + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.5 * M_PI; + q[4] = 0.; + q[5] = 0.5 * M_PI; + q[6] = 0.; + q[7] = 0.; + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = -1.; + qd[4] = -1.; + qd[5] = -1.; + qd[6] = -1.; + qd[7] = 0.; + assert((CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + - CalcPointVelocity6D(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + CalcConstraintsJacobian(model, q, cs, G); + + err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Arms symmetric wrt y axis. + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = M_PI * 3 / 4; + q[4] = -0.5 * M_PI; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = q[3] + q[4] - q[5] - q[6]; + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = -1.; + qd[4] = -1.; + qd[5] = -2.; + qd[6] = +1.; + qd[7] = -1.; + assert((CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + - CalcPointVelocity6D(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + CalcConstraintsJacobian(model, q, cs, G); + + err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage + , TestFloatingFourBarLinkageConstraintsVelocityErrors) { + + VectorNd errd(VectorNd::Zero(cs.size())); + VectorNd errdRef(VectorNd::Zero(cs.size())); + MatrixNd G(cs.size(), model.dof_count); + + // Arms symmetric wrt y axis. + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = M_PI * 3 / 4; + q[4] = -0.5 * M_PI; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = q[3] + q[4] - q[5] - q[6]; + + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = -1.; + qd[4] = -1.; + qd[5] = -2.; + qd[6] = +1.; + qd[7] = -1.; + + CalcConstraintsVelocityError(model, q, qd, cs, errd); + CHECK_ARRAY_CLOSE(errdRef, errd, cs.size(), TEST_PREC); + + // Invalid velocities. + qd[0] = -1.; + qd[1] = -1.; + qd[2] = 0.; + qd[3] = -1.; + qd[4] = -1.; + qd[5] = 0.; + qd[6] = 0.; + qd[7] = 0.; + + CalcConstraintsVelocityError(model, q, qd, cs, errd); + CalcConstraintsJacobian(model, q, cs, G); + errdRef = G * qd; + CHECK_ARRAY_CLOSE(errdRef, errd, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage, TestFloatingFourBarLinkageQAssembly) { + VectorNd weights(q.size()); + VectorNd err(cs.size()); + VectorNd errRef(VectorNd::Zero(cs.size())); + + weights[0] = 0.; + weights[1] = 0.; + weights[2] = 0.; + weights[3] = 1.; + weights[4] = 0.; + weights[5] = 1.; + weights[6] = 0.; + weights[7] = 0.; + + VectorNd qRef = VectorNd::Zero(q.size()); + qRef[0] = 1.; + qRef[1] = 2.; + qRef[2] = 3.; + qRef[3] = M_PI * 3 / 4; + qRef[4] = -0.5 * M_PI; + qRef[5] = M_PI - qRef[3]; + qRef[6] = -qRef[4]; + qRef[7] = qRef[3] + qRef[4] - qRef[5] - qRef[6]; + + assert(qRef[3] + qRef[4] - qRef[5] - qRef[6] - qRef[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, qRef, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, qRef, idB5, X_s.r)).norm() < TEST_PREC); + + bool success; + + // Feasible initial guess. + VectorNd qInit = VectorNd::Zero(q.size()); + qInit = qRef; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[3] + q[4]), inRange(q[5] + q[6] + q[7]), TEST_PREC); + CHECK_CLOSE(qInit[3], q[3], TEST_PREC); + CHECK_CLOSE(qInit[5], q[5], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Perturbed initial guess. + qInit[3] = qRef[3]; + qInit[4] = qRef[4]; + qInit[5] = qRef[5]; + qInit[6] = qRef[6]; + qInit[7] = qRef[7] + 0.05; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK(success); + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[3] + q[4]), inRange(q[5] + q[6] + q[7]), TEST_PREC); + CHECK_CLOSE(qInit[3], q[3], TEST_PREC); + CHECK_CLOSE(qInit[5], q[5], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Perturbed initial guess. + qInit[3] = qRef[3] - 0.2; + qInit[4] = qRef[4] - 0.; + qInit[5] = qRef[5] + 0.1; + qInit[6] = qRef[6] - 0.03; + qInit[7] = qRef[7] + 0.05; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK(success); + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[3] + q[4]), inRange(q[5] + q[6] + q[7]), TEST_PREC); + CHECK_CLOSE(qInit[3], q[3], TEST_PREC); + CHECK_CLOSE(qInit[5], q[5], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Perturbed initial guess. + qInit[3] = qRef[3] + 0.01; + qInit[4] = qRef[4] + 0.02; + qInit[5] = qRef[5] - 0.03; + qInit[6] = qRef[6] - 0.02; + qInit[7] = qRef[7] + 0.01; + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1.e-12); + CalcConstraintsPositionError(model, q, cs, err); + + CHECK(success); + CHECK_ARRAY_CLOSE(CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + , CalcBodyToBaseCoordinates(model, q, idB5, X_s.r), 3, TEST_PREC); + CHECK_CLOSE(inRange(q[3] + q[4]), inRange(q[5] + q[6] + q[7]), TEST_PREC); + CHECK_CLOSE(qInit[3], q[3], TEST_PREC); + CHECK_CLOSE(qInit[5], q[5], TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage, TestFloatingFourBarLinkageQDotAssembly) { + VectorNd weights(q.size()); + + weights[0] = 0.; + weights[1] = 0.; + weights[2] = 0.; + weights[3] = 1.; + weights[4] = 0.; + weights[5] = 1.; + weights[6] = 0.; + weights[7] = 0.; + + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = M_PI * 3 / 4; + q[4] = -0.5 * M_PI; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = q[3] + q[4] - q[5] - q[6]; + + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + VectorNd qdInit = VectorNd::Zero(q.size()); + qdInit[0] = 1.; + qdInit[1] = 2.; + qdInit[2] = 3.; + qdInit[3] = 0.01; + qdInit[4] = 0.5; + qdInit[5] = -0.7; + qdInit[6] = -0.5; + qdInit[7] = 0.3; + + CalcAssemblyQDot(model, q, qdInit, cs, qd, weights); + MatrixNd G(MatrixNd::Zero(cs.size(), q.size())); + VectorNd err(VectorNd::Zero(cs.size())); + VectorNd errRef(VectorNd::Zero(cs.size())); + CalcConstraintsJacobian(model, q, cs, G); + err = G * qd; + + CHECK_ARRAY_CLOSE(CalcPointVelocity6D(model, q, qd, idB2, X_p.r) + , CalcPointVelocity6D(model, q, qd, idB5, X_s.r), 6, TEST_PREC); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + CHECK_CLOSE(qdInit[3], qd[3], TEST_PREC); + CHECK_CLOSE(qdInit[5], qd[5], TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage + , TestFloatingFourBarLinkageForwardDynamics) { + + VectorNd qddDirect; + VectorNd qddNullSpace; + + cs.SetSolver(LinearSolverColPivHouseholderQR); + +#ifndef RBDL_USE_SIMPLE_MATH + // The SimpleMath solver cannot solve the system close to a singular + // configuration. + // Configuration 1. + q[0] = 0.; + q[1] = 0.; + q[2] = 0.; + q[3] = 0.; + q[4] = 0.; + q[5] = 0.; + q[6] = 0.; + q[7] = 0.; + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = 0.; + qd[4] = 0.; + qd[5] = 0.; + qd[6] = 0.; + qd[7] = 0.; + assert(qd[3] + qd[4] - qd[5] - qd[6] - qd[7] == 0.); + assert((CalcPointVelocity(model, q, qd, idB2, X_p.r) + - CalcPointVelocity(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + tau[0] = 0.; + tau[1] = 0.; + tau[2] = 0.; + tau[3] = 1.; + tau[4] = -2.; + tau[5] = 3.; + tau[6] = -5.; + tau[7] = 7.; + + qddDirect = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qddDirect); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddDirect, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddDirect, idB5, X_s.r) + , 6, TEST_PREC); + + qddNullSpace = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qddNullSpace); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB5, X_s.r) + , 6, TEST_PREC); +#endif + + // Configuration 2. + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = M_PI * 3 / 4; + q[4] = -0.5 * M_PI; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = q[3] + q[4] - q[5] - q[6]; + + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + qd[0] = 0.; + qd[1] = 0.; + qd[2] = 0.; + qd[3] = -1.; + qd[4] = -1.; + qd[5] = -2.; + qd[6] = +1.; + qd[7] = -1.; + assert(qd[3] + qd[4] - qd[5] - qd[6] - qd[7] == 0.); + assert((CalcPointVelocity(model, q, qd, idB2, X_p.r) + - CalcPointVelocity(model, q, qd, idB5, X_s.r)).norm() < TEST_PREC); + + tau[0] = 0.; + tau[1] = 0.; + tau[2] = 0.; + tau[3] = 1.; + tau[4] = -2.; + tau[5] = 3.; + tau[6] = -5.; + tau[7] = 7.; + + qddDirect = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qddDirect); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddDirect, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddDirect, idB5, X_s.r) + , 6, TEST_PREC); + + qddNullSpace = VectorNd::Zero(q.size()); + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qddNullSpace); + + CHECK_ARRAY_CLOSE + (CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB2, X_p.r) + , CalcPointAcceleration6D(model, q, qd, qddNullSpace, idB5, X_s.r) + , 6, TEST_PREC); +} + +TEST_FIXTURE(FloatingFourBarLinkage, TestFloatingFourBarLinkageImpulse) { + VectorNd qdPlusDirect(qd.size()); + VectorNd qdPlusRangeSpaceSparse(qd.size()); + VectorNd qdPlusNullSpace(qd.size()); + VectorNd errd(cs.size()); + + q[0] = 1.; + q[1] = 2.; + q[2] = 3.; + q[3] = M_PI * 3 / 4; + q[4] = -0.5 * M_PI; + q[5] = M_PI - q[3]; + q[6] = -q[4]; + q[7] = q[3] + q[4] - q[5] - q[6]; + + assert(q[3] + q[4] - q[5] - q[6] - q[7] == 0.); + assert((CalcBodyToBaseCoordinates(model, q, idB2, X_p.r) + - CalcBodyToBaseCoordinates(model, q, idB5, X_s.r)).norm() < TEST_PREC); + + cs.v_plus[0] = 1.; + cs.v_plus[1] = 2.; + cs.v_plus[2] = 3.; + cs.v_plus[3] = 4.; + cs.v_plus[4] = 5.; + cs.v_plus[5] = 6.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errd); + + CHECK_ARRAY_CLOSE(cs.v_plus, errd, cs.size(), TEST_PREC); + + cs.v_plus[0] = 0.; + cs.v_plus[1] = 0.; + cs.v_plus[2] = 0.; + cs.v_plus[3] = 0.; + cs.v_plus[4] = 0.; + cs.v_plus[5] = 0.; + + qd[0] = 1.; + qd[2] = 2.; + qd[4] = 3.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errd); + + CHECK_ARRAY_CLOSE(cs.v_plus, errd, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointConstraintErrors) { + VectorNd err(VectorNd::Zero(cs.size())); + VectorNd errRef(VectorNd::Zero(cs.size())); + Vector3d pos_p; + Vector3d pos_s; + Matrix3d rot_p; + Matrix3d rot_s; + Matrix3d rot_ps; + Vector3d rotationVec; + + // Test in zero position. + CalcConstraintsPositionError(model, q, cs, err); + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); + + // Test in another configuration. + Quaternion quat = Quaternion::fromZYXAngles(Vector3d(-0.25 * M_PI, 0.01, 0.01)); + q[0] = 0.4; + q[1] = 0.25 * M_PI; + model.SetQuaternion(id_s, quat, q); + + CalcConstraintsPositionError(model, q, cs, err); + + pos_p = CalcBodyToBaseCoordinates(model, q, id_p, X_p.r); + pos_s = CalcBodyToBaseCoordinates(model, q, id_s, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E; + rot_s = CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + rot_ps = rot_p.transpose() * rot_s; + rotationVec = - 0.5 * Vector3d + ( rot_ps(1,2) - rot_ps(2,1) + , rot_ps(2,0) - rot_ps(0,2) + , rot_ps(0,1) - rot_ps(1,0)); + errRef.block<3,1>(0,0) = pos_s - pos_p; + errRef[3] = rotationVec[2]; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointConstraintJacobian) { + MatrixNd G(MatrixNd::Zero(cs.size(), model.dof_count)); + + // Test in zero position. + + G.setZero(); + CalcConstraintsJacobian(model, q, cs, G); + + VectorNd errRef(VectorNd::Zero(cs.size())); + VectorNd err = G * qd; + + CHECK_ARRAY_CLOSE(errRef, err, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointConstraintsVelocityErrors) { + VectorNd errd(VectorNd::Zero(cs.size())); + VectorNd errdRef(VectorNd::Zero(cs.size())); + MatrixNd G(cs.size(), model.dof_count); + VectorNd qWeights(model.dof_count); + VectorNd qInit(model.q_size); + bool success; + + // Compute assembled configuration. + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + Quaternion quat = Quaternion::fromZYXAngles(Vector3d(-0.25 * M_PI, 0.1, 0.1)); + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + model.SetQuaternion(id_s, quat, qInit); + + success = CalcAssemblyQ(model, qInit, cs, q, qWeights, 1e-14, 800); + assert(success); + + // Some random velocity. + qd[0] = -0.2; + qd[1] = 0.1 * M_PI; + qd[2] = -0.1 * M_PI; + qd[3] = 0.; + qd[4] = 0.1 * M_PI; + + CalcConstraintsVelocityError(model, q, qd, cs, errd); + CalcConstraintsJacobian(model, q, cs, G); + errdRef = G * qd; + + CHECK_ARRAY_CLOSE(errdRef, errd, cs.size(), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointAssemblyQ) { + VectorNd weights(model.dof_count); + VectorNd qInit(model.q_size); + + Vector3d pos_p; + Vector3d pos_s; + Matrix3d rot_p; + Matrix3d rot_s; + Matrix3d rot_ps; + + bool success; + + weights[0] = 1.; + weights[1] = 1.; + weights[2] = 1.; + weights[3] = 1.; + weights[4] = 1.; + + Quaternion quat = Quaternion::fromZYXAngles(Vector3d(-0.25 * M_PI, 0.1, 0.1)); + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + model.SetQuaternion(id_s, quat, qInit); + + success = CalcAssemblyQ(model, qInit, cs, q, weights, 1e-14, 800); + pos_p = CalcBodyToBaseCoordinates(model, q, id_p, X_p.r); + pos_s = CalcBodyToBaseCoordinates(model, q, id_s, X_s.r); + rot_p = CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E; + rot_s = CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + rot_ps = rot_p.transpose() * rot_s; + + CHECK(success); + CHECK_ARRAY_CLOSE(pos_p, pos_s, 3, TEST_PREC); + CHECK_CLOSE(0., rot_ps(0,1) - rot_ps(1,0), TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointAssemblyQDot) { + VectorNd qWeights(model.dof_count); + VectorNd qdWeights(model.dof_count); + VectorNd qInit(model.q_size); + VectorNd qdInit(model.dof_count); + + SpatialVector vel_p; + SpatialVector vel_s; + + bool success; + + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + Quaternion quat = Quaternion::fromZYXAngles(Vector3d(-0.25 * M_PI, 0.1, 0.1)); + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + model.SetQuaternion(id_s, quat, qInit); + + qdWeights[0] = 1.; + qdWeights[1] = 0.; + qdWeights[2] = 0.; + qdWeights[3] = 0.; + qdWeights[4] = 0.; + + qdInit[0] = -0.2; + qdInit[1] = 0.1 * M_PI; + qdInit[2] = -0.1 * M_PI; + qdInit[3] = 0.; + qdInit[4] = 0.1 * M_PI; + + success = CalcAssemblyQ(model, qInit, cs, q, qWeights, 1e-14, 800); + assert(success); + + CalcAssemblyQDot(model, q, qdInit, cs, qd, qdWeights); + + vel_p = CalcPointVelocity6D(model, q, qd, id_p, X_p.r); + vel_s = CalcPointVelocity6D(model, q, qd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(vel_p[i], vel_s[i], TEST_PREC); + } + CHECK_CLOSE(qdInit[0], qd[0], TEST_PREC); +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointForwardDynamics) { + + VectorNd qWeights(model.dof_count); + VectorNd qdWeights(model.dof_count); + VectorNd qInit(model.q_size); + VectorNd qdInit(model.dof_count); + + SpatialVector acc_p; + SpatialVector acc_s; + + bool success; + +#ifndef RBDL_USE_SIMPLE_MATH + // The SimpleMath solver cannot solve the system close to a singular + // configuration. + // Test with zero q and qdot. + + tau[0] = 0.12; + tau[1] = -0.3; + tau[2] = 0.05; + tau[3] = 0.7; + tau[4] = -0.1; + + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsRangeSpaceSparse(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } +#endif + + // Compute non-zero assembly q and qdot; + + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + Quaternion quat = Quaternion::fromZYXAngles(Vector3d(-0.25 * M_PI, 0.1, 0.1)); + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + model.SetQuaternion(id_s, quat, qInit); + + qdWeights[0] = 1.; + qdWeights[1] = 0.; + qdWeights[2] = 0.; + qdWeights[3] = 0.; + qdWeights[4] = 0.; + + qdInit[0] = -0.2; + qdInit[1] = 0.1 * M_PI; + qdInit[2] = -0.1 * M_PI; + qdInit[3] = 0.; + qdInit[4] = 0.1 * M_PI; + + qdInit.setZero(); + + success = CalcAssemblyQ(model, qInit, cs, q, qWeights, 1e-14, 800); + assert(success); + CalcAssemblyQDot(model, q, qdInit, cs, qd, qdWeights); + + Matrix3d rot_ps + = (CalcBodyWorldOrientation(model, q, id_p).transpose() * X_p.E).transpose() + * CalcBodyWorldOrientation(model, q, id_s).transpose() * X_s.E; + assert((CalcBodyToBaseCoordinates(model, q, id_p, X_p.r) + - CalcBodyToBaseCoordinates(model, q, id_p, X_p.r)).norm() < TEST_PREC); + assert(rot_ps(0,1) - rot_ps(0,1) < TEST_PREC); + assert((CalcPointVelocity6D(model, q, qd, id_p, X_p.r) + -CalcPointVelocity6D(model, q, qd, id_p, X_p.r)).norm() < TEST_PREC); + + // Test with non-zero q and qdot. + + ForwardDynamicsConstraintsDirect(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsNullSpace(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } + + ForwardDynamicsConstraintsRangeSpaceSparse(model, q, qd, tau, cs, qdd); + + acc_p = CalcPointAcceleration6D(model, q, qd, qdd, id_p, X_p.r); + acc_s = CalcPointAcceleration6D(model, q, qd, qdd, id_s, X_s.r); + + for(size_t i = 2; i < 6; ++i) { + CHECK_CLOSE(acc_p[i], acc_s[i], TEST_PREC); + } +} + +TEST_FIXTURE(SliderCrank3DSphericalJoint + , TestSliderCrank3DSphericalJointImpulse) { + + VectorNd qdPlusDirect(model.dof_count); + VectorNd qdPlusRangeSpaceSparse(model.dof_count); + VectorNd qdPlusNullSpace(model.dof_count); + VectorNd errdDirect(cs.size()); + VectorNd errdSpaceSparse(cs.size()); + VectorNd errdNullSpace(cs.size()); + VectorNd qWeights(model.dof_count); + VectorNd qInit(model.q_size); + + qWeights[0] = 1.; + qWeights[1] = 1.; + qWeights[2] = 1.; + qWeights[3] = 1.; + qWeights[4] = 1.; + + Quaternion quat = Quaternion::fromZYXAngles(Vector3d(-0.25 * M_PI, 0.1, 0.1)); + qInit[0] = 0.4; + qInit[1] = 0.25 * M_PI; + model.SetQuaternion(id_s, quat, qInit); + + bool success = CalcAssemblyQ(model, qInit, cs, q, qWeights, 1e-14, 800); + assert(success); + + cs.v_plus[0] = 1.; + cs.v_plus[1] = 2.; + cs.v_plus[2] = 3.; + cs.v_plus[3] = 4.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errdDirect); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdDirect, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesRangeSpaceSparse(model, q, qd, cs + , qdPlusRangeSpaceSparse); + CalcConstraintsVelocityError(model, q, qdPlusRangeSpaceSparse, cs + , errdSpaceSparse); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdSpaceSparse, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesNullSpace(model, q, qd, cs, qdPlusNullSpace); + CalcConstraintsVelocityError(model, q, qdPlusNullSpace, cs, errdNullSpace); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdNullSpace, cs.size(), TEST_PREC); + + cs.v_plus[0] = 0.; + cs.v_plus[1] = 0.; + cs.v_plus[2] = 0.; + cs.v_plus[3] = 0.; + + qd[0] = 1.; + qd[1] = 2.; + qd[2] = 3.; + + ComputeConstraintImpulsesDirect(model, q, qd, cs, qdPlusDirect); + CalcConstraintsVelocityError(model, q, qdPlusDirect, cs, errdDirect); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdDirect, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesRangeSpaceSparse(model, q, qd, cs + , qdPlusRangeSpaceSparse); + CalcConstraintsVelocityError(model, q, qdPlusRangeSpaceSparse, cs + , errdSpaceSparse); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdSpaceSparse, cs.size(), TEST_PREC); + + ComputeConstraintImpulsesNullSpace(model, q, qd, cs, qdPlusNullSpace); + CalcConstraintsVelocityError(model, q, qdPlusNullSpace, cs, errdNullSpace); + + CHECK_ARRAY_CLOSE(cs.v_plus, errdNullSpace, cs.size(), TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/MathTests.cc b/3rdparty/rbdl/tests/MathTests.cc new file mode 100644 index 0000000..d8d8fda --- /dev/null +++ b/3rdparty/rbdl/tests/MathTests.cc @@ -0,0 +1,109 @@ +#include + +#include "rbdl/Logging.h" +#include "rbdl/rbdl_math.h" +#include "rbdl/rbdl_mathutils.h" +#include + +const double TEST_PREC = 1.0e-14; + +using namespace std; +using namespace RigidBodyDynamics::Math; + +struct MathFixture { +}; + +TEST (GaussElimPivot) { + ClearLogOutput(); + + MatrixNd A; + A.resize(3,3); + VectorNd b(3); + VectorNd x(3); + + A(0,0) = 0; A(0,1) = 2; A(0,2) = 1; + A(1,0) = 1; A(1,1) = 1; A(1,2) = 5; + A(2,0) = 0; A(2,1) = 0; A(2,2) = 1; + + b[0] = 1; + b[1] = 2; + b[2] = 3; + + VectorNd test_result (3); + + test_result[0] = -12; + test_result[1] = -1; + test_result[2] = 3; + + LinSolveGaussElimPivot (A, b, x); + + CHECK_ARRAY_CLOSE (test_result.data(), x.data(), 3, TEST_PREC); + + A(0,0) = 0; A(0,1) = -2; A(0,2) = 1; + A(1,0) = 1; A(1,1) = 1; A(1,2) = 5; + A(2,0) = 0; A(2,1) = 0; A(2,2) = 1; + + LinSolveGaussElimPivot (A, b, x); + test_result[0] = -14; + test_result[1] = 1; + test_result[2] = 3; + + CHECK_ARRAY_CLOSE (test_result.data(), x.data(), 3, TEST_PREC); +} + +TEST (Dynamic_1D_initialize_value) { + VectorNd myvector_10 = VectorNd::Constant ((size_t) 10, 12.); + + double *test_values = new double[10]; + for (unsigned int i = 0; i < 10; i++) + test_values[i] = 12.; + + CHECK_ARRAY_EQUAL (test_values, myvector_10.data(), 10); + delete[] test_values; +} + +TEST (Dynamic_2D_initialize_value) { + MatrixNd mymatrix_10x10 = MatrixNd::Constant (10, 10, 12.); + + double *test_values = new double[10 * 10]; + for (unsigned int i = 0; i < 10; i++) + for (unsigned int j = 0; j < 10; j++) + test_values[i*10 + j] = 12.; + + CHECK_ARRAY_EQUAL (test_values, mymatrix_10x10.data(), 10*10); + delete[] test_values; +} + +TEST (SpatialMatrix_Multiplication) { + SpatialMatrix X_1 ( + 1., 2., 3., 4., 5., 6., + 11., 12., 13., 14., 15., 16., + 21., 22., 23., 24., 25., 26., + 31., 32., 33., 34., 35., 36., + 41., 42., 43., 44., 45., 46., + 51., 52., 53., 54., 55., 56. + ); + + SpatialMatrix X_2 (X_1); + + X_2 *= 2; + + SpatialMatrix correct_result ( + 1442, 1484, 1526, 1568, 1610, 1652, + 4562, 4724, 4886, 5048, 5210, 5372, + 7682, 7964, 8246, 8528, 8810, 9092, + 10802, 11204, 11606, 12008, 12410, 12812, + 13922, 14444, 14966, 15488, 16010, 16532, + 17042, 17684, 18326, 18968, 19610, 20252 + ); + + SpatialMatrix test_result = X_1 * X_2; + + CHECK_ARRAY_CLOSE (correct_result.data(), test_result.data(), 6 * 6, TEST_PREC); + + // check the *= operator: + test_result = X_1; + test_result *= X_2; + + CHECK_ARRAY_CLOSE (correct_result.data(), test_result.data(), 6 * 6, TEST_PREC); +} diff --git a/3rdparty/rbdl/tests/ModelTests.cc b/3rdparty/rbdl/tests/ModelTests.cc new file mode 100644 index 0000000..1d46785 --- /dev/null +++ b/3rdparty/rbdl/tests/ModelTests.cc @@ -0,0 +1,604 @@ +#include + +#include + +#include "Fixtures.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +struct ModelFixture { + ModelFixture () { + ClearLogOutput(); + model = new Model; + model->gravity = Vector3d (0., -9.81, 0.); + } + ~ModelFixture () { + delete model; + } + Model *model; +}; + +TEST_FIXTURE(ModelFixture, TestInit) { + CHECK_EQUAL (1u, model->lambda.size()); + CHECK_EQUAL (1u, model->mu.size()); + CHECK_EQUAL (0u, model->dof_count); + + CHECK_EQUAL (0u, model->q_size); + CHECK_EQUAL (0u, model->qdot_size); + + CHECK_EQUAL (1u, model->v.size()); + CHECK_EQUAL (1u, model->a.size()); + + CHECK_EQUAL (1u, model->mJoints.size()); + CHECK_EQUAL (1u, model->S.size()); + + CHECK_EQUAL (1u, model->c.size()); + CHECK_EQUAL (1u, model->IA.size()); + CHECK_EQUAL (1u, model->pA.size()); + CHECK_EQUAL (1u, model->U.size()); + CHECK_EQUAL (1u, model->d.size()); + CHECK_EQUAL (1u, model->u.size()); + CHECK_EQUAL (1u, model->Ic.size()); + CHECK_EQUAL (1u, model->I.size()); + + CHECK_EQUAL (1u, model->X_lambda.size()); + CHECK_EQUAL (1u, model->X_base.size()); + CHECK_EQUAL (1u, model->mBodies.size()); +} + +TEST_FIXTURE(ModelFixture, TestAddBodyDimensions) { + Body body; + Joint joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + unsigned int body_id = 0; + body_id = model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint, body); + + CHECK_EQUAL (1u, body_id); + CHECK_EQUAL (2u, model->lambda.size()); + CHECK_EQUAL (2u, model->mu.size()); + CHECK_EQUAL (1u, model->dof_count); + + CHECK_EQUAL (2u, model->v.size()); + CHECK_EQUAL (2u, model->a.size()); + + CHECK_EQUAL (2u, model->mJoints.size()); + CHECK_EQUAL (2u, model->S.size()); + + CHECK_EQUAL (2u, model->c.size()); + CHECK_EQUAL (2u, model->IA.size()); + CHECK_EQUAL (2u, model->pA.size()); + CHECK_EQUAL (2u, model->U.size()); + CHECK_EQUAL (2u, model->d.size()); + CHECK_EQUAL (2u, model->u.size()); + CHECK_EQUAL (2u, model->Ic.size()); + CHECK_EQUAL (2u, model->I.size()); + + SpatialVector spatial_zero; + spatial_zero.setZero(); + + CHECK_EQUAL (2u, model->X_lambda.size()); + CHECK_EQUAL (2u, model->X_base.size()); + CHECK_EQUAL (2u, model->mBodies.size()); +} + +TEST_FIXTURE(ModelFixture, TestFloatingBodyDimensions) { + Body body; + Joint float_base_joint (JointTypeFloatingBase); + + model->AppendBody (SpatialTransform(), float_base_joint, body); + + CHECK_EQUAL (3u, model->lambda.size()); + CHECK_EQUAL (3u, model->mu.size()); + CHECK_EQUAL (6u, model->dof_count); + CHECK_EQUAL (7u, model->q_size); + CHECK_EQUAL (6u, model->qdot_size); + + CHECK_EQUAL (3u, model->v.size()); + CHECK_EQUAL (3u, model->a.size()); + + CHECK_EQUAL (3u, model->mJoints.size()); + CHECK_EQUAL (3u, model->S.size()); + + CHECK_EQUAL (3u, model->c.size()); + CHECK_EQUAL (3u, model->IA.size()); + CHECK_EQUAL (3u, model->pA.size()); + CHECK_EQUAL (3u, model->U.size()); + CHECK_EQUAL (3u, model->d.size()); + CHECK_EQUAL (3u, model->u.size()); + + SpatialVector spatial_zero; + spatial_zero.setZero(); + + CHECK_EQUAL (3u, model->X_lambda.size()); + CHECK_EQUAL (3u, model->X_base.size()); + CHECK_EQUAL (3u, model->mBodies.size()); +} + +/** \brief Tests whether the joint and body information stored in the Model are computed correctly +*/ +TEST_FIXTURE(ModelFixture, TestAddBodySpatialValues) { + Body body; + Joint joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint, body); + + SpatialVector spatial_joint_axis(0., 0., 1., 0., 0., 0.); + CHECK_EQUAL (spatial_joint_axis, joint.mJointAxes[0]); + + // \Todo: Dynamic properties +} + +TEST_FIXTURE(ModelFixture, TestAddBodyTestBodyName) { + Body body; + Joint joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(0., 0., 0.)), joint, body, "mybody"); + + unsigned int body_id = model->GetBodyId("mybody"); + + CHECK_EQUAL (1u, body_id); + CHECK_EQUAL (std::numeric_limits::max(), model->GetBodyId("unknownbody")); +} + +TEST_FIXTURE(ModelFixture, TestjcalcSimple) { + Body body; + Joint joint ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model->AddBody(0, Xtrans(Vector3d(1., 0., 0.)), joint, body); + + VectorNd Q = VectorNd::Zero (model->q_size); + VectorNd QDot = VectorNd::Zero (model->q_size); + + QDot[0] = 1.; + jcalc (*model, 1, Q, QDot); + + SpatialMatrix test_matrix ( + 1., 0., 0., 0., 0., 0., + 0., 1., 0., 0., 0., 0., + 0., 0., 1., 0., 0., 0., + 0., 0., 0., 1., 0., 0., + 0., 0., 0., 0., 1., 0., + 0., 0., 0., 0., 0., 1. + ); + SpatialVector test_vector ( + 0., 0., 1., 0., 0., 0. + ); + SpatialVector test_joint_axis ( + 0., 0., 1., 0., 0., 0. + ); + + CHECK (SpatialMatrixCompareEpsilon (test_matrix, model->X_J[1].toMatrix(), 1.0e-16)); + CHECK (SpatialVectorCompareEpsilon (test_vector, model->v_J[1], 1.0e-16)); + CHECK_EQUAL (test_joint_axis, model->S[1]); + + Q[0] = M_PI * 0.5; + QDot[0] = 1.; + + jcalc (*model, 1, Q, QDot); + + test_matrix.set ( + 0., 1., 0., 0., 0., 0., + -1., 0., 0., 0., 0., 0., + 0., 0., 1., 0., 0., 0., + 0., 0., 0., 0., 1., 0., + 0., 0., 0., -1., 0., 0., + 0., 0., 0., 0., 0., 1. + ); + + CHECK (SpatialMatrixCompareEpsilon (test_matrix, model->X_J[1].toMatrix(), 1.0e-16)); + CHECK (SpatialVectorCompareEpsilon (test_vector, model->v_J[1], 1.0e-16)); + CHECK_EQUAL (test_joint_axis, model->S[1]); +} + +TEST_FIXTURE ( ModelFixture, TestTransformBaseToLocal ) { + Body body; + + unsigned int body_id = model->AddBody (0, SpatialTransform(), + Joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ), + body); + + VectorNd q = VectorNd::Zero (model->dof_count); + VectorNd qdot = VectorNd::Zero (model->dof_count); + VectorNd qddot = VectorNd::Zero (model->dof_count); + VectorNd tau = VectorNd::Zero (model->dof_count); + + Vector3d base_coords (0., 0., 0.); + Vector3d body_coords; + Vector3d base_coords_back; + + UpdateKinematics (*model, q, qdot, qddot); + body_coords = CalcBaseToBodyCoordinates (*model, q, body_id, base_coords, false); + base_coords_back = CalcBodyToBaseCoordinates (*model, q, body_id, body_coords, false); + + CHECK_ARRAY_CLOSE (base_coords.data(), base_coords_back.data(), 3, TEST_PREC); + + q[0] = 1.; + q[1] = 0.2; + q[2] = -2.3; + q[3] = -2.3; + q[4] = 0.03; + q[5] = -0.23; + + UpdateKinematics (*model, q, qdot, qddot); + body_coords = CalcBaseToBodyCoordinates (*model, q, body_id, base_coords, false); + base_coords_back = CalcBodyToBaseCoordinates (*model, q, body_id, body_coords, false); + + CHECK_ARRAY_CLOSE (base_coords.data(), base_coords_back.data(), 3, TEST_PREC); +} + +TEST ( Model2DoFJoint ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + Joint joint_rot_x ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + Model model_std; + model_std.gravity = Vector3d (0., -9.81, 0.); + + model_std.AddBody(0, Xtrans(Vector3d(1., 0., 0.)), joint_rot_z, null_body); + model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_x, body); + + // using a model with a 2 DoF joint + Joint joint_rot_zx ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + + Model model_2; + model_2.gravity = Vector3d (0., -9.81, 0.); + + model_2.AddBody(0, Xtrans(Vector3d(1., 0., 0.)), joint_rot_zx, body); + + VectorNd Q = VectorNd::Zero(model_std.dof_count); + VectorNd QDot = VectorNd::Zero(model_std.dof_count); + VectorNd Tau = VectorNd::Zero(model_std.dof_count); + + VectorNd QDDot_2 = VectorNd::Zero(model_std.dof_count); + VectorNd QDDot_std = VectorNd::Zero(model_std.dof_count); + + ForwardDynamics (model_std, Q, QDot, Tau, QDDot_std); + ForwardDynamics (model_2, Q, QDot, Tau, QDDot_2); + + CHECK_ARRAY_CLOSE (QDDot_std.data(), QDDot_2.data(), model_std.dof_count, TEST_PREC); +} + +TEST ( Model3DoFJoint ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + Joint joint_rot_y ( SpatialVector (0., 1., 0., 0., 0., 0.)); + Joint joint_rot_x ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + Model model_std; + model_std.gravity = Vector3d (0., -9.81, 0.); + + unsigned int body_id; + + // in total we add two bodies to make sure that the transformations are + // correct. + model_std.AddBody(0, Xtrans(Vector3d(1., 0., 0.)), joint_rot_z, null_body); + model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_y, null_body); + body_id = model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_x, body); + + model_std.AddBody(body_id, Xtrans(Vector3d(1., 0., 0.)), joint_rot_z, null_body); + model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_y, null_body); + body_id = model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_x, body); + + // using a model with a 2 DoF joint + Joint joint_rot_zyx ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + + Model model_2; + model_2.gravity = Vector3d (0., -9.81, 0.); + + // in total we add two bodies to make sure that the transformations are + // correct. + body_id = model_2.AddBody(0, Xtrans(Vector3d(1., 0., 0.)), joint_rot_zyx, body); + body_id = model_2.AddBody(body_id, Xtrans(Vector3d(1., 0., 0.)), joint_rot_zyx, body); + + VectorNd Q = VectorNd::Zero(model_std.dof_count); + VectorNd QDot = VectorNd::Zero(model_std.dof_count); + VectorNd Tau = VectorNd::Zero(model_std.dof_count); + + VectorNd QDDot_2 = VectorNd::Zero(model_std.dof_count); + VectorNd QDDot_std = VectorNd::Zero(model_std.dof_count); + + ForwardDynamics (model_std, Q, QDot, Tau, QDDot_std); + ForwardDynamics (model_2, Q, QDot, Tau, QDDot_2); + + CHECK_ARRAY_CLOSE (QDDot_std.data(), QDDot_2.data(), model_std.dof_count, TEST_PREC); +} + +TEST ( Model6DoFJoint ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + Joint joint_rot_y ( SpatialVector (0., 1., 0., 0., 0., 0.)); + Joint joint_rot_x ( SpatialVector (1., 0., 0., 0., 0., 0.)); + + Model model_std; + model_std.gravity = Vector3d (0., -9.81, 0.); + + unsigned int body_id; + + Joint joint_floating_base ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.), + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + body_id = model_std.AddBody (0, SpatialTransform(), joint_floating_base, body); + + model_std.AddBody(body_id, Xtrans(Vector3d(1., 0., 0.)), joint_rot_z, null_body); + model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_y, null_body); + body_id = model_std.AppendBody(Xtrans(Vector3d(0., 0., 0.)), joint_rot_x, body); + + // using a model with a 2 DoF joint + Joint joint_rot_zyx ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + + Model model_2; + model_2.gravity = Vector3d (0., -9.81, 0.); + + // in total we add two bodies to make sure that the transformations are + // correct. + body_id = model_2.AddBody(0, Xtrans(Vector3d(1., 0., 0.)), joint_floating_base, body); + body_id = model_2.AddBody(body_id, Xtrans(Vector3d(1., 0., 0.)), joint_rot_zyx, body); + + VectorNd Q = VectorNd::Zero(model_std.dof_count); + VectorNd QDot = VectorNd::Zero(model_std.dof_count); + VectorNd Tau = VectorNd::Zero(model_std.dof_count); + + VectorNd QDDot_2 = VectorNd::Zero(model_std.dof_count); + VectorNd QDDot_std = VectorNd::Zero(model_std.dof_count); + + assert (model_std.q_size == model_2.q_size); + + ForwardDynamics (model_std, Q, QDot, Tau, QDDot_std); + ForwardDynamics (model_2, Q, QDot, Tau, QDDot_2); + + CHECK_ARRAY_CLOSE (QDDot_std.data(), QDDot_2.data(), model_std.dof_count, TEST_PREC); +} + +TEST ( ModelFixedJointQueryBodyId ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), Joint(JointTypeFixed), fixed_body, "fixed_body"); + + CHECK_EQUAL (fixed_body_id, model.GetBodyId("fixed_body")); +} + +/* + * Makes sure that when appending a body to a fixed joint the parent of the + * newly added parent is actually the moving body that the fixed body is + * attached to. + */ +TEST ( ModelAppendToFixedBody ) { + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + unsigned int movable_body = model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + // unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), Joint(JointTypeFixed), fixed_body, "fixed_body"); + unsigned int appended_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), joint_rot_z, body, "appended_body"); + + CHECK_EQUAL (movable_body + 1, appended_body_id); + CHECK_EQUAL (movable_body, model.lambda[appended_body_id]); +} + +// Adds a fixed body to another fixed body. +TEST ( ModelAppendFixedToFixedBody ) { + Body null_body; + + double movable_mass = 1.1; + Vector3d movable_com (1., 0.4, 0.4); + + double fixed_mass = 1.2; + Vector3d fixed_com (1.1, 0.5, 0.5); + + Vector3d fixed_displacement (0., 1., 0.); + + Body body(movable_mass, movable_com, Vector3d (1., 1., 1.)); + Body fixed_body(fixed_mass, fixed_com, Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + unsigned int movable_body = model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(fixed_displacement), Joint(JointTypeFixed), fixed_body, "fixed_body"); + unsigned int fixed_body_2_id = model.AppendBody (Xtrans(fixed_displacement), Joint(JointTypeFixed), fixed_body, "fixed_body_2"); + unsigned int appended_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), joint_rot_z, body, "appended_body"); + + CHECK_EQUAL (movable_body + 1, appended_body_id); + CHECK_EQUAL (movable_body, model.lambda[appended_body_id]); + CHECK_EQUAL (movable_mass + fixed_mass * 2., model.mBodies[movable_body].mMass); + + CHECK_EQUAL (movable_body, model.mFixedBodies[fixed_body_id - model.fixed_body_discriminator].mMovableParent); + CHECK_EQUAL (movable_body, model.mFixedBodies[fixed_body_2_id - model.fixed_body_discriminator].mMovableParent); + + double new_mass = 3.5; + Vector3d new_com = (1. / new_mass) * (movable_mass * movable_com + fixed_mass * (fixed_com + fixed_displacement) + fixed_mass * (fixed_com + fixed_displacement * 2.)); + + CHECK_ARRAY_CLOSE (new_com.data(), model.mBodies[movable_body].mCenterOfMass.data(), 3, TEST_PREC); +} + +// Ensures that the transformations of the movable parent and fixed joint +// frame is in proper order +TEST ( ModelFixedJointRotationOrderTranslationRotation ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + SpatialTransform trans_x = Xtrans (Vector3d (1., 0., 0.)); + SpatialTransform rot_z = Xrotz (45. * M_PI / 180.); + + model.AddBody (0, trans_x, joint_rot_z, body); + model.AppendBody (rot_z, Joint(JointTypeFixed), fixed_body, "fixed_body"); + unsigned int body_after_fixed = model.AppendBody (trans_x, joint_rot_z, body); + + VectorNd Q (VectorNd::Zero(model.dof_count)); + Q[0] = 45 * M_PI / 180.; + Vector3d point = CalcBodyToBaseCoordinates (model, Q, body_after_fixed, Vector3d (0., 1., 0.)); + + CHECK_ARRAY_CLOSE (Vector3d (0., 1., 0.).data(), point.data(), 3, TEST_PREC); +} + +// Ensures that the transformations of the movable parent and fixed joint +// frame is in proper order +TEST ( ModelFixedJointRotationOrderRotationTranslation ) { + // the standard modeling using a null body + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + SpatialTransform rot_z = Xrotz (45. * M_PI / 180.); + SpatialTransform trans_x = Xtrans (Vector3d (1., 0., 0.)); + + model.AddBody (0, rot_z, joint_rot_z, body); + model.AppendBody (trans_x, Joint(JointTypeFixed), fixed_body, "fixed_body"); + unsigned int body_after_fixed = model.AppendBody (trans_x, joint_rot_z, body); + + VectorNd Q (VectorNd::Zero(model.dof_count)); + Q[0] = 45 * M_PI / 180.; + Vector3d point = CalcBodyToBaseCoordinates (model, Q, body_after_fixed, Vector3d (0., 1., 0.)); + + CHECK_ARRAY_CLOSE (Vector3d (-1., 2., 0.).data(), point.data(), 3, TEST_PREC); +} + +TEST ( ModelGetBodyName ) { + Body null_body; + Body body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + Body fixed_body(1., Vector3d (1., 0.4, 0.4), Vector3d (1., 1., 1.)); + + Model model; + + Joint joint_rot_z ( SpatialVector (0., 0., 1., 0., 0., 0.)); + + model.AddBody (0, Xtrans(Vector3d(0., 0., 0.)), joint_rot_z, body); + unsigned int fixed_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), Joint(JointTypeFixed), fixed_body, "fixed_body"); + unsigned int appended_body_id = model.AppendBody (Xtrans(Vector3d(0., 1., 0.)), joint_rot_z, body, "appended_body"); + + CHECK_EQUAL (string("fixed_body"), model.GetBodyName(fixed_body_id)); + CHECK_EQUAL (string("appended_body"), model.GetBodyName(appended_body_id)); + CHECK_EQUAL (string(""), model.GetBodyName(123)); +} + +TEST_FIXTURE ( RotZRotZYXFixed, ModelGetParentBodyId ) { + CHECK_EQUAL (0u, model->GetParentBodyId(0)); + CHECK_EQUAL (0u, model->GetParentBodyId(body_a_id)); + CHECK_EQUAL (body_a_id, model->GetParentBodyId(body_b_id)); +} + +TEST_FIXTURE(RotZRotZYXFixed, ModelGetParentIdFixed) { + CHECK_EQUAL (body_b_id, model->GetParentBodyId(body_fixed_id)); +} + +TEST_FIXTURE(RotZRotZYXFixed, ModelGetJointFrame) { + SpatialTransform transform_a = model->GetJointFrame (body_a_id); + SpatialTransform transform_b = model->GetJointFrame (body_b_id); + SpatialTransform transform_root = model->GetJointFrame (0); + + CHECK_ARRAY_EQUAL (fixture_transform_a.r.data(), transform_a.r.data(), 3); + CHECK_ARRAY_EQUAL (fixture_transform_b.r.data(), transform_b.r.data(), 3); + CHECK_ARRAY_EQUAL (Vector3d(0., 0., 0.).data(), transform_root.r.data(), 3); +} + +TEST_FIXTURE(RotZRotZYXFixed, ModelGetJointFrameFixed) { + SpatialTransform transform_fixed = model->GetJointFrame (body_fixed_id); + + CHECK_ARRAY_EQUAL (fixture_transform_fixed.r.data(), transform_fixed.r.data(), 3); +} + +TEST_FIXTURE(RotZRotZYXFixed, ModelSetJointFrame) { + SpatialTransform new_transform_a = Xtrans (Vector3d(-1., -2., -3.)); + SpatialTransform new_transform_b = Xtrans (Vector3d(-4., -5., -6.)); + SpatialTransform new_transform_root = Xtrans (Vector3d(-99, -99., -99.)); + + model->SetJointFrame (body_a_id, new_transform_a); + model->SetJointFrame (body_b_id, new_transform_b); + model->SetJointFrame (0, new_transform_root); + + SpatialTransform transform_a = model->GetJointFrame (body_a_id); + SpatialTransform transform_b = model->GetJointFrame (body_b_id); + SpatialTransform transform_root = model->GetJointFrame (0); + + CHECK_ARRAY_EQUAL (new_transform_a.r.data(), transform_a.r.data(), 3); + CHECK_ARRAY_EQUAL (new_transform_b.r.data(), transform_b.r.data(), 3); + CHECK_ARRAY_EQUAL (Vector3d(0., 0., 0.).data(), transform_root.r.data(), 3); +} + +TEST (CalcBodyWorldOrientationFixedJoint) { + Model model_fixed; + Model model_movable; + + Body body (1., Vector3d (1., 1., 1.), Vector3d (1., 1., 1.)); + Joint joint_fixed (JointTypeFixed); + Joint joint_rot_x = (SpatialVector (1., 0., 0., 0., 0., 0.)); + + model_fixed.AppendBody (Xrotx (45 * M_PI / 180), joint_rot_x, body); + unsigned int body_id_fixed = model_fixed.AppendBody (Xroty (45 * M_PI / 180), joint_fixed, body); + + model_movable.AppendBody (Xrotx (45 * M_PI / 180), joint_rot_x, body); + unsigned int body_id_movable = model_movable.AppendBody (Xroty (45 * M_PI / 180), joint_rot_x, body); + + VectorNd q_fixed (VectorNd::Zero (model_fixed.q_size)); + VectorNd q_movable (VectorNd::Zero (model_movable.q_size)); + + Matrix3d E_fixed = CalcBodyWorldOrientation (model_fixed, q_fixed, body_id_fixed); + Matrix3d E_movable = CalcBodyWorldOrientation (model_movable, q_movable, body_id_movable); + + CHECK_ARRAY_CLOSE (E_movable.data(), E_fixed.data(), 9, TEST_PREC); +} + diff --git a/3rdparty/rbdl/tests/MultiDofTests.cc b/3rdparty/rbdl/tests/MultiDofTests.cc new file mode 100644 index 0000000..a12d45c --- /dev/null +++ b/3rdparty/rbdl/tests/MultiDofTests.cc @@ -0,0 +1,1055 @@ +#include + +#include + +#include "Fixtures.h" +#include "Human36Fixture.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Constraints.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-12; + +struct SphericalJoint { + SphericalJoint () { + ClearLogOutput(); + + emulated_model.gravity = Vector3d (0., 0., -9.81); + multdof3_model.gravity = Vector3d (0., 0., -9.81); + eulerzyx_model.gravity = Vector3d (0., 0., -9.81); + + body = Body (1., Vector3d (1., 0., 0.), Vector3d (1., 1., 1.)); + + joint_rot_zyx = Joint ( + SpatialVector (0., 0., 1., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.) + ); + joint_spherical = Joint (JointTypeSpherical); + joint_eulerzyx = Joint (JointTypeEulerZYX); + + joint_rot_y = Joint (SpatialVector (0., 1., 0., 0., 0., 0.)); + + emulated_model.AppendBody (Xtrans(Vector3d (0., 0., 0.)), joint_rot_y, body); + emu_body_id = emulated_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_rot_zyx, body); + emu_child_id = emulated_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_rot_y, body); + + multdof3_model.AppendBody (Xtrans(Vector3d (0., 0., 0.)), joint_rot_y, body); + sph_body_id = multdof3_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_spherical, body); + sph_child_id = multdof3_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_rot_y, body); + + eulerzyx_model.AppendBody (Xtrans(Vector3d (0., 0., 0.)), joint_rot_y, body); + eulerzyx_body_id = eulerzyx_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_eulerzyx, body); + eulerzyx_child_id = eulerzyx_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_rot_y, body); + + emuQ = VectorNd::Zero ((size_t) emulated_model.q_size); + emuQDot = VectorNd::Zero ((size_t) emulated_model.qdot_size); + emuQDDot = VectorNd::Zero ((size_t) emulated_model.qdot_size); + emuTau = VectorNd::Zero ((size_t) emulated_model.qdot_size); + + sphQ = VectorNd::Zero ((size_t) multdof3_model.q_size); + sphQDot = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + sphQDDot = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + sphTau = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + + eulerzyxQ = VectorNd::Zero ((size_t) eulerzyx_model.q_size); + eulerzyxQDot = VectorNd::Zero ((size_t) eulerzyx_model.qdot_size); + eulerzyxQDDot = VectorNd::Zero ((size_t) eulerzyx_model.qdot_size); + eulerzyxTau = VectorNd::Zero ((size_t) eulerzyx_model.qdot_size); + } + + Joint joint_rot_zyx; + Joint joint_spherical; + Joint joint_eulerzyx; + Joint joint_rot_y; + Body body; + + unsigned int emu_body_id, emu_child_id, + sph_body_id, sph_child_id, + eulerzyx_body_id, eulerzyx_child_id; + + Model emulated_model; + Model multdof3_model; + Model eulerzyx_model; + + VectorNd emuQ; + VectorNd emuQDot; + VectorNd emuQDDot; + VectorNd emuTau; + + VectorNd sphQ; + VectorNd sphQDot; + VectorNd sphQDDot; + VectorNd sphTau; + + VectorNd eulerzyxQ; + VectorNd eulerzyxQDot; + VectorNd eulerzyxQDDot; + VectorNd eulerzyxTau; +}; + +void ConvertQAndQDotFromEmulated ( + const Model &emulated_model, const VectorNd &q_emulated, const VectorNd &qdot_emulated, + const Model &multdof3_model, VectorNd *q_spherical, VectorNd *qdot_spherical) { + for (unsigned int i = 1; i < multdof3_model.mJoints.size(); i++) { + unsigned int q_index = multdof3_model.mJoints[i].q_index; + + if (multdof3_model.mJoints[i].mJointType == JointTypeSpherical) { + Quaternion quat = Quaternion::fromZYXAngles ( Vector3d ( + q_emulated[q_index + 0], q_emulated[q_index + 1], q_emulated[q_index + 2])); + multdof3_model.SetQuaternion (i, quat, (*q_spherical)); + + Vector3d omega = angular_velocity_from_angle_rates ( + Vector3d (q_emulated[q_index], q_emulated[q_index + 1], q_emulated[q_index + 2]), + Vector3d (qdot_emulated[q_index], qdot_emulated[q_index + 1], qdot_emulated[q_index + 2]) + ); + + (*qdot_spherical)[q_index] = omega[0]; + (*qdot_spherical)[q_index + 1] = omega[1]; + (*qdot_spherical)[q_index + 2] = omega[2]; + } else { + (*q_spherical)[q_index] = q_emulated[q_index]; + (*qdot_spherical)[q_index] = qdot_emulated[q_index]; + } + } +} + +TEST(TestQuaternionIntegration ) { + double timestep = 0.001; + + Vector3d zyx_angles_t0 (0.1, 0.2, 0.3); + Vector3d zyx_rates (3., 5., 2.); + Vector3d zyx_angles_t1 = zyx_angles_t0 + timestep * zyx_rates; + Quaternion q_zyx_t1 = Quaternion::fromZYXAngles (zyx_angles_t1); + + Quaternion q_t0 = Quaternion::fromZYXAngles (zyx_angles_t0); + Vector3d w_base = global_angular_velocity_from_rates (zyx_angles_t0, zyx_rates); + Quaternion q_t1 = q_t0.timeStep (w_base, timestep); + + // Note: we test with a rather crude precision. My guess for the error is + // that we compare two different things: + // A) integration under the assumption that the euler rates are + // constant + // B) integration under the assumption that the angular velocity is + // constant + // However I am not entirely sure about this... + CHECK_ARRAY_CLOSE (q_zyx_t1.data(), q_t1.data(), 4, 1.0e-5); +} + +TEST_FIXTURE(SphericalJoint, TestQIndices) { + CHECK_EQUAL (0u, multdof3_model.mJoints[1].q_index); + CHECK_EQUAL (1u, multdof3_model.mJoints[2].q_index); + CHECK_EQUAL (4u, multdof3_model.mJoints[3].q_index); + + CHECK_EQUAL (5u, emulated_model.q_size); + CHECK_EQUAL (5u, emulated_model.qdot_size); + + CHECK_EQUAL (6u, multdof3_model.q_size); + CHECK_EQUAL (5u, multdof3_model.qdot_size); + CHECK_EQUAL (5u, multdof3_model.multdof3_w_index[2]); +} + +TEST_FIXTURE(SphericalJoint, TestGetQuaternion) { + multdof3_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_spherical, body); + + sphQ = VectorNd::Zero ((size_t) multdof3_model.q_size); + sphQDot = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + sphQDDot = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + sphTau = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + + CHECK_EQUAL (10u, multdof3_model.q_size); + CHECK_EQUAL (8u, multdof3_model.qdot_size); + + CHECK_EQUAL (0u, multdof3_model.mJoints[1].q_index); + CHECK_EQUAL (1u, multdof3_model.mJoints[2].q_index); + CHECK_EQUAL (4u, multdof3_model.mJoints[3].q_index); + CHECK_EQUAL (5u, multdof3_model.mJoints[4].q_index); + + CHECK_EQUAL (8u, multdof3_model.multdof3_w_index[2]); + CHECK_EQUAL (9u, multdof3_model.multdof3_w_index[4]); + + sphQ[0] = 100.; + sphQ[1] = 0.; + sphQ[2] = 1.; + sphQ[3] = 2.; + sphQ[4] = 100.; + sphQ[5] = -6.; + sphQ[6] = -7.; + sphQ[7] = -8; + sphQ[8] = 4.; + sphQ[9] = -9.; + + Quaternion reference_1 (0., 1., 2., 4.); + Quaternion quat_1 = multdof3_model.GetQuaternion (2, sphQ); + CHECK_ARRAY_EQUAL (reference_1.data(), quat_1.data(), 4); + + Quaternion reference_3 (-6., -7., -8., -9.); + Quaternion quat_3 = multdof3_model.GetQuaternion (4, sphQ); + CHECK_ARRAY_EQUAL (reference_3.data(), quat_3.data(), 4); +} + +TEST_FIXTURE(SphericalJoint, TestSetQuaternion) { + multdof3_model.AppendBody (Xtrans (Vector3d (1., 0., 0.)), joint_spherical, body); + + sphQ = VectorNd::Zero ((size_t) multdof3_model.q_size); + sphQDot = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + sphQDDot = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + sphTau = VectorNd::Zero ((size_t) multdof3_model.qdot_size); + + Quaternion reference_1 (0., 1., 2., 3.); + multdof3_model.SetQuaternion (2, reference_1, sphQ); + Quaternion test = multdof3_model.GetQuaternion (2, sphQ); + CHECK_ARRAY_EQUAL (reference_1.data(), test.data(), 4); + + Quaternion reference_2 (11., 22., 33., 44.); + multdof3_model.SetQuaternion (4, reference_2, sphQ); + test = multdof3_model.GetQuaternion (4, sphQ); + CHECK_ARRAY_EQUAL (reference_2.data(), test.data(), 4); +} + +TEST_FIXTURE(SphericalJoint, TestOrientation) { + emuQ[0] = 1.1; + emuQ[1] = 1.1; + emuQ[2] = 1.1; + emuQ[3] = 1.1; + + for (unsigned int i = 0; i < emuQ.size(); i++) { + sphQ[i] = emuQ[i]; + } + + Quaternion quat = Quaternion::fromAxisAngle (Vector3d (0., 0., 1.), emuQ[0]) + * Quaternion::fromAxisAngle (Vector3d (0., 1., 0.), emuQ[1]) + * Quaternion::fromAxisAngle (Vector3d (1., 0., 0.), emuQ[2]); + multdof3_model.SetQuaternion (2, quat, sphQ); + + Matrix3d emu_orientation = CalcBodyWorldOrientation (emulated_model, emuQ, emu_child_id); + Matrix3d sph_orientation = CalcBodyWorldOrientation (multdof3_model, sphQ, sph_child_id); + + CHECK_ARRAY_CLOSE (emu_orientation.data(), sph_orientation.data(), 9, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestUpdateKinematics) { + emuQ[0] = 1.; + emuQ[1] = 1.; + emuQ[2] = 1.; + emuQ[3] = 1.; + emuQ[4] = 1.; + + emuQDot[0] = 1.; + emuQDot[1] = 1.; + emuQDot[2] = 1.; + emuQDot[3] = 1.; + emuQDot[4] = 1.; + + emuQDDot[0] = 1.; + emuQDDot[1] = 1.; + emuQDDot[2] = 1.; + emuQDDot[3] = 1.; + emuQDDot[4] = 1.; + + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDot, multdof3_model, &sphQ, &sphQDot); + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDDot, multdof3_model, &sphQ, &sphQDDot); + + Vector3d a = angular_acceleration_from_angle_rates ( + Vector3d (emuQ[3], emuQ[2], emuQ[1]), + Vector3d (emuQDot[3], emuQDot[2], emuQDot[1]), + Vector3d (emuQDDot[3], emuQDDot[2], emuQDDot[1]) + ); + + sphQDDot[0] = emuQDDot[0]; + sphQDDot[1] = a[0]; + sphQDDot[2] = a[1]; + sphQDDot[3] = a[2]; + sphQDDot[4] = emuQDDot[4]; + + UpdateKinematicsCustom (emulated_model, &emuQ, &emuQDot, &emuQDDot); + UpdateKinematicsCustom (multdof3_model, &sphQ, &sphQDot, &sphQDDot); + + CHECK_ARRAY_CLOSE (emulated_model.v[emu_body_id].data(), multdof3_model.v[sph_body_id].data(), 6, TEST_PREC); + CHECK_ARRAY_CLOSE (emulated_model.a[emu_body_id].data(), multdof3_model.a[sph_body_id].data(), 6, TEST_PREC); + + UpdateKinematics (multdof3_model, sphQ, sphQDot, sphQDDot); + + CHECK_ARRAY_CLOSE (emulated_model.v[emu_child_id].data(), multdof3_model.v[sph_child_id].data(), 6, TEST_PREC); + CHECK_ARRAY_CLOSE (emulated_model.a[emu_child_id].data(), multdof3_model.a[sph_child_id].data(), 6, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestSpatialVelocities) { + emuQ[0] = 1.; + emuQ[1] = 2.; + emuQ[2] = 3.; + emuQ[3] = 4.; + + emuQDot[0] = 4.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 6.; + + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDot, multdof3_model, &sphQ, &sphQDot); + + UpdateKinematicsCustom (emulated_model, &emuQ, &emuQDot, NULL); + UpdateKinematicsCustom (multdof3_model, &sphQ, &sphQDot, NULL); + + CHECK_ARRAY_CLOSE (emulated_model.v[emu_child_id].data(), multdof3_model.v[sph_child_id].data(), 6, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestForwardDynamicsQAndQDot) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + + emuQDot[0] = 2.2; + emuQDot[1] = 2.3; + emuQDot[2] = 2.4; + emuQDot[3] = 2.5; + + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDot, multdof3_model, &sphQ, &sphQDot); + + ForwardDynamics (emulated_model, emuQ, emuQDot, emuTau, emuQDDot); + ForwardDynamics (multdof3_model, sphQ, sphQDot, sphTau, sphQDDot); + + CHECK_ARRAY_CLOSE (emulated_model.a[emu_child_id].data(), multdof3_model.a[sph_child_id].data(), 6, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestDynamicsConsistencyRNEA_ABA ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + sphTau[0] = 5.; + sphTau[1] = 4.; + sphTau[2] = 7.; + sphTau[3] = 3.; + sphTau[4] = 2.; + + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDot, multdof3_model, &sphQ, &sphQDot); + + ForwardDynamics (multdof3_model, sphQ, sphQDot, sphTau, sphQDDot); + + VectorNd tau_id (VectorNd::Zero (multdof3_model.qdot_size)); + InverseDynamics (multdof3_model, sphQ, sphQDot, sphQDDot, tau_id); + + CHECK_ARRAY_CLOSE (sphTau.data(), tau_id.data(), tau_id.size(), TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestCRBA ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + sphTau[0] = 5.; + sphTau[1] = 4.; + sphTau[2] = 7.; + sphTau[3] = 3.; + sphTau[4] = 2.; + + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDot, multdof3_model, &sphQ, &sphQDot); + + MatrixNd H_crba (MatrixNd::Zero (multdof3_model.qdot_size, multdof3_model.qdot_size)); + + UpdateKinematicsCustom (multdof3_model, &sphQ, NULL, NULL); + CompositeRigidBodyAlgorithm (multdof3_model, sphQ, H_crba, false); + + MatrixNd H_id (MatrixNd::Zero (multdof3_model.qdot_size, multdof3_model.qdot_size)); + VectorNd H_col = VectorNd::Zero (multdof3_model.qdot_size); + VectorNd QDDot_zero = VectorNd::Zero (multdof3_model.qdot_size); + + for (unsigned int i = 0; i < multdof3_model.qdot_size; i++) { + // compute each column + VectorNd delta_a = VectorNd::Zero (multdof3_model.qdot_size); + delta_a[i] = 1.; + + // compute ID (model, q, qdot, delta_a) + VectorNd id_delta = VectorNd::Zero (multdof3_model.qdot_size); + InverseDynamics (multdof3_model, sphQ, sphQDot, delta_a, id_delta); + + // compute ID (model, q, qdot, zero) + VectorNd id_zero = VectorNd::Zero (multdof3_model.qdot_size); + InverseDynamics (multdof3_model, sphQ, sphQDot, QDDot_zero, id_zero); + + H_col = id_delta - id_zero; + H_id.block(0, i, multdof3_model.qdot_size, 1) = H_col; + } + + CHECK_ARRAY_CLOSE (H_id.data(), H_crba.data(), multdof3_model.qdot_size * multdof3_model.qdot_size, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestForwardDynamicsLagrangianVsABA ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + sphTau[0] = 5.; + sphTau[1] = 4.; + sphTau[2] = 7.; + sphTau[3] = 3.; + sphTau[4] = 2.; + + ConvertQAndQDotFromEmulated (emulated_model, emuQ, emuQDot, multdof3_model, &sphQ, &sphQDot); + + VectorNd QDDot_aba = VectorNd::Zero (multdof3_model.qdot_size); + VectorNd QDDot_lag = VectorNd::Zero (multdof3_model.qdot_size); + + ForwardDynamicsLagrangian (multdof3_model, sphQ, sphQDot, sphTau, QDDot_lag); + ForwardDynamics (multdof3_model, sphQ, sphQDot, sphTau, QDDot_aba); + + CHECK_ARRAY_CLOSE (QDDot_lag.data(), QDDot_aba.data(), multdof3_model.qdot_size, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestContactsLagrangian) { + ConstraintSet constraint_set_emu; + + constraint_set_emu.AddContactConstraint (emu_child_id, Vector3d (0., 0., -1.), Vector3d (1., 0., 0.)); + constraint_set_emu.AddContactConstraint (emu_child_id, Vector3d (0., 0., -1.), Vector3d (0., 1., 0.)); + constraint_set_emu.AddContactConstraint (emu_child_id, Vector3d (0., 0., -1.), Vector3d (0., 0., 1.)); + + constraint_set_emu.Bind(emulated_model); + + ConstraintSet constraint_set_sph; + + constraint_set_sph.AddContactConstraint (sph_child_id, Vector3d (0., 0., -1.), Vector3d (1., 0., 0.)); + constraint_set_sph.AddContactConstraint (sph_child_id, Vector3d (0., 0., -1.), Vector3d (0., 1., 0.)); + constraint_set_sph.AddContactConstraint (sph_child_id, Vector3d (0., 0., -1.), Vector3d (0., 0., 1.)); + + constraint_set_sph.Bind(multdof3_model); + + ForwardDynamicsConstraintsDirect (emulated_model, emuQ, emuQDot, emuTau, constraint_set_emu, emuQDDot); + VectorNd emu_force_lagrangian = constraint_set_emu.force; + ForwardDynamicsConstraintsDirect (multdof3_model, sphQ, sphQDot, sphTau, constraint_set_sph, sphQDDot); + VectorNd sph_force_lagrangian = constraint_set_sph.force; + + CHECK_ARRAY_CLOSE (emu_force_lagrangian.data(), sph_force_lagrangian.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestContacts) { + ConstraintSet constraint_set_emu; + + constraint_set_emu.AddContactConstraint (emu_child_id, Vector3d (0., 0., -1.), Vector3d (1., 0., 0.)); + constraint_set_emu.AddContactConstraint (emu_child_id, Vector3d (0., 0., -1.), Vector3d (0., 1., 0.)); + constraint_set_emu.AddContactConstraint (emu_child_id, Vector3d (0., 0., -1.), Vector3d (0., 0., 1.)); + + constraint_set_emu.Bind(emulated_model); + + ConstraintSet constraint_set_sph; + + constraint_set_sph.AddContactConstraint (sph_child_id, Vector3d (0., 0., -1.), Vector3d (1., 0., 0.)); + constraint_set_sph.AddContactConstraint (sph_child_id, Vector3d (0., 0., -1.), Vector3d (0., 1., 0.)); + constraint_set_sph.AddContactConstraint (sph_child_id, Vector3d (0., 0., -1.), Vector3d (0., 0., 1.)); + + constraint_set_sph.Bind(multdof3_model); + + ForwardDynamicsContactsKokkevis (emulated_model, emuQ, emuQDot, emuTau, constraint_set_emu, emuQDDot); + VectorNd emu_force_kokkevis = constraint_set_emu.force; + ForwardDynamicsContactsKokkevis (multdof3_model, sphQ, sphQDot, sphTau, constraint_set_sph, sphQDDot); + VectorNd sph_force_kokkevis = constraint_set_sph.force; + + CHECK_ARRAY_CLOSE (emu_force_kokkevis.data(), sph_force_kokkevis.data(), 3, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestEulerZYXvsEmulatedLagrangian ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + emuTau[0] = 5.; + emuTau[1] = 4.; + emuTau[2] = 7.; + emuTau[3] = 3.; + emuTau[4] = 2.; + + VectorNd QDDot_emu = VectorNd::Zero (emulated_model.qdot_size); + VectorNd QDDot_eulerzyx = VectorNd::Zero (eulerzyx_model.qdot_size); + + ForwardDynamicsLagrangian (emulated_model, emuQ, emuQDot, emuTau, QDDot_emu); + ForwardDynamicsLagrangian (eulerzyx_model, emuQ, emuQDot, emuTau, QDDot_eulerzyx); + + CHECK_ARRAY_CLOSE (QDDot_emu.data(), QDDot_eulerzyx.data(), emulated_model.qdot_size, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestEulerZYXvsEmulatedArticulatedBodyAlgorithm ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + emuTau[0] = 5.; + emuTau[1] = 4.; + emuTau[2] = 7.; + emuTau[3] = 3.; + emuTau[4] = 2.; + + VectorNd QDDot_emu = VectorNd::Zero (emulated_model.qdot_size); + VectorNd QDDot_eulerzyx = VectorNd::Zero (eulerzyx_model.qdot_size); + + ForwardDynamics (emulated_model, emuQ, emuQDot, emuTau, QDDot_emu); + ForwardDynamics (eulerzyx_model, emuQ, emuQDot, emuTau, QDDot_eulerzyx); + + CHECK_ARRAY_CLOSE (QDDot_emu.data(), QDDot_eulerzyx.data(), emulated_model.qdot_size, TEST_PREC); +} + +TEST_FIXTURE(SphericalJoint, TestEulerZYXvsEmulatedContacts ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + emuTau[0] = 5.; + emuTau[1] = 4.; + emuTau[2] = 7.; + emuTau[3] = 3.; + emuTau[4] = 2.; + + VectorNd QDDot_emu = VectorNd::Zero (emulated_model.qdot_size); + VectorNd QDDot_eulerzyx = VectorNd::Zero (eulerzyx_model.qdot_size); + + ConstraintSet CS_euler; + CS_euler.AddContactConstraint (eulerzyx_child_id, Vector3d (1., 1., 1.), Vector3d (1., 0., 0.)); + CS_euler.AddContactConstraint (eulerzyx_child_id, Vector3d (0., 0., 0.), Vector3d (0., 1., 0.)); + CS_euler.AddContactConstraint (eulerzyx_child_id, Vector3d (0., 0., 0.), Vector3d (0., 0., 1.)); + CS_euler.Bind (eulerzyx_model); + + ConstraintSet CS_emulated; + CS_emulated.AddContactConstraint (emu_child_id, Vector3d (1., 1., 1.), Vector3d (1., 0., 0.)); + CS_emulated.AddContactConstraint (emu_child_id, Vector3d (0., 0., 0.), Vector3d (0., 1., 0.)); + CS_emulated.AddContactConstraint (emu_child_id, Vector3d (0., 0., 0.), Vector3d (0., 0., 1.)); + CS_emulated.Bind (emulated_model); + + ForwardDynamicsConstraintsDirect (emulated_model, emuQ, emuQDot, emuTau, CS_emulated, QDDot_emu); + ForwardDynamicsConstraintsDirect (eulerzyx_model, emuQ, emuQDot, emuTau, CS_euler, QDDot_eulerzyx); + + CHECK_ARRAY_CLOSE (QDDot_emu.data(), QDDot_eulerzyx.data(), emulated_model.qdot_size, TEST_PREC); + + ClearLogOutput(); + + ForwardDynamicsContactsKokkevis (emulated_model, emuQ, emuQDot, emuTau, CS_emulated, QDDot_emu); + ForwardDynamicsContactsKokkevis (eulerzyx_model, emuQ, emuQDot, emuTau, CS_euler, QDDot_eulerzyx); + + CHECK_ARRAY_CLOSE (QDDot_emu.data(), QDDot_eulerzyx.data(), emulated_model.qdot_size, TEST_PREC * QDDot_emu.norm()); + + ForwardDynamicsContactsKokkevis (emulated_model, emuQ, emuQDot, emuTau, CS_emulated, QDDot_emu); + ForwardDynamicsConstraintsDirect (eulerzyx_model, emuQ, emuQDot, emuTau, CS_euler, QDDot_eulerzyx); + + CHECK_ARRAY_CLOSE (QDDot_emu.data(), QDDot_eulerzyx.data(), emulated_model.qdot_size, TEST_PREC * QDDot_emu.norm()); +} + +TEST_FIXTURE(SphericalJoint, TestEulerZYXvsEmulatedCRBA ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + MatrixNd H_emulated (MatrixNd::Zero (emulated_model.q_size, emulated_model.q_size)); + MatrixNd H_eulerzyx (MatrixNd::Zero (eulerzyx_model.q_size, eulerzyx_model.q_size)); + + CompositeRigidBodyAlgorithm (emulated_model, emuQ, H_emulated); + CompositeRigidBodyAlgorithm (eulerzyx_model, emuQ, H_eulerzyx); + + CHECK_ARRAY_CLOSE (H_emulated.data(), H_eulerzyx.data(), emulated_model.q_size * emulated_model.q_size, TEST_PREC); +} + +TEST ( TestJointTypeTranslationZYX ) { + Model model_emulated; + Model model_3dof; + + Body body (1., Vector3d (1., 2., 1.), Matrix3d (1., 0., 0, 0., 1., 0., 0., 0., 1.)); + Joint joint_emulated ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.) + ); + Joint joint_3dof (JointTypeTranslationXYZ); + + model_emulated.AppendBody (SpatialTransform (), joint_emulated, body); + model_3dof.AppendBody (SpatialTransform (), joint_3dof, body); + + VectorNd q (VectorNd::Zero (model_emulated.q_size)); + VectorNd qdot (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_emulated (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_3dof (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd tau (VectorNd::Zero (model_emulated.qdot_size)); + + for (int i = 0; i < q.size(); i++) { + q[i] = 1.1 * (static_cast(i + 1)); + qdot[i] = 0.1 * (static_cast(i + 1)); + qddot_3dof[i] = 0.21 * (static_cast(i + 1)); + tau[i] = 2.1 * (static_cast(i + 1)); + } + + qddot_emulated = qddot_3dof; + VectorNd id_emulated (tau); + VectorNd id_3dof(tau); + InverseDynamics (model_emulated, q, qdot, qddot_emulated, id_emulated); + InverseDynamics (model_3dof, q, qdot, qddot_emulated, id_3dof); + CHECK_ARRAY_CLOSE (id_emulated.data(), id_3dof.data(), id_emulated.size(), TEST_PREC * id_emulated.norm()); + + ForwardDynamicsLagrangian (model_emulated, q, qdot, tau, qddot_emulated); + ForwardDynamicsLagrangian (model_3dof, q, qdot, tau, qddot_3dof); + + CHECK_ARRAY_EQUAL (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size()); + + MatrixNd H_emulated (MatrixNd::Zero (q.size(), q.size())); + MatrixNd H_3dof (MatrixNd::Zero (q.size(), q.size())); + + CompositeRigidBodyAlgorithm (model_emulated, q, H_emulated); + CompositeRigidBodyAlgorithm (model_3dof, q, H_3dof); + + CHECK_ARRAY_CLOSE (H_emulated.data(), H_3dof.data(), q.size() * q.size(), TEST_PREC); +} + +TEST ( TestJointTypeEulerXYZ ) { + Model model_emulated; + Model model_3dof; + + Body body (1., Vector3d (1., 2., 1.), Matrix3d (1., 0., 0, 0., 1., 0., 0., 0., 1.)); + Joint joint_emulated ( + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + Joint joint_3dof (JointTypeEulerXYZ); + + model_emulated.AppendBody (SpatialTransform (), joint_emulated, body); + model_3dof.AppendBody (SpatialTransform (), joint_3dof, body); + + VectorNd q (VectorNd::Zero (model_emulated.q_size)); + VectorNd qdot (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_emulated (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_3dof (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd tau (VectorNd::Zero (model_emulated.qdot_size)); + + for (int i = 0; i < q.size(); i++) { + q[i] = 1.1 * (static_cast(i + 1)); + qdot[i] = 0.55* (static_cast(i + 1)); + qddot_emulated[i] = 0.23 * (static_cast(i + 1)); + qddot_3dof[i] = 0.22 * (static_cast(i + 1)); + tau[i] = 2.1 * (static_cast(i + 1)); + } + + UpdateKinematicsCustom (model_emulated, &q, &qdot, &qddot_emulated); + UpdateKinematicsCustom (model_3dof, &q, &qdot, &qddot_emulated); + + CHECK_ARRAY_EQUAL (model_emulated.X_base[3].E.data(), model_3dof.X_base[1].E.data(), 9); + CHECK_ARRAY_EQUAL (model_emulated.v[3].data(), model_3dof.v[1].data(), 6); + + ForwardDynamicsLagrangian (model_emulated, q, qdot, tau, qddot_emulated); + ForwardDynamicsLagrangian (model_3dof, q, qdot, tau, qddot_3dof); + + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC); + + MatrixNd H_emulated (MatrixNd::Zero (q.size(), q.size())); + MatrixNd H_3dof (MatrixNd::Zero (q.size(), q.size())); + + CompositeRigidBodyAlgorithm (model_emulated, q, H_emulated); + CompositeRigidBodyAlgorithm (model_3dof, q, H_3dof); + + CHECK_ARRAY_CLOSE (H_emulated.data(), H_3dof.data(), q.size() * q.size(), TEST_PREC); +} + +TEST ( TestJointTypeEulerYXZ ) { + Model model_emulated; + Model model_3dof; + + Body body (1., Vector3d (1., 2., 1.), Matrix3d (1., 0., 0, 0., 1., 0., 0., 0., 1.)); + Joint joint_emulated ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + Joint joint_3dof (JointTypeEulerYXZ); + + model_emulated.AppendBody (SpatialTransform (), joint_emulated, body); + model_3dof.AppendBody (SpatialTransform (), joint_3dof, body); + + VectorNd q (VectorNd::Zero (model_emulated.q_size)); + VectorNd qdot (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_emulated (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_3dof (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd tau (VectorNd::Zero (model_emulated.qdot_size)); + + for (int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot_3dof[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + qddot_emulated = qddot_3dof; + + UpdateKinematicsCustom (model_emulated, &q, &qdot, &qddot_emulated); + UpdateKinematicsCustom (model_3dof, &q, &qdot, &qddot_emulated); + + CHECK_ARRAY_CLOSE (model_emulated.X_base[3].E.data(), model_3dof.X_base[1].E.data(), 9, TEST_PREC); + CHECK_ARRAY_CLOSE (model_emulated.v[3].data(), model_3dof.v[1].data(), 6, TEST_PREC); + + VectorNd id_emulated (tau); + VectorNd id_3dof(tau); + InverseDynamics (model_emulated, q, qdot, qddot_emulated, id_emulated); + InverseDynamics (model_3dof, q, qdot, qddot_emulated, id_3dof); + + CHECK_ARRAY_CLOSE (id_emulated.data(), id_3dof.data(), id_emulated.size(), TEST_PREC); + + ForwardDynamicsLagrangian (model_emulated, q, qdot, tau, qddot_emulated); + ForwardDynamicsLagrangian (model_3dof, q, qdot, tau, qddot_3dof); + + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC); + + MatrixNd H_emulated (MatrixNd::Zero (q.size(), q.size())); + MatrixNd H_3dof (MatrixNd::Zero (q.size(), q.size())); + + CompositeRigidBodyAlgorithm (model_emulated, q, H_emulated); + CompositeRigidBodyAlgorithm (model_3dof, q, H_3dof); + + CHECK_ARRAY_CLOSE (H_emulated.data(), H_3dof.data(), q.size() * q.size(), TEST_PREC); +} + +TEST_FIXTURE (Human36, TestUpdateKinematics) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd id_emulated (tau); + VectorNd id_3dof (tau); + + UpdateKinematics (*model_emulated, q, qdot, qddot); + UpdateKinematics (*model_3dof, q, qdot, qddot); + + CHECK_ARRAY_CLOSE (model_emulated->v[body_id_emulated[BodyPelvis]].data(), model_3dof->v[body_id_3dof[BodyPelvis]].data(), 6, TEST_PREC); + CHECK_ARRAY_CLOSE (model_emulated->v[body_id_emulated[BodyThighRight]].data(), model_3dof->v[body_id_3dof[BodyThighRight]].data(), 6, TEST_PREC); + CHECK_ARRAY_CLOSE (model_emulated->v[body_id_emulated[BodyShankRight]].data(), model_3dof->v[body_id_3dof[BodyShankRight]].data(), 6, TEST_PREC); +} + +TEST_FIXTURE (Human36, TestInverseDynamics) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd id_emulated (tau); + VectorNd id_3dof (tau); + + ClearLogOutput(); + InverseDynamics (*model_emulated, q, qdot, qddot, id_emulated); + InverseDynamics (*model_3dof, q, qdot, qddot, id_3dof); + + CHECK_ARRAY_CLOSE (id_emulated.data(), id_3dof.data(), id_emulated.size(), TEST_PREC * id_emulated.norm()); +} + +TEST_FIXTURE (Human36, TestNonlinearEffects) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd nle_emulated (tau); + VectorNd nle_3dof (tau); + + ClearLogOutput(); + NonlinearEffects (*model_emulated, q, qdot, nle_emulated); + NonlinearEffects (*model_3dof, q, qdot, nle_3dof); + + CHECK_ARRAY_CLOSE (nle_emulated.data(), nle_3dof.data(), nle_emulated.size(), TEST_PREC * nle_emulated.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedLagrangianKokkevis) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd qddot_lagrangian (qddot_emulated); + VectorNd qddot_kokkevis (qddot_emulated); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_lagrangian); + ForwardDynamicsContactsKokkevis (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_kokkevis); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_kokkevis.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_lagrangian); + ForwardDynamicsContactsKokkevis (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_kokkevis); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_kokkevis.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_lagrangian); + ForwardDynamicsContactsKokkevis (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_kokkevis); + + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_kokkevis.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedLagrangianSparse) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = -0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd qddot_lagrangian (qddot_emulated); + VectorNd qddot_sparse (qddot_emulated); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_lagrangian); + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_sparse); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_sparse.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_lagrangian); + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_sparse); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_sparse.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_lagrangian); + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_sparse); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_sparse.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedLagrangianNullSpace) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = -0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd qddot_lagrangian (qddot_emulated); + VectorNd qddot_nullspace (qddot_emulated); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_lagrangian); + ForwardDynamicsConstraintsNullSpace (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_nullspace); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_nullspace.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_lagrangian); + ForwardDynamicsConstraintsNullSpace (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_nullspace); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_nullspace.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_lagrangian); + ForwardDynamicsConstraintsNullSpace (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_nullspace); + CHECK_ARRAY_CLOSE (qddot_lagrangian.data(), qddot_nullspace.data(), qddot_lagrangian.size(), TEST_PREC * qddot_lagrangian.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedMultdofLagrangian) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qddot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_emulated); + ForwardDynamicsConstraintsDirect (*model_3dof, q, qdot, tau, constraints_1B1C_3dof, qddot_3dof); + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC * qddot_emulated.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_emulated); + ForwardDynamicsConstraintsDirect (*model_3dof, q, qdot, tau, constraints_1B4C_3dof, qddot_3dof); + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC * qddot_emulated.norm()); + + ForwardDynamicsConstraintsDirect (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_emulated); + ForwardDynamicsConstraintsDirect (*model_3dof, q, qdot, tau, constraints_4B4C_3dof, qddot_3dof); + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC * qddot_emulated.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedMultdofSparse) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_emulated); + + for (unsigned int i = 0; i < q.size(); i++) { + CHECK_EQUAL (model_emulated->lambda_q[i], model_3dof->lambda_q[i]); + } + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraints_1B1C_3dof, qddot_3dof); + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC * qddot_emulated.norm()); + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_1B4C_emulated, qddot_emulated); + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraints_1B4C_3dof, qddot_3dof); + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC * qddot_emulated.norm()); + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_4B4C_emulated, qddot_emulated); + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraints_4B4C_3dof, qddot_3dof); + CHECK_ARRAY_CLOSE (qddot_emulated.data(), qddot_3dof.data(), qddot_emulated.size(), TEST_PREC * qddot_emulated.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedMultdofKokkevisSparse) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_emulated, q, qdot, tau, constraints_1B1C_emulated, qddot_emulated); + + for (unsigned int i = 0; i < q.size(); i++) { + CHECK_EQUAL (model_emulated->lambda_q[i], model_3dof->lambda_q[i]); + } + + VectorNd qddot_sparse (qddot_emulated); + VectorNd qddot_kokkevis (qddot_emulated); + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraints_1B1C_3dof, qddot_sparse); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_1B1C_3dof, qddot_kokkevis); + CHECK_ARRAY_CLOSE (qddot_sparse.data(), qddot_kokkevis.data(), qddot_sparse.size(), TEST_PREC * qddot_sparse.norm()); + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraints_1B4C_3dof, qddot_sparse); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_1B4C_3dof, qddot_kokkevis); + CHECK_ARRAY_CLOSE (qddot_sparse.data(), qddot_kokkevis.data(), qddot_sparse.size(), TEST_PREC * qddot_sparse.norm()); + + ForwardDynamicsConstraintsRangeSpaceSparse (*model_3dof, q, qdot, tau, constraints_4B4C_3dof, qddot_sparse); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_4B4C_3dof, qddot_kokkevis); + CHECK_ARRAY_CLOSE (qddot_sparse.data(), qddot_kokkevis.data(), qddot_sparse.size(), TEST_PREC * qddot_sparse.norm()); +} + +TEST_FIXTURE (Human36, TestContactsEmulatedMultdofKokkevisMultiple ) { + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + qdot[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + tau[i] = 0.5 * M_PI * static_cast(rand()) / static_cast(RAND_MAX); + } + + VectorNd qddot_kokkevis (qddot_emulated); + VectorNd qddot_kokkevis_2 (qddot_emulated); + + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_1B1C_3dof, qddot_kokkevis); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_1B1C_3dof, qddot_kokkevis_2); + CHECK_ARRAY_CLOSE (qddot_kokkevis.data(), qddot_kokkevis_2.data(), qddot_kokkevis.size(), TEST_PREC * qddot_kokkevis.norm()); + + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_1B4C_3dof, qddot_kokkevis); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_1B4C_3dof, qddot_kokkevis_2); + CHECK_ARRAY_CLOSE (qddot_kokkevis.data(), qddot_kokkevis_2.data(), qddot_kokkevis.size(), TEST_PREC * qddot_kokkevis.norm()); + + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_4B4C_3dof, qddot_kokkevis); + ForwardDynamicsContactsKokkevis (*model_3dof, q, qdot, tau, constraints_4B4C_3dof, qddot_kokkevis_2); + CHECK_ARRAY_CLOSE (qddot_kokkevis.data(), qddot_kokkevis_2.data(), qddot_kokkevis.size(), TEST_PREC * qddot_kokkevis.norm()); +} + +TEST_FIXTURE(SphericalJoint, TestEulerZYXvsEmulated ) { + emuQ[0] = 1.1; + emuQ[1] = 1.2; + emuQ[2] = 1.3; + emuQ[3] = 1.4; + emuQ[4] = 1.5; + + emuQDot[0] = 1.; + emuQDot[1] = 2.; + emuQDot[2] = 3.; + emuQDot[3] = 4.; + emuQDot[4] = 5.; + + emuTau[0] = 5.; + emuTau[1] = 4.; + emuTau[2] = 7.; + emuTau[3] = 3.; + emuTau[4] = 2.; + + VectorNd QDDot_emu = VectorNd::Zero (emulated_model.qdot_size); + VectorNd QDDot_eulerzyx = VectorNd::Zero (eulerzyx_model.qdot_size); + + ForwardDynamicsLagrangian (emulated_model, emuQ, emuQDot, emuTau, QDDot_emu); + ForwardDynamicsLagrangian (eulerzyx_model, emuQ, emuQDot, emuTau, QDDot_eulerzyx); + + CHECK_ARRAY_CLOSE (QDDot_emu.data(), QDDot_eulerzyx.data(), emulated_model.qdot_size, TEST_PREC); +} + +TEST_FIXTURE ( Human36, SolveMInvTimesTau) { + for (unsigned int i = 0; i < model->dof_count; i++) { + q[i] = rand() / static_cast(RAND_MAX); + tau[i] = rand() / static_cast(RAND_MAX); + } + + MatrixNd M (MatrixNd::Zero(model->dof_count, model->dof_count)); + CompositeRigidBodyAlgorithm (*model, q, M); + + VectorNd qddot_solve_llt = M.llt().solve(tau); + + VectorNd qddot_minv (q); + CalcMInvTimesTau (*model, q, tau, qddot_minv); + + CHECK_ARRAY_CLOSE (qddot_solve_llt.data(), qddot_minv.data(), model->dof_count, TEST_PREC * qddot_minv.norm()); +} + +TEST_FIXTURE ( Human36, SolveMInvTimesTauReuse) { + for (unsigned int i = 0; i < model->dof_count; i++) { + q[i] = rand() / static_cast(RAND_MAX); + tau[i] = rand() / static_cast(RAND_MAX); + } + + MatrixNd M (MatrixNd::Zero(model->dof_count, model->dof_count)); + CompositeRigidBodyAlgorithm (*model, q, M); + + VectorNd qddot_solve_llt = M.llt().solve(tau); + + VectorNd qddot_minv (q); + CalcMInvTimesTau (*model, q, tau, qddot_minv); + + for (unsigned int j = 0; j < 1; j++) { + for (unsigned int i = 0; i < model->dof_count; i++) { + tau[i] = rand() / static_cast(RAND_MAX); + } + + CompositeRigidBodyAlgorithm (*model, q, M); + qddot_solve_llt = M.llt().solve(tau); + + CalcMInvTimesTau (*model, q, tau, qddot_minv); + + CHECK_ARRAY_CLOSE (qddot_solve_llt.data(), qddot_minv.data(), model->dof_count, TEST_PREC * qddot_solve_llt.norm()); + } +} diff --git a/3rdparty/rbdl/tests/ScrewJointTests.cc b/3rdparty/rbdl/tests/ScrewJointTests.cc new file mode 100644 index 0000000..e0a0680 --- /dev/null +++ b/3rdparty/rbdl/tests/ScrewJointTests.cc @@ -0,0 +1,115 @@ +#include + +#include +#include + +#include "Fixtures.h" +#include "Human36Fixture.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Constraints.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + + + +const double TEST_PREC = 1.0e-12; + +struct ScrewJoint1DofFixedBase { + ScrewJoint1DofFixedBase() { + using namespace RigidBodyDynamics; + using namespace RigidBodyDynamics::Math; + + ClearLogOutput(); + model = new Model; + + /* Single screw joint with a fixed base. Rotation about Z, translation along X. + * A rolling log. + */ + + Body body = Body (1., Vector3d (0., 0, 0.), Vector3d (1., 1., 1.)); + + Joint joint = Joint (SpatialVector (0., 0., 1., 1., 0., 0.)); + + roller = model->AppendBody (Xtrans (Vector3d (0., 0., 0.)), joint, body, "Roller"); + + q = VectorNd::Constant ((size_t) model->dof_count, 0.); + qdot = VectorNd::Constant ((size_t) model->dof_count, 0.); + qddot = VectorNd::Constant ((size_t) model->dof_count, 0.); + tau = VectorNd::Constant ((size_t) model->dof_count, 0.); + + epsilon = 1e-8; + + ClearLogOutput(); + } + ~ScrewJoint1DofFixedBase() { + delete model; + } + + RigidBodyDynamics::Model *model; + + RigidBodyDynamics::Math::VectorNd q; + RigidBodyDynamics::Math::VectorNd qdot; + RigidBodyDynamics::Math::VectorNd qddot; + RigidBodyDynamics::Math::VectorNd tau; + + unsigned int roller; + double epsilon; +}; + + +TEST_FIXTURE ( ScrewJoint1DofFixedBase, UpdateKinematics ) { + q[0] = 1; + qdot[0] = 2; + qddot[0] = 0; + + UpdateKinematics (*model, q, qdot, qddot); + + CHECK_ARRAY_EQUAL (Xrot(1,Vector3d(0,0,1)).E.data(), model->X_base[roller].E.data(), 9); + CHECK_ARRAY_EQUAL (Vector3d(1,0,0).data(), model->X_base[roller].r.data(), 3); + CHECK_ARRAY_EQUAL (SpatialVector(0.,0.,2.,cos(q[0])*2,-sin(q[0])*2.,0.).data(), model->v[roller].data(), 6); + + SpatialVector a0(model->a[roller]); + SpatialVector v0(model->v[roller]); + + q[0] = 1+2*epsilon; + qdot[0] = 2; + qddot[0] = 0; + + UpdateKinematics (*model, q, qdot, qddot); + + v0 = model->v[roller] - v0; + v0 /= epsilon; + + CHECK_ARRAY_CLOSE (a0.data(),v0.data(), 6, 1e-5); //finite diff vs. analytical derivative + +} + +TEST_FIXTURE ( ScrewJoint1DofFixedBase, Jacobians ) { + q[0] = 1; + qdot[0] = 0; + qddot[0] = 9; + + Vector3d refPt = Vector3d(1,0,3); + MatrixNd GrefPt = MatrixNd::Constant(3,1,0.); + MatrixNd Gexpected = MatrixNd::Constant(3,1,0.); + Vector3d refPtBaseCoord = Vector3d(); + + refPtBaseCoord = CalcBodyToBaseCoordinates(*model, q, roller, refPt); + + CHECK_ARRAY_EQUAL (Vector3d(1+cos(1), sin(1), 3).data(), refPtBaseCoord.data(), 3); + + CalcPointJacobian(*model, q, roller, refPt, GrefPt); + + Gexpected(0,0) = 1 - sin(1); + Gexpected(1,0) = cos(1); + Gexpected(2,0) = 0; + + CHECK_ARRAY_EQUAL (Gexpected.data(), GrefPt.data(), 3); +} diff --git a/3rdparty/rbdl/tests/SparseFactorizationTests.cc b/3rdparty/rbdl/tests/SparseFactorizationTests.cc new file mode 100644 index 0000000..ec50a17 --- /dev/null +++ b/3rdparty/rbdl/tests/SparseFactorizationTests.cc @@ -0,0 +1,268 @@ +#include + +#include + +#include "Fixtures.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/rbdl_utils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-12; + +TEST_FIXTURE (FloatingBase12DoF, TestSparseFactorizationLTL) { + for (unsigned int i = 0; i < model->q_size; i++) { + Q[i] = static_cast (i + 1) * 0.1; + } + + MatrixNd H (MatrixNd::Zero (model->qdot_size, model->qdot_size)); + + CompositeRigidBodyAlgorithm (*model, Q, H); + + MatrixNd L (H); + SparseFactorizeLTL (*model, L); + MatrixNd LTL = L.transpose() * L; + + CHECK_ARRAY_CLOSE (H.data(), LTL.data(), model->qdot_size * model->qdot_size, TEST_PREC); +} + +TEST_FIXTURE (FloatingBase12DoF, TestSparseSolveLx) { + for (unsigned int i = 0; i < model->q_size; i++) { + Q[i] = static_cast (i + 1) * 0.1; + } + + MatrixNd H (MatrixNd::Zero (model->qdot_size, model->qdot_size)); + + CompositeRigidBodyAlgorithm (*model, Q, H); + + MatrixNd L (H); + SparseFactorizeLTL (*model, L); + VectorNd x = L * Q; + + SparseSolveLx (*model, L, x); + + CHECK_ARRAY_CLOSE (Q.data(), x.data(), model->qdot_size, TEST_PREC); +} + +TEST_FIXTURE (FloatingBase12DoF, TestSparseSolveLTx) { + for (unsigned int i = 0; i < model->q_size; i++) { + Q[i] = static_cast (i + 1) * 0.1; + } + + MatrixNd H (MatrixNd::Zero (model->qdot_size, model->qdot_size)); + + CompositeRigidBodyAlgorithm (*model, Q, H); + + MatrixNd L (H); + SparseFactorizeLTL (*model, L); + VectorNd x = L.transpose() * Q; + + SparseSolveLTx (*model, L, x); + + CHECK_ARRAY_CLOSE (Q.data(), x.data(), model->qdot_size, TEST_PREC); +} + +TEST_FIXTURE (FixedBase6DoF12DoFFloatingBase, ForwardDynamicsContactsSparse) { + ConstraintSet constraint_set_var1; + + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (1., 0., 0.)); + constraint_set.AddContactConstraint (contact_body_id, contact_point, Vector3d (0., 1., 0.)); + constraint_set.AddContactConstraint (child_2_id, contact_point, Vector3d (0., 1., 0.)); + + constraint_set_var1 = constraint_set.Copy(); + constraint_set_var1.Bind (*model); + constraint_set.Bind (*model); + + VectorNd QDDot_var1 = VectorNd::Constant (model->dof_count, 0.); + + Q[0] = 0.1; + Q[1] = -0.3; + Q[2] = 0.15; + Q[3] = -0.21; + Q[4] = -0.81; + Q[5] = 0.11; + Q[6] = 0.31; + Q[7] = -0.91; + Q[8] = 0.61; + + QDot[0] = 1.3; + QDot[1] = -1.7; + QDot[2] = 3; + QDot[3] = -2.5; + QDot[4] = 1.5; + QDot[5] = -5.5; + QDot[6] = 2.5; + QDot[7] = -1.5; + QDot[8] = -3.5; + + ClearLogOutput(); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set, QDDot); + + ClearLogOutput(); + ForwardDynamicsConstraintsRangeSpaceSparse (*model, Q, QDot, Tau, constraint_set_var1, QDDot_var1); + + CHECK_ARRAY_CLOSE (QDDot.data(), QDDot_var1.data(), QDDot.size(), TEST_PREC); +} + +TEST ( TestSparseFactorizationMultiDof) { + Model model_emulated; + Model model_3dof; + + Body body (1., Vector3d (1., 2., 1.), Matrix3d (1., 0., 0, 0., 1., 0., 0., 0., 1.)); + Joint joint_emulated ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + Joint joint_3dof (JointTypeEulerYXZ); + + Joint joint_rot_y ( + SpatialVector (0., 1., 0., 0., 0., 0.) + ); + + model_emulated.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_rot_y, body); + unsigned int multdof_body_id_emulated = model_emulated.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_emulated, body); + model_emulated.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_emulated, body); + model_emulated.AddBody (multdof_body_id_emulated, SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_rot_y, body); + model_emulated.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_emulated, body); + + model_3dof.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_rot_y, body); + unsigned int multdof_body_id_3dof = model_3dof.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_3dof, body); + model_3dof.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_3dof, body); + model_3dof.AddBody (multdof_body_id_3dof, SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_rot_y, body); + model_3dof.AppendBody (SpatialTransform (Matrix3d::Identity(), Vector3d::Zero()), joint_3dof, body); + + VectorNd q (VectorNd::Zero (model_emulated.q_size)); + VectorNd qdot (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_emulated (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_3dof (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd tau (VectorNd::Zero (model_emulated.qdot_size)); + + for (int i = 0; i < q.size(); i++) { + q[i] = 1.1 * (static_cast(i + 1)); + qdot[i] = 0.55* (static_cast(i + 1)); + qddot_emulated[i] = 0.23 * (static_cast(i + 1)); + qddot_3dof[i] = 0.22 * (static_cast(i + 1)); + tau[i] = 2.1 * (static_cast(i + 1)); + } + + MatrixNd H_emulated (MatrixNd::Zero (q.size(), q.size())); + MatrixNd H_3dof (MatrixNd::Zero (q.size(), q.size())); + + CompositeRigidBodyAlgorithm (model_emulated, q, H_emulated); + CompositeRigidBodyAlgorithm (model_3dof, q, H_3dof); + + VectorNd b (VectorNd::Zero (q.size())); + VectorNd x_emulated (VectorNd::Zero (q.size())); + VectorNd x_3dof (VectorNd::Zero (q.size())); + + for (unsigned int i = 0; i < b.size(); i++) { + b[i] = static_cast (i + 1) * 2.152; + } + b = H_emulated * b; + + SparseFactorizeLTL (model_emulated, H_emulated); + SparseFactorizeLTL (model_3dof, H_3dof); + + CHECK_ARRAY_CLOSE (H_emulated.data(), H_3dof.data(), q.size() * q.size(), TEST_PREC); + + x_emulated = b; + SparseSolveLx (model_emulated, H_emulated, x_emulated); + x_3dof = b; + SparseSolveLx (model_3dof, H_3dof, x_3dof); + + CHECK_ARRAY_CLOSE (x_emulated.data(), x_3dof.data(), x_emulated.size(), 1.0e-9); + + x_emulated = b; + SparseSolveLTx (model_emulated, H_emulated, x_emulated); + x_3dof = b; + SparseSolveLTx (model_3dof, H_3dof, x_3dof); + + CHECK_ARRAY_CLOSE (x_emulated.data(), x_3dof.data(), x_emulated.size(), 1.0e-9); +} + +TEST ( TestSparseFactorizationMultiDofAndFixed) { + Model model_emulated; + Model model_3dof; + + Body body (1., Vector3d (1., 2., 1.), Matrix3d (1., 0., 0, 0., 1., 0., 0., 0., 1.)); + Joint joint_emulated ( + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + Joint joint_3dof (JointTypeEulerYXZ); + + Joint joint_rot_y ( + SpatialVector (0., 1., 0., 0., 0., 0.) + ); + + SpatialTransform translate_x (Matrix3d::Identity(), Vector3d (1., 0., 0.)); + + model_emulated.AppendBody (SpatialTransform(Matrix3d::Identity(), Vector3d::Zero()), joint_rot_y, body); + unsigned int multdof_body_id_emulated = model_emulated.AppendBody (translate_x, joint_emulated, body); + model_emulated.AppendBody (translate_x, joint_emulated, body); + model_emulated.AddBody(multdof_body_id_emulated, translate_x, Joint(JointTypeFixed), body); + model_emulated.AppendBody (translate_x, joint_emulated, body); + + model_3dof.AppendBody (SpatialTransform(Matrix3d::Identity(), Vector3d::Zero()), joint_rot_y, body); + unsigned int multdof_body_id_3dof = model_3dof.AppendBody (translate_x, joint_3dof, body); + model_3dof.AppendBody (translate_x, joint_3dof, body); + model_3dof.AddBody (multdof_body_id_3dof, translate_x, Joint(JointTypeFixed), body); + model_3dof.AppendBody (translate_x, joint_3dof, body); + + VectorNd q (VectorNd::Zero (model_emulated.q_size)); + VectorNd qdot (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_emulated (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd qddot_3dof (VectorNd::Zero (model_emulated.qdot_size)); + VectorNd tau (VectorNd::Zero (model_emulated.qdot_size)); + + for (int i = 0; i < q.size(); i++) { + q[i] = 1.1 * (static_cast(i + 1)); + qdot[i] = 0.55* (static_cast(i + 1)); + qddot_emulated[i] = 0.23 * (static_cast(i + 1)); + qddot_3dof[i] = 0.22 * (static_cast(i + 1)); + tau[i] = 2.1 * (static_cast(i + 1)); + } + + MatrixNd H_emulated (MatrixNd::Zero (q.size(), q.size())); + MatrixNd H_3dof (MatrixNd::Zero (q.size(), q.size())); + + CompositeRigidBodyAlgorithm (model_emulated, q, H_emulated); + CompositeRigidBodyAlgorithm (model_3dof, q, H_3dof); + + VectorNd b (VectorNd::Zero (q.size())); + VectorNd x_emulated (VectorNd::Zero (q.size())); + VectorNd x_3dof (VectorNd::Zero (q.size())); + + for (unsigned int i = 0; i < b.size(); i++) { + b[i] = static_cast (i + 1) * 2.152; + } + b = H_emulated * b; + + SparseFactorizeLTL (model_emulated, H_emulated); + SparseFactorizeLTL (model_3dof, H_3dof); + + CHECK_ARRAY_CLOSE (H_emulated.data(), H_3dof.data(), q.size() * q.size(), TEST_PREC); + + x_emulated = b; + SparseSolveLx (model_emulated, H_emulated, x_emulated); + x_3dof = b; + SparseSolveLx (model_3dof, H_3dof, x_3dof); + + CHECK_ARRAY_CLOSE (x_emulated.data(), x_3dof.data(), x_emulated.size(), 1.0e-9); + + x_emulated = b; + SparseSolveLTx (model_emulated, H_emulated, x_emulated); + x_3dof = b; + SparseSolveLTx (model_3dof, H_3dof, x_3dof); + + CHECK_ARRAY_CLOSE (x_emulated.data(), x_3dof.data(), x_emulated.size(), 1.0e-9); +} diff --git a/3rdparty/rbdl/tests/SpatialAlgebraTests.cc b/3rdparty/rbdl/tests/SpatialAlgebraTests.cc new file mode 100644 index 0000000..6fed310 --- /dev/null +++ b/3rdparty/rbdl/tests/SpatialAlgebraTests.cc @@ -0,0 +1,586 @@ +#include + +#include +#include + +#include "rbdl/Body.h" +#include "rbdl/rbdl_math.h" +#include "rbdl/rbdl_mathutils.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-14; + +SpatialMatrix spatial_adjoint(const SpatialMatrix &m) { + SpatialMatrix res (m); + res.block<3,3>(3,0) = m.block<3,3>(0,3); + res.block<3,3>(0,3) = m.block<3,3>(3,0); + return res; +} + +SpatialMatrix spatial_inverse(const SpatialMatrix &m) { + SpatialMatrix res(m); + res.block<3,3>(0,0) = m.block<3,3>(0,0).transpose(); + res.block<3,3>(3,0) = m.block<3,3>(3,0).transpose(); + res.block<3,3>(0,3) = m.block<3,3>(0,3).transpose(); + res.block<3,3>(3,3) = m.block<3,3>(3,3).transpose(); + return res; +} + +Matrix3d get_rotation (const SpatialMatrix &m) { + return m.block<3,3>(0,0); +} + +Vector3d get_translation (const SpatialMatrix &m) { + return Vector3d (-m(4,2), m(3,2), -m(3,1)); +} + +/// \brief Checks the multiplication of a SpatialMatrix with a SpatialVector +TEST(TestSpatialMatrixTimesSpatialVector) { + SpatialMatrix s_matrix ( + 1., 0., 0., 0., 0., 7., + 0., 2., 0., 0., 8., 0., + 0., 0., 3., 9., 0., 0., + 0., 0., 6., 4., 0., 0., + 0., 5., 0., 0., 5., 0., + 4., 0., 0., 0., 0., 6. + ); + SpatialVector s_vector ( + 1., 2., 3., 4., 5., 6. + ); + + SpatialVector result; + result = s_matrix * s_vector; + + SpatialVector test_result ( + 43., 44., 45., 34., 35., 40. + ); + CHECK_EQUAL (test_result, result); +} + +/// \brief Checks the multiplication of a scalar with a SpatialVector +TEST(TestScalarTimesSpatialVector) { + SpatialVector s_vector ( + 1., 2., 3., 4., 5., 6. + ); + + SpatialVector result; + result = 3. * s_vector; + + SpatialVector test_result(3., 6., 9., 12., 15., 18.); + + CHECK_EQUAL (test_result, result); +} + +/// \brief Checks the multiplication of a scalar with a SpatialMatrix +TEST(TestScalarTimesSpatialMatrix) { + SpatialMatrix s_matrix ( + 1., 0., 0., 0., 0., 7., + 0., 2., 0., 0., 8., 0., + 0., 0., 3., 9., 0., 0., + 0., 0., 6., 4., 0., 0., + 0., 5., 0., 0., 5., 0., + 4., 0., 0., 0., 0., 6. + ); + + SpatialMatrix result; + result = 3. * s_matrix; + + SpatialMatrix test_result( + 3., 0., 0., 0., 0., 21., + 0., 6., 0., 0., 24., 0., + 0., 0., 9., 27., 0., 0., + 0., 0., 18., 12., 0., 0., + 0., 15., 0., 0., 15., 0., + 12., 0., 0., 0., 0., 18. + ); + + CHECK_EQUAL (test_result, result); +} + +/// \brief Checks the multiplication of a scalar with a SpatialMatrix +TEST(TestSpatialMatrixTimesSpatialMatrix) { + SpatialMatrix s_matrix ( + 1., 0., 0., 0., 0., 7., + 0., 2., 0., 0., 8., 0., + 0., 0., 3., 9., 0., 0., + 0., 0., 6., 4., 0., 0., + 0., 5., 0., 0., 5., 0., + 4., 0., 0., 0., 0., 6. + ); + + SpatialMatrix result; + result = s_matrix * s_matrix; + + SpatialMatrix test_result( + 29., 0., 0., 0., 0., 49., + 0., 44., 0., 0., 56., 0., + 0., 0., 63., 63., 0., 0., + 0., 0., 42., 70., 0., 0., + 0., 35., 0., 0., 65., 0., + 28., 0., 0., 0., 0., 64. + ); + + CHECK_EQUAL (test_result, result); +} + +/// \brief Checks the adjoint method +// +// This method computes a spatial force transformation from a spatial +// motion transformation and vice versa +TEST(TestSpatialMatrixTransformAdjoint) { + SpatialMatrix s_matrix ( + 1., 2., 3., 4., 5., 6., + 7., 8., 9., 10., 11., 12., + 13., 14., 15., 16., 17., 18., + 19., 20., 21., 22., 23., 24., + 25., 26., 27., 28., 29., 30., + 31., 32., 33., 34., 35., 36. + ); + + SpatialMatrix result = spatial_adjoint(s_matrix); + + SpatialMatrix test_result_matrix ( + 1., 2., 3., 19., 20., 21., + 7., 8., 9., 25., 26., 27., + 13., 14., 15., 31., 32., 33., + 4., 5., 6., 22., 23., 24., + 10., 11., 12., 28., 29., 30., + 16., 17., 18., 34., 35., 36.); + + CHECK_EQUAL (test_result_matrix, result); +} + +TEST(TestSpatialMatrixInverse) { + SpatialMatrix s_matrix ( + 0, 1, 2, 0, 1, 2, + 3, 4, 5, 3, 4, 5, + 6, 7, 8, 6, 7, 8, + 0, 1, 2, 0, 1, 2, + 3, 4, 5, 3, 4, 5, + 6, 7, 8, 6, 7, 8 + ); + + SpatialMatrix test_inv ( + 0, 3, 6, 0, 3, 6, + 1, 4, 7, 1, 4, 7, + 2, 5, 8, 2, 5, 8, + 0, 3, 6, 0, 3, 6, + 1, 4, 7, 1, 4, 7, + 2, 5, 8, 2, 5, 8 + ); + + CHECK_EQUAL (test_inv, spatial_inverse(s_matrix)); +} + +TEST(TestSpatialMatrixGetRotation) { + SpatialMatrix spatial_transform ( + 1., 2., 3., 0., 0., 0., + 4., 5., 6., 0., 0., 0., + 7., 8., 9., 0., 0., 0., + 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0. + ); + + // Matrix3d rotation = spatial_transform.block<3,3>(0,0); + Matrix3d rotation = get_rotation (spatial_transform); + Matrix3d test_result ( + 1., 2., 3., + 4., 5., 6., + 7., 8., 9. + ); + + CHECK_EQUAL(test_result, rotation); +} + +TEST(TestSpatialMatrixGetTranslation) { + SpatialMatrix spatial_transform ( + 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., + 0., -3., 2., 0., 0., 0., + 0., 0., -1., 0., 0., 0., + 0., 0., 0., 0., 0., 0. + ); + + Vector3d translation = get_translation(spatial_transform); + Vector3d test_result ( + 1., 2., 3. + ); + + CHECK_EQUAL( test_result, translation); +} + +TEST(TestSpatialVectorCross) { + SpatialVector s_vec (1., 2., 3., 4., 5., 6.); + + SpatialMatrix test_cross ( + 0., -3., 2., 0., 0., 0., + 3., 0., -1., 0., 0., 0., + -2., 1., 0., 0., 0., 0., + 0., -6., 5., 0., -3., 2., + 6., 0., -4., 3., 0., -1., + -5., 4., 0., -2., 1., 0. + ); + + SpatialMatrix s_vec_cross (crossm(s_vec)); + CHECK_EQUAL (test_cross, s_vec_cross); + + SpatialMatrix s_vec_crossf (crossf(s_vec)); + SpatialMatrix test_crossf = -1. * crossm(s_vec).transpose(); + + CHECK_EQUAL (test_crossf, s_vec_crossf); +} + +TEST(TestSpatialVectorCrossmCrossf) { + SpatialVector s_vec (1., 2., 3., 4., 5., 6.); + SpatialVector t_vec (9., 8., 7., 6., 5., 4.); + + // by explicitly building the matrices (crossm/f with only one vector) + SpatialVector crossm_s_x_t = crossm(s_vec) * t_vec; + SpatialVector crossf_s_x_t = crossf(s_vec) * t_vec; + + // by using direct computation that avoids building of the matrix + SpatialVector crossm_s_t = crossm(s_vec, t_vec); + SpatialVector crossf_s_t = crossf(s_vec, t_vec); + + /* + cout << crossm_s_x_t << endl; + cout << "---" << endl; + cout << crossf_s_x_t << endl; + cout << "---" << endl; + cout << crossf_s_t << endl; + */ + + CHECK_EQUAL (crossm_s_x_t, crossm_s_t); + CHECK_EQUAL (crossf_s_x_t, crossf_s_t); +} + +TEST(TestSpatialTransformApply) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialTransform X_st; + X_st.r = trans; + + SpatialMatrix X_66_matrix (SpatialMatrix::Zero(6,6)); + X_66_matrix = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + X_st.E = X_66_matrix.block<3,3>(0,0); + + // cout << X_66_matrix << endl; + // cout << X_st.E << endl; + // cout << X_st.r.transpose() << endl; + + SpatialVector v (1.1, 2.1, 3.1, 4.1, 5.1, 6.1); + SpatialVector v_66_res = X_66_matrix * v; + SpatialVector v_st_res = X_st.apply(v); + + // cout << (v_66_res - v_st_res).transpose() << endl; + + CHECK_ARRAY_CLOSE (v_66_res.data(), v_st_res.data(), 6, TEST_PREC); +} + +TEST(TestSpatialTransformApplyTranspose) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialTransform X_st; + X_st.r = trans; + + SpatialMatrix X_66_matrix (SpatialMatrix::Zero(6,6)); + X_66_matrix = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + X_st.E = X_66_matrix.block<3,3>(0,0); + + // cout << X_66_matrix << endl; + // cout << X_st.E << endl; + // cout << X_st.r.transpose() << endl; + + SpatialVector v (1.1, 2.1, 3.1, 4.1, 5.1, 6.1); + SpatialVector v_66_res = X_66_matrix.transpose() * v; + SpatialVector v_st_res = X_st.applyTranspose(v); + + // cout << (v_66_res - v_st_res).transpose() << endl; + + CHECK_ARRAY_CLOSE (v_66_res.data(), v_st_res.data(), 6, TEST_PREC); +} + +TEST(TestSpatialTransformApplyAdjoint) { + SpatialTransform X ( + Xrotz (0.5) * + Xroty (0.9) * + Xrotx (0.2) * + Xtrans (Vector3d (1.1, 1.2, 1.3)) + ); + + SpatialMatrix X_adjoint = X.toMatrixAdjoint(); + + SpatialVector f (1.1, 2.1, 4.1, 9.2, 3.3, 0.8); + SpatialVector f_apply = X.applyAdjoint(f); + SpatialVector f_matrix = X_adjoint * f; + + CHECK_ARRAY_CLOSE (f_matrix.data(), f_apply.data(), 6, TEST_PREC); +} + +TEST(TestSpatialTransformToMatrix) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialMatrix X_matrix (SpatialMatrix::Zero(6,6)); + X_matrix = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + + SpatialTransform X_st; + X_st.E = X_matrix.block<3,3>(0,0); + X_st.r = trans; + + // SpatialMatrix X_diff = X_st.toMatrix() - X_matrix; + // cout << "Error: " << endl << X_diff << endl; + + CHECK_ARRAY_CLOSE (X_matrix.data(), X_st.toMatrix().data(), 36, TEST_PREC); +} + +TEST(TestSpatialTransformToMatrixAdjoint) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialMatrix X_matrix (SpatialMatrix::Zero(6,6)); + X_matrix = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + + SpatialTransform X_st; + X_st.E = X_matrix.block<3,3>(0,0); + X_st.r = trans; + + // SpatialMatrix X_diff = X_st.toMatrixAdjoint() - spatial_adjoint(X_matrix); + // cout << "Error: " << endl << X_diff << endl; + + CHECK_ARRAY_CLOSE (spatial_adjoint(X_matrix).data(), X_st.toMatrixAdjoint().data(), 36, TEST_PREC); +} + +TEST(TestSpatialTransformToMatrixTranspose) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialMatrix X_matrix (SpatialMatrix::Zero(6,6)); + X_matrix = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + + SpatialTransform X_st; + X_st.E = X_matrix.block<3,3>(0,0); + X_st.r = trans; + + // we have to copy the matrix as it is only transposed via a flag and + // thus data() does not return the proper data. + SpatialMatrix X_matrix_transposed = X_matrix.transpose(); + // SpatialMatrix X_diff = X_st.toMatrixTranspose() - X_matrix_transposed; + // cout << "Error: " << endl << X_diff << endl; + // cout << "X_st: " << endl << X_st.toMatrixTranspose() << endl; + // cout << "X: " << endl << X_matrix_transposed() << endl; + + CHECK_ARRAY_CLOSE (X_matrix_transposed.data(), X_st.toMatrixTranspose().data(), 36, TEST_PREC); +} + +TEST(TestSpatialTransformMultiply) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialMatrix X_matrix_1 (SpatialMatrix::Zero(6,6)); + SpatialMatrix X_matrix_2 (SpatialMatrix::Zero(6,6)); + + X_matrix_1 = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + X_matrix_2 = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + + SpatialTransform X_st_1; + SpatialTransform X_st_2; + + X_st_1.E = X_matrix_1.block<3,3>(0,0); + X_st_1.r = trans; + X_st_2.E = X_matrix_2.block<3,3>(0,0); + X_st_2.r = trans; + + SpatialTransform X_st_res = X_st_1 * X_st_2; + SpatialMatrix X_matrix_res = X_matrix_1 * X_matrix_2; + + // SpatialMatrix X_diff = X_st_res.toMatrix() - X_matrix_res; + // cout << "Error: " << endl << X_diff << endl; + + CHECK_ARRAY_CLOSE (X_matrix_res.data(), X_st_res.toMatrix().data(), 36, TEST_PREC); +} + +TEST(TestSpatialTransformMultiplyEqual) { + Vector3d rot (1.1, 1.2, 1.3); + Vector3d trans (1.1, 1.2, 1.3); + + SpatialMatrix X_matrix_1 (SpatialMatrix::Zero(6,6)); + SpatialMatrix X_matrix_2 (SpatialMatrix::Zero(6,6)); + + X_matrix_1 = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + X_matrix_2 = Xrotz_mat (rot[2]) * Xroty_mat (rot[1]) * Xrotx_mat (rot[0]) * Xtrans_mat(trans); + + SpatialTransform X_st_1; + SpatialTransform X_st_2; + + X_st_1.E = X_matrix_1.block<3,3>(0,0); + X_st_1.r = trans; + X_st_2.E = X_matrix_2.block<3,3>(0,0); + X_st_2.r = trans; + + SpatialTransform X_st_res = X_st_1; + X_st_res *= X_st_2; + SpatialMatrix X_matrix_res = X_matrix_1 * X_matrix_2; + + // SpatialMatrix X_diff = X_st_res.toMatrix() - X_matrix_res; + // cout << "Error: " << endl << X_diff << endl; + + CHECK_ARRAY_CLOSE (X_matrix_res.data(), X_st_res.toMatrix().data(), 36, TEST_PREC); +} + +TEST(TestXrotAxis) { + SpatialTransform X_rotX = Xrotx (M_PI * 0.15); + SpatialTransform X_rotX_axis = Xrot (M_PI * 0.15, Vector3d (1., 0., 0.)); + + CHECK_ARRAY_CLOSE (X_rotX.toMatrix().data(), X_rotX_axis.toMatrix().data(), 36, TEST_PREC); + + // all the other axes + SpatialTransform X_rotX_90 = Xrotx (M_PI * 0.5); + SpatialTransform X_rotX_90_axis = Xrot (M_PI * 0.5, Vector3d (1., 0., 0.)); + + CHECK_ARRAY_CLOSE (X_rotX_90.toMatrix().data(), X_rotX_90_axis.toMatrix().data(), 36, TEST_PREC); + + SpatialTransform X_rotY_90 = Xroty (M_PI * 0.5); + SpatialTransform X_rotY_90_axis = Xrot (M_PI * 0.5, Vector3d (0., 1., 0.)); + + CHECK_ARRAY_CLOSE (X_rotY_90.toMatrix().data(), X_rotY_90_axis.toMatrix().data(), 36, TEST_PREC); + + SpatialTransform X_rotZ_90 = Xrotz (M_PI * 0.5); + SpatialTransform X_rotZ_90_axis = Xrot (M_PI * 0.5, Vector3d (0., 0., 1.)); + + CHECK_ARRAY_CLOSE (X_rotZ_90.toMatrix().data(), X_rotZ_90_axis.toMatrix().data(), 36, TEST_PREC); +} + +TEST(TestSpatialTransformApplySpatialRigidBodyInertiaAdd) { + SpatialRigidBodyInertia rbi ( + 1.1, + Vector3d (1.2, 1.3, 1.4), + Matrix3d ( + 1.1, 0.5, 0.3, + 0.5, 1.2, 0.4, + 0.3, 0.4, 1.3 + )); + + SpatialMatrix rbi_matrix_added = rbi.toMatrix() + rbi.toMatrix(); + SpatialRigidBodyInertia rbi_added = rbi + rbi; + + // cout << "rbi = " << endl << rbi.toMatrix() << endl; + // cout << "rbi_added = " << endl << rbi_added.toMatrix() << endl; + // cout << "rbi_matrix_added = " << endl << rbi_matrix_added << endl; + // cout << "diff = " << endl << + // rbi_added.toMatrix() - rbi_matrix_added << endl; + + CHECK_ARRAY_CLOSE ( + rbi_matrix_added.data(), + rbi_added.toMatrix().data(), + 36, + TEST_PREC + ); +} + +TEST(TestSpatialTransformApplySpatialRigidBodyInertiaFull) { + SpatialRigidBodyInertia rbi ( + 1.1, + Vector3d (1.2, 1.3, 1.4), + Matrix3d ( + 1.1, 0.5, 0.3, + 0.5, 1.2, 0.4, + 0.3, 0.4, 1.3 + )); + + SpatialTransform X ( + Xrotz (0.5) * + Xroty (0.9) * + Xrotx (0.2) * + Xtrans (Vector3d (1.1, 1.2, 1.3)) + ); + + SpatialRigidBodyInertia rbi_transformed = X.apply (rbi); + SpatialMatrix rbi_matrix_transformed = X.toMatrixAdjoint () * rbi.toMatrix() * X.inverse().toMatrix(); + + CHECK_ARRAY_CLOSE ( + rbi_matrix_transformed.data(), + rbi_transformed.toMatrix().data(), + 36, + TEST_PREC + ); +} + +TEST(TestSpatialTransformApplyTransposeSpatialRigidBodyInertiaFull) { + SpatialRigidBodyInertia rbi ( + 1.1, + Vector3d (1.2, 1.3, 1.4), + Matrix3d ( + 1.1, 0.5, 0.3, + 0.5, 1.2, 0.4, + 0.3, 0.4, 1.3 + )); + + SpatialTransform X ( + Xrotz (0.5) * + Xroty (0.9) * + Xrotx (0.2) * + Xtrans (Vector3d (1.1, 1.2, 1.3)) + ); + + SpatialRigidBodyInertia rbi_transformed = X.applyTranspose (rbi); + SpatialMatrix rbi_matrix_transformed = X.toMatrixTranspose() * rbi.toMatrix() * X.toMatrix(); + + CHECK_ARRAY_CLOSE ( + rbi_matrix_transformed.data(), + rbi_transformed.toMatrix().data(), + 36, + TEST_PREC + ); +} + +TEST(TestSpatialRigidBodyInertiaCreateFromMatrix) { + double mass = 1.1; + Vector3d com (0., 0., 0.); + Matrix3d inertia ( + 1.1, 0.5, 0.3, + 0.5, 1.2, 0.4, + 0.3, 0.4, 1.3 + ); + SpatialRigidBodyInertia body_rbi(mass, com , inertia); + + SpatialMatrix spatial_inertia = body_rbi.toMatrix(); + + SpatialRigidBodyInertia rbi; + rbi.createFromMatrix (spatial_inertia); + + CHECK_EQUAL (mass, rbi.m); + CHECK_ARRAY_EQUAL (Vector3d(mass * com).data(), rbi.h.data(), 3); + Matrix3d rbi_I_matrix ( + rbi.Ixx, rbi.Iyx, rbi.Izx, + rbi.Iyx, rbi.Iyy, rbi.Izy, + rbi.Izx, rbi.Izy, rbi.Izz + ); + CHECK_ARRAY_EQUAL (inertia.data(), rbi_I_matrix.data(), 9); +} + +#ifdef USE_SLOW_SPATIAL_ALGEBRA +TEST(TestSpatialLinSolve) { + SpatialVector b (1, 2, 0, 1, 1, 1); + SpatialMatrix A ( + 1., 2., 3., 0., 0., 0., + 3., 4., 5., 0., 0., 0., + 6., 7., 7., 0., 0., 0., + 0., 0., 0., 1., 0., 0., + 0., 0., 0., 0., 1., 0., + 0., 0., 0., 0., 0., 1. + ); + + SpatialVector x = SpatialLinSolve (A, b); + SpatialVector x_test (3.5, -6.5, 3.5, 1, 1, 1); + + CHECK_ARRAY_CLOSE (x_test.data(), x.data(), 6, TEST_PREC); +} +#endif diff --git a/3rdparty/rbdl/tests/TwolegModelTests.cc b/3rdparty/rbdl/tests/TwolegModelTests.cc new file mode 100644 index 0000000..ca5c379 --- /dev/null +++ b/3rdparty/rbdl/tests/TwolegModelTests.cc @@ -0,0 +1,404 @@ +#include + +#include + +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Constraints.h" +#include "rbdl/Dynamics.h" +#include "rbdl/Kinematics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +const double TEST_PREC = 1.0e-13; + +unsigned int hip_id, + upper_leg_right_id, + lower_leg_right_id, + foot_right_id, + upper_leg_left_id, + lower_leg_left_id, + foot_left_id; + +Body hip_body, + upper_leg_right_body, + lower_leg_right_body, + foot_right_body, + upper_leg_left_body, + lower_leg_left_body, + foot_left_body; + +Joint joint_txtyrz ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); +Joint joint_rot_z (SpatialVector (0., 0., 1., 0., 0., 0.)); + +VectorNd Q; +VectorNd QDot; +VectorNd QDDot; +VectorNd Tau; + +ConstraintSet constraint_set_right; +ConstraintSet constraint_set_left; +ConstraintSet constraint_set_left_flat; +ConstraintSet constraint_set_both; + +enum ParamNames { + ParamSteplength = 0, + ParamNameLast +}; + +enum PosNames { + PosHipPosX, + PosHipPosY, + PosHipRotZ, + PosRightThighRotZ, + PosRightShankRotZ, + PosRightAnkleRotZ, + PosLeftThighRotZ, + PosLeftShankRotZ, + PosLeftAnkleRotZ, + PosNameLast +}; + +enum StateNames { + StateHipPosX, + StateHipPosY, + StateHipRotZ, + StateRightThighRotZ, + StateRightShankRotZ, + StateRightAnkleRotZ, + StateLeftThighRotZ, + StateLeftShankRotZ, + StateLeftAnkleRotZ, + StateHipVelX, + StateHipVelY, + StateHipRotVelZ, + StateRightThighRotVelZ, + StateRightShankRotVelZ, + StateRightAnkleRotVelZ, + StateLeftThighRotVelZ, + StateLeftShankRotVelZ, + StateLeftAnkleRotVelZ, + StateNameLast +}; + +enum ControlNames { + ControlRightThighRotZ, + ControlRightKneeRotZ, + ControlRightAnkleRotZ, + ControlLeftThighRotZ, + ControlLeftKneeRotZ, + ControlLeftAnkleRotZ, + ControlNameLast +}; + +enum SegmentLengthsNames { + SegmentLengthsHip = 0, + SegmentLengthsThigh, + SegmentLengthsShank, + SegmentLengthsFootHeight, + SegmentLengthsFoot, + SegmentLengthsNameLast +}; + +const double ModelMass = 73.; +const double ModelHeight = 1.741; + +// absolute lengths! +double segment_lengths[SegmentLengthsNameLast] = { + 0.4346, + 0.4222, + 0.4340, + 0.0317, + 0.2581 +}; + +enum JointLocations { + JointLocationHip = 0, + JointLocationKnee, + JointLocationAnkle, + JointLocationLast +}; + +Vector3d joint_location[JointLocationLast] = { + Vector3d (0., 0., 0.), + Vector3d (0., - 0.2425 * ModelHeight, 0.), + Vector3d (0., - 0.2529 * ModelHeight, 0.) +}; + +enum SegmentMassNames { + SegmentMassHip, + SegmentMassThigh, + SegmentMassShank, + SegmentMassFoot, + SegmentMassLast +}; + +double segment_mass[SegmentMassLast] = { + 0.4346 * ModelMass, + 0.1416 * ModelMass, + 0.0433 * ModelMass, + 0.0137 * ModelMass +}; + +enum COMNames { + COMHip, + COMThigh, + COMShank, + COMFoot, + COMNameLast +}; + +Vector3d com_position[COMNameLast] = { + Vector3d (0., 0.3469 * ModelHeight, 0.), + Vector3d (0., 0.2425 * ModelHeight, 0.), + Vector3d (0., 0.2529 * ModelHeight, 0.), + Vector3d (0.0182 * ModelHeight, 0., 0.) +}; + +enum RGyrationNames { + RGyrationHip, + RGyrationThigh, + RGyrationShank, + RGyrationFoot, + RGyrationLast +}; + +Vector3d rgyration[RGyrationLast] = { + Vector3d (0.1981, 0.1021, 0.1848), + Vector3d (0.1389, 0.0629, 0.1389), + Vector3d (0.1123, 0.0454, 0.1096), + Vector3d (0.0081, 0.0039, 0.0078) +}; + +Vector3d heel_point (0., 0., 0.); +Vector3d medial_point (0., 0., 0.); + +void init_model (Model* model) { + assert (model); + + constraint_set_right = ConstraintSet(); + constraint_set_left = ConstraintSet(); + constraint_set_left_flat = ConstraintSet(); + constraint_set_both = ConstraintSet(); + + model->gravity = Vector3d (0., -9.81, 0.); + + // hip + hip_body = Body (segment_mass[SegmentMassHip], com_position[COMHip], rgyration[RGyrationHip]); + + // lateral right + upper_leg_right_body = Body (segment_mass[SegmentMassThigh], com_position[COMThigh], rgyration[RGyrationThigh]); + lower_leg_right_body = Body (segment_mass[SegmentMassShank], com_position[COMShank], rgyration[RGyrationShank]); + foot_right_body = Body (segment_mass[SegmentMassFoot], com_position[COMFoot], rgyration[RGyrationFoot]); + + // lateral left + upper_leg_left_body = Body (segment_mass[SegmentMassThigh], com_position[COMThigh], rgyration[RGyrationThigh]); + lower_leg_left_body = Body (segment_mass[SegmentMassShank], com_position[COMShank], rgyration[RGyrationShank]); + foot_left_body = Body (segment_mass[SegmentMassFoot], com_position[COMFoot], rgyration[RGyrationFoot]); + + // add hip to the model (planar, 3 DOF) + hip_id = model->AddBody (0, Xtrans (Vector3d (0., 0., 0.)), joint_txtyrz, hip_body); + + // + // right leg + // + + unsigned int temp_id = 0; + + // add right upper leg + temp_id = model->AddBody (hip_id, Xtrans (Vector3d(0., 0., 0.)), joint_rot_z, upper_leg_right_body); + upper_leg_right_id = temp_id; + + // add the right lower leg (only one DOF) + temp_id = model->AddBody (temp_id, Xtrans (joint_location[JointLocationKnee]), joint_rot_z, lower_leg_right_body); + lower_leg_right_id = temp_id; + + // add the right foot (1 DOF) + temp_id = model->AddBody (temp_id, Xtrans (joint_location[JointLocationAnkle]), joint_rot_z, foot_right_body); + foot_right_id = temp_id; + + // + // left leg + // + + // add left upper leg + temp_id = model->AddBody (hip_id, Xtrans (Vector3d(0., 0., 0.)), joint_rot_z, upper_leg_left_body); + upper_leg_left_id = temp_id; + + // add the left lower leg (only one DOF) + temp_id = model->AddBody (temp_id, Xtrans (joint_location[JointLocationKnee]), joint_rot_z, lower_leg_left_body); + lower_leg_left_id = temp_id; + + // add the left foot (1 DOF) + temp_id = model->AddBody (temp_id, Xtrans (joint_location[JointLocationAnkle]), joint_rot_z, foot_left_body); + foot_left_id = temp_id; + + // cerr << "--- model created (" << model->dof_count << " DOF) ---" << endl; + + // contact data + + // the contact points for heel and toe + heel_point.set (-0.05, -0.0317, 0.); + medial_point.set (-0.05, -0.0317 + segment_lengths[SegmentLengthsFoot], 0.); + + constraint_set_right.AddContactConstraint(foot_right_id, heel_point, Vector3d (1., 0., 0.), "right_heel_x"); + constraint_set_right.AddContactConstraint(foot_right_id, heel_point, Vector3d (0., 1., 0.), "right_heel_y"); + + constraint_set_left.AddContactConstraint(foot_left_id, heel_point, Vector3d (1., 0., 0.), "left_heel_x"); + constraint_set_left.AddContactConstraint(foot_left_id, heel_point, Vector3d (0., 1., 0.), "left_heel_y"); + + constraint_set_both.AddContactConstraint(foot_right_id, heel_point, Vector3d (1., 0., 0.), "right_heel_x"); + constraint_set_both.AddContactConstraint(foot_right_id, heel_point, Vector3d (0., 1., 0.), "right_heel_y"); + constraint_set_both.AddContactConstraint(foot_right_id, heel_point, Vector3d (0., 0., 1.), "right_heel_z"); + + constraint_set_both.AddContactConstraint(foot_left_id, heel_point, Vector3d (1., 0., 0.), "left_heel_x"); + constraint_set_both.AddContactConstraint(foot_left_id, heel_point, Vector3d (0., 1., 0.), "left_heel_y"); + constraint_set_both.AddContactConstraint(foot_left_id, heel_point, Vector3d (0., 0., 1.), "left_heel_z"); + + constraint_set_right.Bind (*model); + constraint_set_left.Bind (*model); + constraint_set_both.Bind (*model); +} + +template +void copy_values (T *dest, const T *src, size_t count) { + memcpy (dest, src, count * sizeof (T)); +} + +TEST ( TestForwardDynamicsConstraintsDirectFootmodel ) { + Model* model = new Model; + + init_model(model); + + Q.resize(model->dof_count); + QDot.resize(model->dof_count); + QDDot.resize(model->dof_count); + Tau.resize(model->dof_count); + + Q[0] = -0.2; + Q[1] = 0.9; + Q[2] = 0; + Q[3] = -0.15; + Q[4] = -0.15; + Q[5] = 0.1; + Q[6] = 0.15; + Q[7] = -0.15; + Q[8] = 0; + + QDot.setZero(); + + Tau[0] = 0; + Tau[1] = 0; + Tau[2] = 0; + Tau[3] = 1; + Tau[4] = 1; + Tau[5] = 1; + Tau[6] = 1; + Tau[7] = 1; + Tau[8] = 1; + + Vector3d contact_accel_left; + Vector3d contact_vel_left; + Vector3d contact_force = Vector3d::Zero(); + + VectorNd QDDot_aba (QDDot); + VectorNd QDDot_lag (QDDot); + ForwardDynamics (*model, Q, QDot, Tau, QDDot_aba); + ForwardDynamicsLagrangian (*model, Q, QDot, Tau, QDDot_lag); + + // cout << "QDDot_aba = " << QDDot_aba.transpose() << endl; + // cout << "QDDot_lag = " << QDDot_lag.transpose() << endl; + + unsigned int body_id = constraint_set_left.body[0]; + Vector3d contact_point = constraint_set_left.point[0]; + + MatrixNd G (3, Q.size()); + CalcPointJacobian (*model, Q, body_id, contact_point, G, true); + + // cout << G << endl; + + ClearLogOutput(); + + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_left, QDDot); + + // cout << "C0: " << contact_data_left[0].body_id << ", " << contact_data_left[0].point.transpose() << endl; + // cout << "C1: " << contact_data_left[1].body_id << ", " << contact_data_left[1].point.transpose() << endl; + // cout << "td: " << foot_left_id << ", " << heel_point.transpose() << endl; + + contact_force[0] = constraint_set_left.force[0]; + contact_force[1] = constraint_set_left.force[1]; + + CHECK_EQUAL (body_id, foot_left_id); + CHECK_EQUAL (contact_point, heel_point); + + // cout << LogOutput.str() << endl; + contact_accel_left = CalcPointAcceleration (*model, Q, QDot, QDDot, foot_left_id, heel_point); + contact_vel_left = CalcPointVelocity (*model, Q, QDot, foot_left_id, heel_point); + // cout << contact_force << endl; + // cout << contact_accel_left << endl; + + CHECK_ARRAY_CLOSE (Vector3d (0., 0., 0.).data(), contact_accel_left.data(), 3, TEST_PREC); + + delete model; +} + +TEST ( TestClearContactsInertiaMatrix ) { + Model* model = new Model; + + init_model(model); + + Q.resize(model->dof_count); + QDot.resize(model->dof_count); + QDDot.resize(model->dof_count); + Tau.resize(model->dof_count); + + Q[0] = -0.2; + Q[1] = 0.9; + Q[2] = 0; + Q[3] = -0.15; + Q[4] = -0.15; + Q[5] = 0.1; + Q[6] = 0.15; + Q[7] = -0.15; + Q[8] = 0; + + QDot.setZero(); + + Tau[0] = 0; + Tau[1] = 0; + Tau[2] = 0; + Tau[3] = 1; + Tau[4] = 1; + Tau[5] = 1; + Tau[6] = 1; + Tau[7] = 1; + Tau[8] = 1; + + VectorNd QDDot_aba (QDDot); + VectorNd QDDot_lag (QDDot); + + // initialize matrix with erroneous values + constraint_set_right.bound = false; + constraint_set_right.H = MatrixNd::Zero (model->dof_count, model->dof_count); + for (unsigned int i = 0; i < model->dof_count; i++) { + for (unsigned int j = 0; j < model->dof_count; j++) { + constraint_set_right.H(i,j) = 1.234; + } + } + + constraint_set_right.Bind (*model); + + ForwardDynamicsConstraintsDirect (*model, Q, QDot, Tau, constraint_set_right, QDDot_lag); + ForwardDynamicsContactsKokkevis (*model, Q, QDot, Tau, constraint_set_right, QDDot_aba); + + CHECK_ARRAY_CLOSE (QDDot_lag.data(), QDDot_aba.data(), QDDot.size(), TEST_PREC * QDDot_lag.norm()); + + delete model; +} diff --git a/3rdparty/rbdl/tests/UtilsTests.cc b/3rdparty/rbdl/tests/UtilsTests.cc new file mode 100644 index 0000000..4009d8c --- /dev/null +++ b/3rdparty/rbdl/tests/UtilsTests.cc @@ -0,0 +1,156 @@ +#include + +#include + +#include "Fixtures.h" +#include "rbdl/rbdl_mathutils.h" +#include "rbdl/rbdl_utils.h" +#include "rbdl/Logging.h" + +#include "rbdl/Model.h" +#include "rbdl/Kinematics.h" +#include "rbdl/Dynamics.h" + +using namespace std; +using namespace RigidBodyDynamics; +using namespace RigidBodyDynamics::Math; + +TEST_FIXTURE(FloatingBase12DoF, TestKineticEnergy) { + VectorNd q = VectorNd::Zero(model->q_size); + VectorNd qdot = VectorNd::Zero(model->q_size); + + for (unsigned int i = 0; i < q.size(); i++) { + q[i] = 0.1 * i; + qdot[i] = 0.3 * i; + } + + MatrixNd H = MatrixNd::Zero (model->q_size, model->q_size); + CompositeRigidBodyAlgorithm (*model, q, H, true); + + double kinetic_energy_ref = 0.5 * qdot.transpose() * H * qdot; + double kinetic_energy = Utils::CalcKineticEnergy (*model, q, qdot); + + CHECK_EQUAL (kinetic_energy_ref, kinetic_energy); +} + +TEST(TestPotentialEnergy) { + Model model; + Matrix3d inertia = Matrix3d::Zero(3,3); + Body body (0.5, Vector3d (0., 0., 0.), inertia); + Joint joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.) + ); + + model.AppendBody (Xtrans (Vector3d::Zero()), joint, body); + + VectorNd q = VectorNd::Zero(model.q_size); + double potential_energy_zero = Utils::CalcPotentialEnergy (model, q); + CHECK_EQUAL (0., potential_energy_zero); + + q[1] = 1.; + double potential_energy_lifted = Utils::CalcPotentialEnergy (model, q); + CHECK_EQUAL (4.905, potential_energy_lifted); +} + +TEST(TestCOMSimple) { + Model model; + Matrix3d inertia = Matrix3d::Zero(3,3); + Body body (123., Vector3d (0., 0., 0.), inertia); + Joint joint ( + SpatialVector (0., 0., 0., 1., 0., 0.), + SpatialVector (0., 0., 0., 0., 1., 0.), + SpatialVector (0., 0., 0., 0., 0., 1.) + ); + + model.AppendBody (Xtrans (Vector3d::Zero()), joint, body); + + VectorNd q = VectorNd::Zero(model.q_size); + VectorNd qdot = VectorNd::Zero(model.qdot_size); + + double mass; + Vector3d com; + Vector3d com_velocity; + Utils::CalcCenterOfMass (model, q, qdot, mass, com, &com_velocity); + + CHECK_EQUAL (123., mass); + CHECK_EQUAL (Vector3d (0., 0., 0.), com); + CHECK_EQUAL (Vector3d (0., 0., 0.), com_velocity); + + q[1] = 1.; + Utils::CalcCenterOfMass (model, q, qdot, mass, com, &com_velocity); + CHECK_EQUAL (Vector3d (0., 1., 0.), com); + CHECK_EQUAL (Vector3d (0., 0., 0.), com_velocity); + + qdot[1] = 1.; + Utils::CalcCenterOfMass (model, q, qdot, mass, com, &com_velocity); + CHECK_EQUAL (Vector3d (0., 1., 0.), com); + CHECK_EQUAL (Vector3d (0., 1., 0.), com_velocity); +} + +TEST(TestAngularMomentumSimple) { + Model model; + Matrix3d inertia = Matrix3d::Zero(3,3); + inertia(0,0) = 1.1; + inertia(1,1) = 2.2; + inertia(2,2) = 3.3; + + Body body (0.5, Vector3d (1., 0., 0.), inertia); + Joint joint ( + SpatialVector (1., 0., 0., 0., 0., 0.), + SpatialVector (0., 1., 0., 0., 0., 0.), + SpatialVector (0., 0., 1., 0., 0., 0.) + ); + + model.AppendBody (Xtrans (Vector3d(0., 0., 0.)), joint, body); + + VectorNd q = VectorNd::Zero(model.q_size); + VectorNd qdot = VectorNd::Zero(model.qdot_size); + + double mass; + Vector3d com; + Vector3d angular_momentum; + + qdot << 1., 0., 0.; + Utils::CalcCenterOfMass (model, q, qdot, mass, com, NULL, &angular_momentum); + CHECK_EQUAL (Vector3d (1.1, 0., 0.), angular_momentum); + + qdot << 0., 1., 0.; + Utils::CalcCenterOfMass (model, q, qdot, mass, com, NULL, &angular_momentum); + CHECK_EQUAL (Vector3d (0., 2.2, 0.), angular_momentum); + + qdot << 0., 0., 1.; + Utils::CalcCenterOfMass (model, q, qdot, mass, com, NULL, &angular_momentum); + CHECK_EQUAL (Vector3d (0., 0., 3.3), angular_momentum); +} + +TEST_FIXTURE (TwoArms12DoF, TestAngularMomentumSimple) { + double mass; + Vector3d com; + Vector3d angular_momentum; + + Utils::CalcCenterOfMass (*model, q, qdot, mass, com, NULL, &angular_momentum); + + CHECK_EQUAL (Vector3d (0., 0., 0.), angular_momentum); + + qdot[0] = 1.; + qdot[1] = 2.; + qdot[2] = 3.; + + Utils::CalcCenterOfMass (*model, q, qdot, mass, com, NULL, &angular_momentum); + + // only a rough guess from test calculation + CHECK_ARRAY_CLOSE (Vector3d (3.3, 2.54, 1.5).data(), angular_momentum.data(), 3, 1.0e-1); + + qdot[3] = -qdot[0]; + qdot[4] = -qdot[1]; + qdot[5] = -qdot[2]; + + ClearLogOutput(); + Utils::CalcCenterOfMass (*model, q, qdot, mass, com, NULL, &angular_momentum); + + CHECK (angular_momentum[0] == 0); + CHECK (angular_momentum[1] < 0); + CHECK (angular_momentum[2] == 0.); +} diff --git a/3rdparty/rbdl/tests/main.cc b/3rdparty/rbdl/tests/main.cc new file mode 100644 index 0000000..4608c22 --- /dev/null +++ b/3rdparty/rbdl/tests/main.cc @@ -0,0 +1,19 @@ +#include +#include +#include + +#include + +int main (int argc, char *argv[]) +{ + rbdl_check_api_version (RBDL_API_VERSION); + + if (argc > 1) { + std::string arg (argv[1]); + + if (arg == "-v" || arg == "--version") + rbdl_print_version(); + } + + return UnitTest::RunAllTests (); +} diff --git a/3rdparty/rbdl/utils/matlab/FrameTranslation.m b/3rdparty/rbdl/utils/matlab/FrameTranslation.m new file mode 100755 index 0000000..c95b7cf --- /dev/null +++ b/3rdparty/rbdl/utils/matlab/FrameTranslation.m @@ -0,0 +1,30 @@ +function [F] = FrameTranslation (translation, euler_angles) + +% Here we store the result +Result = zeros (4,4); +Result(4,4) = 1; + +% Set the translation part: +Result (1:3, 4) = -translation; + +% Calculate the rotations +Rotation = eye (3,3); + +% Z Rotation +RotZ = eye (3,3); + +if (euler_angles(1) != 0.) + s = sin (euler_angles(1)); + c = cos (euler_angles(1)); + RotZ = [c, s, 0; + -s, c, 0; + 0, 0, 1]; +end + +Rotation = RotZ; + +Result (1:3, 1:3) = Rotation; + +F = Result; + +end; diff --git a/3rdparty/rbdl/utils/matlab/VectorCrossMatrix.m b/3rdparty/rbdl/utils/matlab/VectorCrossMatrix.m new file mode 100644 index 0000000..399c8c6 --- /dev/null +++ b/3rdparty/rbdl/utils/matlab/VectorCrossMatrix.m @@ -0,0 +1,5 @@ +function R = VectorCrossMatrix (r) + +R = [0, -r(3), r(2); + r(3), 0, -r(1); + -r(2), r(1), 0]; diff --git a/3rdparty/rbdl/utils/matlab/ZYXEulerToMatrix.m b/3rdparty/rbdl/utils/matlab/ZYXEulerToMatrix.m new file mode 100644 index 0000000..b7870fd --- /dev/null +++ b/3rdparty/rbdl/utils/matlab/ZYXEulerToMatrix.m @@ -0,0 +1,56 @@ +function [Rx, Ry, Rz, M] = ZYXEulerToMatrix (e) +% +% Calculates the rotation matrix from ZYX Euler Angles +% + +z = e(1); +y = e(2); +x = e(3); + +sz = sin (z); +cz = cos (z); +sy = sin (y); +cy = cos (y); +sx = sin (x); +cx = cos (x); + +Rx = [ +1, 0, 0; +0, cx, -sx; +0, sx, cx; +]; + +Ry = [ + cy, 0, sy; + 0, 1, 0; +-sy, 0, cy +]; + +Rz = [ +cz, -sz, 0; +sz, cz, 0; + 0, 0, 1; +]; + +% Rx * Ry * Rz +M = [ + cy * cz, - cy * sz, +sy; +sz*cx + sx*sy*cz, cz*cx - sx*sy*sz, -sx*cy; +sz*sx - cx*sy*cz, cz*sx + cx*sy*sz, cx*cy; +]; + +% Rz' * Ry' * Rx' +M = [ + cz * cy, -cz*sy*sx + sz* cx, cz*sy*cx + sz*sx; +-sz*cy, - sz*sy*sx + cz*cx, sz*sy*cx + cz*sx; +sy, -cy*sx, cy*cx; +]; + +% Rx' * Ry' * Rz' +M = [ +cy*cz, cy*sz, -sy; +-cx*sz+sx*sy*cz, cx*cz+sx*sy*sz, sx*cy; +sx*sz+cx*sy*cz, -sx*cz+cx*sy*sz, cx*cy; +]; + +M = Rz * Ry * Rx; diff --git a/CMakeLists.txt b/CMakeLists.txt index 2179626..bf8ce26 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,9 @@ SET (CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) SET (CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") SET (CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) +# For YouCompleteMe +SET (CMAKE_EXPORT_COMPILE_COMMANDS ON) + # Enable proper C++11 flags include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) @@ -26,6 +29,10 @@ endif() # enable strings in GDB set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_DEBUG") +# enable LuaModel addon for RBDL +SET (RBDL_BUILD_ADDON_LUAMODEL ON) +SET (RBDL_USE_SIMPLE_MATH ON) + INCLUDE_DIRECTORIES ( ${QT_INCLUDE_DIR}/QtOpenGL ${CMAKE_CURRENT_BINARY_DIR} @@ -35,6 +42,10 @@ INCLUDE_DIRECTORIES ( # Required to compile RCPP related code ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/bgfx + + # RBDL and its LuaModel addon + ${CMAKE_CURRENT_BINARY_DIR}/3rdparty/rbdl/ + ${CMAKE_CURRENT_BINARY_DIR}/3rdparty/rbdl/include ) FIND_PACKAGE (X11 REQUIRED) @@ -62,6 +73,7 @@ SUBDIRS ( 3rdparty/glfw 3rdparty/bgfx + 3rdparty/rbdl 3rdparty/googletest ) @@ -129,11 +141,12 @@ SET (glfw_dependencies ) TARGET_LINK_LIBRARIES ( protot - #glew glfw ${glfw_dependencies} ${OPENGL_LIBRARIES} bgfx bgfx_aux glsl-optimizer + rbdl + rbdl_luamodel ) diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 2e43af3..f930e6b 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -1,5 +1,8 @@ INCLUDE_DIRECTORIES ( ${CMAKE_CURRENT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/3rdparty/rbdl/include + ${PROJECT_SOURCE_DIR}/3rdparty/rbdl/ + ${PROJECT_BINARY_DIR}/3rdparty/rbdl/include ) ADD_LIBRARY (RenderModule SHARED diff --git a/src/modules/CharacterModule.cc b/src/modules/CharacterModule.cc index adda342..58f89b5 100644 --- a/src/modules/CharacterModule.cc +++ b/src/modules/CharacterModule.cc @@ -16,6 +16,7 @@ #include "CharacterModule.h" using namespace std; +using namespace RigidBodyDynamics; const float cJumpVelocity = 4.0f; const float cVelocityDamping = 4.0f; @@ -26,9 +27,11 @@ const float cCharacterWidth = 1.f; CharacterEntity::CharacterEntity() { entity = gRenderer->createEntity(); - position = Vector3f (0.f, 0.0f, 0.0f); - cout << "Creating render entity ... success!" << endl; + mPosition = Vector3f (0.f, 0.0f, 0.0f); + mRigModel = new Model(); + + cout << "Creating render entity ... success!" << endl; cout << "Creating render entity mesh ..." << endl; // Mesh* base_mesh = Mesh::sCreateUVSphere(45, 45, 0.9); @@ -69,54 +72,56 @@ CharacterEntity::CharacterEntity() { Mesh::sCreateUVSphere (45, 45, 0.5) ); - // state->character->entity->mesh = bgfxutils::createCuboid (1.f, 1.f, 1.f); - // state->character->entity->mesh = bgfxutils::createCylinder (20); + // mState->character->entity->mesh = bgfxutils::createCuboid (1.f, 1.f, 1.f); + // mState->character->entity->mesh = bgfxutils::createCylinder (20); cout << "Creating render entity mesh ... success!" << endl; } CharacterEntity::~CharacterEntity() { gRenderer->destroyEntity(entity); entity = nullptr; + delete mRigModel; + mRigModel = nullptr; } static float cur_time = 0.0f; void CharacterEntity::update(float dt) { - Vector3f controller_acceleration ( - controller.direction[0] * cGroundAcceleration, - controller.direction[1] * cGroundAcceleration, - controller.direction[2] * cGroundAcceleration + Vector3f mController_acceleration ( + mController.mDirection[0] * cGroundAcceleration, + mController.mDirection[1] * cGroundAcceleration, + mController.mDirection[2] * cGroundAcceleration ); Vector3f gravity (0.0f, -cGravity, 0.0f); Vector3f damping ( - -velocity[0] * cVelocityDamping, + -mVelocity[0] * cVelocityDamping, 0.0f, - -velocity[2] * cVelocityDamping + -mVelocity[2] * cVelocityDamping ); - Vector3f acceleration = controller_acceleration + gravity + damping; + Vector3f acceleration = mController_acceleration + gravity + damping; - velocity = velocity + acceleration * dt; + mVelocity = mVelocity + acceleration * dt; - if (position[1] == 0.0f - && controller.state[CharacterController::ControlStateJump]) { - velocity[1] = cJumpVelocity; + if (mPosition[1] == 0.0f + && mController.mState[CharacterController::ControlStateJump]) { + mVelocity[1] = cJumpVelocity; } - // integrate position - position += velocity * dt; + // integrate mPosition + mPosition += mVelocity * dt; - if (position[1] < 0.f) { - position[1] = 0.f; - velocity[1] = 0.0f; + if (mPosition[1] < 0.f) { + mPosition[1] = 0.f; + mVelocity[1] = 0.0f; } // apply transformation entity->transform.translation.set( - position[0], - position[1], - position[2]); + mPosition[0], + mPosition[1], + mPosition[2]); gRenderer->drawDebugSphere (Vector3f (0.f, 1.3 + sin(cur_time * 2.f), 0.f), 2.2f); @@ -142,8 +147,8 @@ void ShowCharacterPropertiesWindow (CharacterEntity* character) { character->reset(); } - ImGui::DragFloat3 ("Position", character->position.data(), 0.01, -10.0f, 10.0f); - ImGui::DragFloat3 ("Velocity", character->velocity.data(), 0.01, -10.0f, 10.0f); + ImGui::DragFloat3 ("Position", character->mPosition.data(), 0.01, -10.0f, 10.0f); + ImGui::DragFloat3 ("Velocity", character->mVelocity.data(), 0.01, -10.0f, 10.0f); for (int i = 0; i < character->entity->mesh.meshes.size(); ++i) { @@ -188,7 +193,7 @@ template static void module_serialize ( struct module_state *state, Serializer* serializer) { -// SerializeVec3(*serializer, "protot.TestModule.entity.position", state->character->position); +// SerializeVec3(*serializer, "protot.TestModule.entity.mPosition", state->character->mPosition); } static void module_finalize(struct module_state *state) { diff --git a/src/modules/CharacterModule.h b/src/modules/CharacterModule.h index e861a82..a578615 100644 --- a/src/modules/CharacterModule.h +++ b/src/modules/CharacterModule.h @@ -10,6 +10,12 @@ #include "Globals.h" #include "imgui_protot_ext.h" +#include "rbdl/rbdl.h" +#include "rbdl/addons/luamodel/luamodel.h" + +namespace RigidBodyDynamics { + struct Model; +} struct CharacterController { enum ControllerState { @@ -17,16 +23,16 @@ struct CharacterController { ControlStateLast }; - bool state[ControlStateLast]; + bool mState[ControlStateLast]; - Vector3f direction = Vector3f::Zero(); + Vector3f mDirection = Vector3f::Zero(); void reset() { for (int i = 0; i < ControlStateLast; i++) { - state[i] = false; + mState[i] = false; } - direction.setZero(); + mDirection.setZero(); } CharacterController() { @@ -37,17 +43,19 @@ struct CharacterController { struct CharacterEntity { /// Render entity Entity *entity = nullptr; - Vector3f position; - Vector3f velocity; - CharacterController controller; + Vector3f mPosition; + Vector3f mVelocity; + CharacterController mController; + + RigidBodyDynamics::Model* mRigModel = nullptr; CharacterEntity (); ~CharacterEntity (); void reset() { - position.setZero(); - velocity.setZero(); - controller.reset(); + mPosition.setZero(); + mVelocity.setZero(); + mController.reset(); } void update(float dt); diff --git a/src/modules/TestModule.cc b/src/modules/TestModule.cc index 265a531..3a120af 100644 --- a/src/modules/TestModule.cc +++ b/src/modules/TestModule.cc @@ -132,7 +132,7 @@ void handle_keyboard (struct module_state *state, float dt) { active_camera->poi = poi; } else if (state->character != nullptr) { // Movement of the character - CharacterController& controller = state->character->controller; + CharacterController& controller = state->character->mController; controller.reset(); @@ -140,23 +140,23 @@ void handle_keyboard (struct module_state *state, float dt) { // Reset the character control state: if (glfwGetKey(gWindow, GLFW_KEY_W) == GLFW_PRESS) { - controller.direction += forward_plane; + controller.mDirection += forward_plane; } if (glfwGetKey(gWindow, GLFW_KEY_S) == GLFW_PRESS) { - controller.direction -= forward_plane; + controller.mDirection -= forward_plane; } if (glfwGetKey(gWindow, GLFW_KEY_D) == GLFW_PRESS) { - controller.direction += right; + controller.mDirection += right; } if (glfwGetKey(gWindow, GLFW_KEY_A) == GLFW_PRESS) { - controller.direction -= right; + controller.mDirection -= right; } if (glfwGetKey(gWindow, GLFW_KEY_SPACE) == GLFW_PRESS) { - controller.state[CharacterController::ControlStateJump] = true; + controller.mState[CharacterController::ControlStateJump] = true; } } @@ -186,8 +186,8 @@ template static void module_serialize ( struct module_state *state, Serializer* serializer) { - SerializeVec3(*serializer, "protot.TestModule.entity.position", state->character->position); - SerializeVec3(*serializer, "protot.TestModule.entity.velocity", state->character->velocity); + SerializeVec3(*serializer, "protot.TestModule.entity.mPosition", state->character->mPosition); + SerializeVec3(*serializer, "protot.TestModule.entity.mVelocity", state->character->mVelocity); SerializeBool(*serializer, "protot.TestModule.character_window.visible", state->character_properties_window_visible); SerializeBool(*serializer, "protot.TestModule.modules_window.visible", state->modules_window_visible); SerializeBool(*serializer, "protot.TestModule.imgui_demo_window_visible", state->imgui_demo_window_visible); @@ -205,7 +205,7 @@ static void module_reload(struct module_state *state, void* read_serializer) { cout << "Creating render entity ..." << endl; state->character = new CharacterEntity; - state->character->position = Vector3f (0.f, 0.f, 0.f); + state->character->mPosition = Vector3f (0.f, 0.f, 0.f); // load the state of the entity if (read_serializer != nullptr) {

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 literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_PassiveTorqueAngleCurveSimple.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_PassiveTorqueAngleCurveSimple.png new file mode 100644 index 0000000000000000000000000000000000000000..e7db07a9c138125b850323affd65bbf5a255db72 GIT binary patch literal 37055 zcma&NcT`i|6E;dmx`2Rm3stIgq=}+{Qltox8bEp#q=sIlE4?=nq)DPwX$e(8iXsqt z5FwO6LNB4*gTBA-TlfBTvsP9(Cpmleo;~x-vuB3bXFBRMlx&m)1Ozmi8fy9k1Vq}v z+wuw-aL4-3?{B~diPr;7!z;im@XE_Kz%_-3#tSb30H=*08w=!^(_4OmrYXf(? z*S=PswgkSuzM_t9&R#ZF9=4+Hp7yz0ifjY~HwZMVT`K0f~3J%6{rfR>*A-N|k+ z$^d>2AF#5>49mKrMOH?jOH>u@>&=izW@H}b@rP=&aiqqUu+ ziACs6Y88{;t87#Zv_vj zeij;aVj-xv3fH&yMVe%K>^NaPrt-d9N1l1yX$$VpCbS4F9_+Qvnv`Hf;jXGv+Q`^u z`nH7ju+!MCmV3|fgg(3%KX2J?-8{*w)ew{vMLgC76>h-7LA*_4)CNX>X8$^WBS^C&9Wx`l9S<{ubI3T4a^P3U0l+ zm_*FYb$*AFD?fbB7AWUs$&umVe5@~Bk;&XBb2WtxHeq>a)*0098mnSDnKt--X{MFjN2Imldy=Qa1{&U`_W4k%z9_o7!zu!Z-_ zIuApl@)aE_Uq|%A1BsjD{gi2rrg1fK)bP*9T^70-6YB5Gs9}`*`)N*)Mc1)N`K#TQ z$0?FwDnyw!^}7UKFh%|f3$mxbX>)&{lRNK!?4AspH~D1vUT;*YZU>$lN`ttl?PKWu z08?a#SX4GVBO7u1)gTn0F0RX}Oe@EEB+o2v&XVi10?G<0f4&i9xvb|9ydsD;X zjj^}aq-fgQggfbi*f$Pd2x)_U7gHAq!q9XX0tz|nEin5~J<{6r6F2oADivP}4q{BC z$z^PYDs(3uf(b%+m0~DDk2p(uuy{B^^B#LX!yG@S7_+2ojS`TxA!^VLj&`lprod7R_4IPzOK|uWdlxPi zexXam%-o8Of|G*mBNEkAe|`X2?-~iKkf2@)*WNszD@@*(AihrCdfRqR2K>;fdvZT5 z(le7-mt;f|&WbqE?s76M2NMn$hj)yxB9xzsoP9=JkRk)PBZBOgpdVOv9h(lx_$_b755`;5&deLY0^6Uy3C}T6U_4TqVB*4DZ{VWR_zd?7 zRe17!i4&ykaHNEZJ1f{hx*u}F!;mmf)`@IXFT*ZJa!`m^SJJCw@UiDYckp`_MsLa~ zV&^B-QryxJG!d^3UfH{qxAgN&0rQ0lXcq4HB24uUeC%!y{Be zIq&z9?&1YxvP!yW_mZs?Y~__G$e(_QMQ!gKS`~_HF}SdE>b!nF8_9X4(dIqj$nB|D zNE!Lmc4;jUBtZxl`%B3!B_z^P{m0>*Kq6chN-4A3B1FgsM*~6lVeqt@;X#FSu(d^y zm5LDPfGT2yF=Hy}`Ed-foldvTV^dg!1Bh5g1Vm0}mQ{Dt>0^o-?im?7u@gIv;$(uO zm4IE=f#x9hjRF_d8>7CAV>XwBdTuNJb&u&u#tevy_`(qASv`TmXa}A1J+n~KQC%zk zccX(5%b#LJWVvREFf?YmQs7elHb<{(KB{Tt${KZII=xAfi^|V8ObV$R3iI`FCx@{U z$4_m)qpdNw#9LQ!h*lMj@6KEeVs0&UKRJ>%(StB-2m>>ZSv+aMU~KeeQ}`b% z#V5d4gb1Op))Q?eo|Ww&dtB<1im=$`jOMINaF}@@gxl!Z3Jp@3O$E(#Fx$d_ueK^V z@x<&d#Gd(d*`U54@2;p}{F(~d3ta_$ZeI7L59)J)5V5NW?QsZS-73Of5A3rJn>GNA z@;xj{{8L0BTrPOZP}p07Y0_66tAducI%~K&c_W!B`OImb}q#Ph)m$7>^6Au%YxHl4#&6sPfpK|d? zO-YwBqDBuHwm=6qY; zo1@E#@YbeE@FC{v(IM+A{ocY{Ns+^Ba4IWF-;y#H$>~C+-RV@^d3K{ZJ)7P%GnaCH zmk-m}Kr_QnB?1r6yP%j4(R+{$JrEFdpp-9x_5KVmvt-KdyIw+QWm&j$?!+0vDMS^* z-be4+7$cxk^G5`BDAMWVOc}LQw+~h|EN*+FvlqqUn+n~Wduq6Jmt3KlMW%*{Dkmo_ zgk&uBvC0BxOV)r5v9{5Y)Cn1L6k&)+tF+8TXOWs z0QQO0{Y{m)>V?jt?{sVUGfeWCMPq89Dn1SsM5iAJcB*(M^D~0)4ec|ojk^qVTydn@ zu1J=-_= zj!hvTI$}EoVsAO5maR8(u1SSSA1+5oxC?$xu-)ru`yLa1SaUmw#)YgI;M zl$OcpwJn1g*bT9Mg*Od)BIeQKGS{5=*b*0P>XnxL+%QVMZ}Vnk`Hc7+x#I_Pk>V=l3OXk7Mx|L=&J0cIp%jRqC%`2BZJrjyg1AQC;rzK1={7^3uu1;;;U= z5#?x{09KeUgqg+a@@Qcx&Xc;_9?_pj8l4@_MILsyfA^+h^->CEL~8tb&HR7nlC znZiM4=%}ili%DBbF8n*|xLi~y(`VvE+eLy^cN%6A&O~n>Uw{1)=S$N+BZIl(Nix3K zwY@MPrr80fZ9KmFTohdqe$i)iH(7sxGp-1E&Qdr6Ldn7=TcDxqXL}c}7GXghBBhq! zI=HC5FEvMs-E{K72M^$XOLvtEIIsmLk2Rj{JEwi}W_^@K{CxO^jTO<%peCKpgw>E+ zI_SxXs^a;Hl5JW=43~v2L7}c+AhCi&=CraYbh^ws%=#!oq&ojdjCoe8$I~u2$()l* ztIl<++PShy;4=R>fuzi?6X?|Kj4jI0r5wISukAF_ysQASW=5BSCXwC{O_25jqa9Ud^= zUZj;|hVG}6{_o@0wIw?`TE0x#4I)RmhN*_@9BT#znL=7g(K{hRQ7mT50<9i7hCPbW z4=WaQSM{m|>4eK0a1i#WWtQL$)c$05LFSh;IwNfJ3634ZK3@I6h~8T6LkY$njljk=Y!Xw+*^9JOA9mcn(m(n zsMvC$jOSi!3z8M1-f6=ld!@;Q*HkEUdfKZxTcOsKN@R0Mq+?OXWyR33t`NZszw0au zEB2#}Q!=8;e;!FcWSfl`uZ&`G{<1dHQqAxlYGJp`vrPAEopQh;n$!I=62^0W3{2IR z3Igx(%4$~&5CLUjWnQQnocSqC5IgB&LicHZwjj1xA??ix{(Xa(TzxyJj+z2{%=lIF zH_JI^gwCC&4Bn_>|&#!DSl zo{yi@nzg3b&@oi1I923~tS>Lr< z&5fIt=?d`!;bl(d3FBs3-NB6)vD&$6zhW0t4f)$g%p*9(ent*9)oNr*<-(Fg`AIJ5 zO6`2_`6#l*6;;Bu9(M-^8sO$(&2iJu=vw{{*zc zTG;{fX(jV8O5goxxG1n&?5Cs7yD-xzaMs2&$Rf{3g?UPPztN_x5S~Ly?)5S58P&vl z4`n0g0Md*B55@_Xb43hMqX_`4b-(oMw}pf)8LD5rvP$3emmfd;9kF*-%PiWL)bajJ zbhjK!m9+K~!&*PQkVB;k_SSm+XWb{+<7#c`E2U|%R1h2QvBt@yU&B(>hjd7a8#R<| zxX`=gRElNR#?y|{F(JHcvH~2|x71Ho=fLDg@l^8$HR3lNb_*b4c(>O0-#JF&@KfAhKKes`Hr7KVl^Rj9_Nz`;~p7bhIILqdP=S( zOE1OvD_Gu=KW@vKgDuU9Rhq@LUC%3k6!*oNI5S(RVmHWrP(%U$7!FwKIdGf(~>i@W61yblOdpQRK?Hn){}Ma zr`tb%TyUKxPBsV^`R%;^38|##i;N(3Du1Uu+ih0@7WqE*RFblV>5<0M=e6MLGN_^0{n!$!%D)_q;U^c%D)0BG5c^P5N!4e8K5hyU&c`m z9@1}2d!fu_mOp@!b=2t*wIC1p@owcs$-+P<*x%7<+q5~|fnez2M@5zmhU#YJ3Onh7 zD}@agoX;0`D(Njy%h2tbt=nx=n*k-$jm96+DB9k(OXDRphKu*WmYSk8^TyTFD#8ZU znNQ_Brt5hMN!r=8IvT^h@x&k0M}zr)+_!hE=a1($@_j+9XxG9T#K#P^>$LGwBwa@ZC+dt8U32QVWM97cBFVB3m{{L^bXYGxzN0 z*|HXoP$k^}_$B-D|BAdHUs#46x2TDV&C;Tcxup$F;a9l*w8?9CxdveZ%uRFu!ldyqHFrO1ThO?|)WdDYlaQ|lQz%r06vov#cFB6ux(`$7VAF-qL!*w{WR z1U(LrJ+(H0x^4@W=zZ>5xHvAItm8-sYP974Dx2NsM6MDMRA?pA`xI)^ zKLqm(p|Ma974W)`E9ikqRE3Wmn4Gv>T)f8yr|qqF2Ym0%T{!F;CSFKL-+&Ws8jtyQ zOq1f1#)5?`;bfIH%STV_t?!xdijjLRJ6cmSQ?GY)Iq|(M}gMK9(OkJ#ysp%}V+;z&Q(leS3sV z7TDDDdpIDat~YOv((R><&V_Buoks^9U-S~?cM^Q8=|1uPdC|A~n_*cO9?a7-5J{2R zA*|0guPqh9$&>juc-%7a>62>vBY{?F4S9>Fd+#skWZHi!A;TmURV^cVh1_~RHRM3C z0W=vx+Z~{SL2l^X)(o)mB(@wIwyK2e4-ym3GGR*VV5Wj@6!w)%8zh?I|~X|y@(1tVTr)>=DVov#(` z&6|<#qfk65P%R%oBLiB%!d&;ct25}IkZXf&&Q3|4PmU#x7|+BoE!?`V2V-%tus^d+ zl2EMkQz&ht)xwN*W$pf4)bAMq$ChyOYiTEY_G&;=Z&Cv5paP3U;TFlbD=xo1;fdxUtJ;Qtu;U|6J`<4;o;g)fm zwZ1le>9V}l?hG-@cFNZjgX1|f~o)@OGyMEkUH?zK-$oXf2HCZDA2m0QpF za~k3Og(ir;DE*<=*wpHro>#Z3H;kAX(}>HBBfamBxdHMMdZ&lPig8?hr8se0 zfTP?_%12s}nX1T$PuNCG_!%vuCcm|D6fORoThnjkzBbiFTT9&GR#IY98PY9qNOB~M*Cips z>t~?xxcNIP&HNMhpqwoZln`~UIL^ib_yNKx=7HeyjcU8vpJ(+H_^ooAj*jJ?`1m&( zZFkDcG@X`WkPV^}^0!65GgjN-G##(5EAijD_0!VNvdvSW|FxC$!{vl+UYaMW2I-%t zFx6(FzGr)DW90AEGvB6A>SnX8RFM7)2+l7L zB||(kG&E&9n8C}t7Wd{?XE~HhGpen82lUx3OI)~KmXw`fClN#kwX|+)<)SMSfk!`< zC)#N9y*a+1ug(@4UMBJK%apGII;Zu}xzTmzpl@Pg;?X=_HzJ&mp7k{qbROdPsSNh` z#9eI(dbgkxYfU-fT+iOpzs0TD;rJ!t24zm===^+eG4ONxclq%Qh(~H_Y6C?@n!raX zHDwO)@&EU7sH=^Lp?}$Pfnb>KJc!v(LU~U9a6Tz1X<@?2Khq*$Z8Fb`pU@90W>)9$ zW5ik79gr9R^V|L%AC^fhhgJ@p_eYyQ9}2uCHBOH``!w8~fm+Z++;)2)l8Mfni9j7t zXKUG6i=ahr-O?9xYjNGWsjYpCiKNJ#vdz>UtISWnTmwDp54UF9dCc=FA5zo@F>{Hk z>0<$h%G<-u3Sq|N7Z#Z#Xt4y{!+90CJ!f8E_mMJg9OG=tg^K=NlHNWk(y`XTl1o!B>Tq+0C@IoobKOxw_rYTp806oF$CH3^g#xiXvml@WY2@;4-o ze25ROZ>3+QpVHjiQlc&T@FAu@Ro1n}=W(TDkHj`17b1+#US^x~;Xd*qj`%L%v6`nY%?L`AoV_6zE z6-Ty&E9w6JRHZcQj4t_aL6nL#h~?d5S?NHDSHD*Xhcr|TSOE);$@3B;E_0ju%uAJ! zq_!DXKZTZNo^f zag)xIpAt1)X(Indyc_Ck6V@Xec0npby>pVbWD6HWUS;SM7G{1J=0C%c|@?vVg5J%k|pd#TTo+OMRc85Eq z{oW#-PsmTG?F?_uBuX}F(y2d4M-h5R{)oNEg9w8=@!g^7F&Xihqrddy9B%dep}JGMv0Bg4S|*eF!sT zk;Qw;Pq_FA0D&HE#7lRrobA)-b7fHcpdq!v|&Dp)VqZ!J}lK>V4q|;;Q9+f zMi6}WDJmt>aT|N5qRc}ccMnw<0kyc5_|Z-^6>u=Jk=I~mJ&^$fNas24^q}yeUUh@5 zAnt?2fMv9_J?E9c;!bZi7dr5eI{`nT`yKs;nD%<{+dW(GePX{0eH1HL%hgD|w0QHI zV~)70YN|%tJ;o-srq8H)Pi;~Q!^h2YVTa@+Vx$3se!9v6R5+vXnYCZ<;$da-p|#dS z-U_J|_t6CCA5l50YWJnJZ_zAj2{-;B6lP@?Zf-Wi7Pa#2Jp%1=!!7S*g=9I*lC)NZ zAJRlA8gG#Xldw2RL(qOX>VFizUt+m;`!l+Wispp&6lh2u2jYEa_>ClXEFrg(&>_6T9!U=HB% z!%*aDT$2L|SxueTWmOb8TmbbPPKfk{u;BRP=o()+;9g9Jxx!VTu1AKf8rPCIZ5oI;9mXvORq?rh^(6!rV(d` zk@T@@yXGz=Aj^hNXt63BBQESYNS?InayX6r6ZDB`CIfH7f)43TIYs0|qQUZ}BqK{* zp^A~bM==(W-n3WBF9?qy1NOm!xF^+ip2QxE4cG~T;R5QmoN*~l&VIWu@t)0jBg>xc zZEbC!TkhJz7Oh9+@hoBom+wq-pD9x^xfVxI;($7g*oe{Ra|iLpogjx!NPEQo>JDP* zDyDn)1xg-H1yx(7f$Iql23d$-aD$a9b>O|XzOrv6wV;D#$xi6aG z5&ru zq7s;Y!jCJH`22cx-(w6uL4j)~`Db42z7+n74~l~`uzdy=1XTZAoN?0;h@Ap>!#)jM zD3l+T>Q2jkfFc8XKRXZVn`IM5%K}Ba0BG={y+s0oOm z$Euuf7vg*3Sqc?qoq423QL1D1FyiTy@X14Xc(Qs}ECtpJi5Rz0`an6k^cFnb!7#5t z!(M1766CCXh|qZ)(xtQ70+tqh)e~NpOfoiaf(xKx{-dP1W|z#n$9)^kjR~jM0m4Wil0q`PhL^%p4o|S{tBP$vk7}F+pjqw zJEn3Rbnie3;-XdJ_tr?*;U4DC2~J2bR44;Mk?Q8Gmf8$@b&Ivnb?S3hP3K~A4iqE|6OK{UiCX63@rjk(kfPA zm&&gSM|e@x;GTOKZLZ_kW4YS&HrOK7R-cm)RZSmJHky!IAnQP`L3e*JQrU-`yKwr}PT_S^2FiFKfeQ#gw=YVmJ!QwHfckv+ ziRH2y8f-ira$BCx+7TyP(|on$T{V@;;B&Q^vJ{KgJ=O6_x|RJxyj%iG=Hen|7n`6& z!(#E;N~r%zU`f?QX<)eryQnZkq=sU{7qNTsJ|yu$u@`JQH6*p5kD06e7}p>_~0{HC)AGvzc@OFMmya(+hjhishjaZYDrKiWHVC|N3K0u0Xj|IZzCcd zlWnv!YCEGTKJwgE>VJq#zff~gczGue-Huq;1bq&imS`{*AMs$?z-_xUwna`VZhLHCfC}wh4si+>8cf%g%y~CMQ4V1P*v+I zry*?Qx=dL4oa9wuzv>S#HGuA=wsu5yJvaQ?(f^WJ>377gpYm8KSfuU%h=VRKgoU+dC*n=Jhzf7_Xy;yP96TOs(CQ%GYov0NXvfqx{ zXzV#il5AoFErlUCCn|xxsa%G!<&Qz(jv*Mn?&YlLL z^^P}9xez%)!Abc7DhcB}yc7}B*8FQnbwLILh7v#hV4H?(GS&4SkZT+F44afv!prJ5 z@5ZhE`DOkihvs_$3@xO&upp|N&3D^U8!{~DkYXaw&Te}`QO$NbjI6UKbIf*z@}Rt6 zfw8q#VFPGrC1hRj22kz-uk)qFy+MZlHizc-{WRC+5sarsl=|GmUDW@COK%7 zB%{>KZFKK*xc{#KM$oe(sd3jX>rb6Kal_pKPQk(-%&z}xDAS5*S9P6EjE|=&AJv$! zsV{9|5|n(q2~+Y*C>?Ec+u1^ER=4KOF8K4r2gtu2L0T;j*{M^$x2a7KG*CU zzn&;vrPUSCmzTI-b|RJGU_Lar)s++P^ti|e^kqgjKFvRG6&`#q-LeUzdZD)J{3hk| z!ZSpx1^20Wm^XQCdYDU>XDerwx6@5F_KMLq#DcZ91f;uw<=}+Fv2&!+{+)!Cq;G)( zK3#~Tk6O4nSe%Dbvf!qZ(FBwzrPPkt6zJ}%NVI_wtk9p(y(K@Zbz69N@n_Mg&gxiT z#JX*IK87ax$n?%2A{H;I*SI4nXzO!RUi==BbB3vrHLF$VXN8Fk{-F$%yP}})wKEk{ zuEE2pI2tdHfY{}TaS6_-@$P)}of~h)DIxc{bVr?e5n7e(AAuH=m}VhIY0XU15SF*W zc~mj9O^JKm%kOS+WE6vlg0kX$lqTv-D!9o)lKI>C?O{bC7(&ymNUmwI<$c_jeEpa6 zVu;?Jl>B6;oYJkxDes}Zh4NlTQZgIskR|?LV_f62>Ai8}Rl>qv9kMCpdk?K*PqaV( zL|d@b1Sa#(!#%9vGPKCm@#6Q{X^ue1>iDAprK_i|@oh4>e^iCB@QFqis_SuctG>)K zWpDf3#y!2BG&^- z^I)!#S4(JrYEZ;0{I`?pL3FfGfc4>fA|pE!0keeg`K=8=n%e zODuXLuS>sDCm&NM-#GO;$&Ducy14nm&RTO4=&iwmU9@VxM6B`KEL;SsH73gQgL)@n zjN8(nw=(p2Wv*LJWs29U=mP+T#7&f*?_el=BM7A0O{5A+xMPu4HUmm%#d0!1!TG7j|Xx4G#MG zcw8NLx(%*ErL0cXP^i-#Y_N?`vy2%Zt?u4w+WLT1zUCVYomYW3cxUO#(&Ak=6NAv4 zh>iDa7wo6X8M?m4odIrC1D46=eu^4(LkE2ai@8vgaDPbn4i)t526c%Ycwzq#$gfTz zTCu$c?_5CzD0kS|u{6wkhDw1fD6?4mS>{Qxnn#7gh_$u>wkPHF2>k0wZJ*BDLu>NS zRAIW-{<8knnRBmCCSK>g;UwX{?PPcI23FR@X}wq{8rk{AZYtszhCteTCY zR1E?{M=X0R=$!ik)*zjxQKT;|sVzbn*x$mCoBF}(ufy9Vvidj?4e~@(JHe5>gXQ~? z1HBWHr&=*2$yE`@!reJHyUM=VgpaV0X|u!bqo^^Fhw?5V$WT(X#jK|!y9)P?0vC5^ zX}JJ4xevOZZB)e^oZM%FKpRI)?ttJvSA(#(kq@Lye2&iKs#(4603ArMy#*DZj}NT` zy5Y^xP#8JJ0&Wwk?Ij}!Qf3y8sZSL{0KALrb`5ef@IkKiE(!j0QRe!?fYCi+qg`J% znUjM?*x9XQ(g1R+j(4L?kt4{sAY(?x3C8M!LvfeyWly1-ZCq3!ikHHbVpMx{^K7CsXUt`qZ zcF|Xhe$4nofA!6;9n3p>sqMTJb? zP;`+C^>{nf$VhWl@KzVP4J7vK!8q*$A^jxKXdUn7d=3*pLvDwayC3?}SBT{b1n<6_ zYECRZ8Y-=-zg_NldNeIHB3OQzKa2)C;$WTgiS=y129k2hC>Ujg92kJ4U-ThywbzI- zlsV!#67`WEOx$lI#3eOe>aU}k`Uc3gHuk_xVXxzZpfl&ET3i|w#f=+2~bPAb238X-y zXKIi%gQ<1h=c3eHgm`tr=C|k@a`+ zO58Eh*nIMiDat+tIVQg`$(ouOXV3SZ(j%8s)>sU!7hhuYW0yglepGjs_8OPk{F8KV zU4T~p&((=f=J@_Wv5wRAMGJ0v`l+=Zc zQWb-#Gb$=)pur7Tibhv%DCy%EYk$p2eG6CpyFl06U2q5CcW3f{Pv+^6wclT^*X-%e zMW>DZ-HFch2=NhBO_E8u%k-(lPk~EIpcfpLC2Z-n@T)fRGWxe;{h!oq6kQ38<)RJ7 z|H}HNAt9jc^)Dg)nE7Aw)%JQ_k>*2Mz*c~7SwYcr$y@U#!yq7=1q_{nA^uKC+vSEc zmHeB&e#tW>d0;00H)|{STXpPqeID#zrdac@fH!HXLtemxw|b84z>P?%O2^BYX^N){ zG)$4WJruG3^fwv)I8JrYp6K8DJpY^hWs)7RXNmtVyNc$jS^iD>TXD|+>#P6IFNc!} z`Jyk1)I2U9SA+BvsF1pu*0B7y%5YWJ9O8eH{x|ghp6^yCHvX%qlv@^KhPws-Qw-T( z6MK_pH5yU2g2-C)$da1Nyk2nZ+u&{S;br3ClZ>MHWyTU%ie zw8(but2g(~d`_IkJ)E}AX}$VUrf z&uZXu=vg4<+}oloU-oSFy@wVZM(=L!t_&#zt^NG)Jdt)9s9&7yjp<(ekUs<~7plAj za_|Za-@~VCumMKSuxo3_Zu+d)vqpkEFYe1}aB14cWh+4r z=Ha=mhw)-BSA02+G^Hjw)GZ73o zrwW-T9R_>iUhBa;XByoXBtgAJb?@VVY2P8$?%CVhW7Sphp@Q?)wyYE?$FH;!Uw!{~^p?lYlLJ!ZYI5|0=3jJ@Kg$JZ;at@xuwq0mg zkw6V5X${CmrpBj>yxtv>J(NSw(;lHdRC{gdZf~k zC;?Q1=qo6sN7BXb2L5gnlU_eeD(Z(=Zoee}1Wo#3Ey-OdEIjA&UM40cMsn!j?EaU- zs~AimONNIiU}+DZsv6v#$8Rt0YVm!k0KGuZCauA`x8h!Vdz)Vx=M|{T!Oo|7ydW?y zmZ1VAh1>%RKq9LlWzG}iUFy)o_Eq#Z^sd5yokaG86Z6FUe0zw2!B}J1Fj%XMv)*{P zITLV=^K$XY%g9ES>rUho-zM%LuT2ACXF|`~VY{SmKTa2Dd2du{cy5pa6c6hT&l};o z_Y@QsX!jcG>(A<%n~x>1XHUbeQ}et!oZa13s;6IiV9(}T&Z543`eCzsiAjha_ok3_Gbf?LpVLO?d{Q~6bzj$ zz${%nPxmOqjCxUsp}_6xFLIvU~DSKadU>n8^%r+cIFhqb8s+g{DH;glJd zSTW9?63FL(%Ha#wYcF=E(T<})EVz!9>xMwzzkgp%TG!Z!#kQ#+As2%{{jYCqK*X{= zP}|gW=9RX#_F!nmJiaSTVQiqh|D^<7Uk-;tXzuSZ2n3R8d`}kuM0y=n1HpOx3@nG6 zye?BfS@x*q_e!q&Rt5O3m3TvmgJ4lh3&b3-Y3d^l#h6Nl^C`+xlb@NpH20Q!N9Gy3#-LF7a0gxG!w54AYJ3iaDIz(powx} z#{#6IU*4D6)}=~|PqI(`(9qd=BQZVbGK^HDIOPod7#s5<#ZkZAp6?K>NbuU)M=Ur4 zn%&aK3YPbXJ3OxeT=~Kw&;)RV0Q77RJkLc2KWx468QYHET5hu>2@C+Rk%`Xv1~AR% zdH_goZEdNChw;4{4wB0p++-}A2P(Lf(5rsJsr|xFvb7a!oy``z0U*_tLx1uf4id~9 z40??SCl!gZ1q_5LCpY(k(roev@ZTyz zE8I-<5CX3Ea?3e92ZJq9CBwtRgR3gSVV9u8MXH^?y1xGP^>CiNG2l}Lly&wJ+5T`F zxFF!^vig4>e^pJLd)KoPg3kAKBX37eOdKR?@p%c$to|{?p4~yuN)I=yyng-KrDl%* zl1}~%6w%AzpG{?b1+`q5D8aw%^l`l}=6D)mk@YvYI~tBzk1#^|CsY2r{6`N2Bb@$~ z#;?{o{^OdL0l9QV9uW5K{FDbAh|?8|P-K()OAX%jUaPeYATh&&#h(48xKbrIH|5Fy z(v#A=G>#+k&;ADWL`MzCaM(XVS`VkaX#cS-8-49dUaY~IcI6W4^vQU`>uHH!-FJtpe<0Layxq_Z1_(`&P`pSe}wF(U$kN&#lOLp^!^(xIuQKyKe6h6nbR%X z>pNj0Kt&N4TZ-$Sj5O7MU-cZ{h5x~EqZW%zKL48^&vsceKirkGlJp;|d@LcXg#HUI zZ=^D;`v0R!KS9wS&>a6Mq2xbK^s($;cfk`97C>Es5jkjJN7UkWX^}z);s5l?phajB zfu0HFkM>;rrzX8?QbY18X;lB{Pogz)C;LBXNmlG`1ph7Wdcz{w|8Dzfr~VsRU*PX? zq?CH#FSYx>0^f%zS&RLf4G)4EPC8OZ(CGNl_`dz`yUUxhUOGk`af&I5Y~~6G!i7!^ga2fi2xS&98=O^y8m-6i_t{= zBY*1)Q2wxgY_B5Zf425l|_soQV5-p(E^%v&UvIPIM*gpQ~f5x_7BO36Zu~oQx|M;J=1y$v)G_Ne0`H9pw zHWvPF3%TuaWt3QvbapeZrRn{98%G)zfVs^A_&j+JO-ss0=nOGU_-jH3$pSBAc+%tD~EFwQYfAcu-+#9ZlJu?TGfc-D)7x=9ftOd{! z!-u*9ey&^?(lb$GY7fDV?9XG{0M6PmT&NOaCX#Up(d`sj%n*RA6K{_`qzmem_m#on z0c7#@@%61G1~q#wf6mCrNDcFz#|zm50kR}a7--6oANz}U9PdWZx|sF#fQ5l{A?%B` zfPDiI5fM6)o`Z!t$1!hFu&&A%(SRfE8OUmT z0X4oC>m>;pTE6M%f#E&2t3hnKUyA8J*Vu^al_|Lk~FE6Q$3V__|%(Phe0 z9pD@VOu=LT&S)AN18j|5N*>)CGqR+r7w0!x_q2HsH{Ta=qp<&F`=308s;a6Di_l#6 z_U3ZnFB8-)a4>Lm+IMwztyvzn1YGIoksv%0Rmg=}v@&BY3X9;wp2^@I{`p@o021fV zmpWxp;^%%;T2WDvl2+z+0Tcb4FUeQELYau;>W&b zYX8gM3+lV4$Gd$!+@PIi;J`6l^eF{i42vkU@8TbB{?S8egiLFE$d?+6e&V{hzW#)> z=_lmE5cm4fb*4e7cGk8V*j3JlxBRdpvw#=$9=&gCI~yPWZVdE(5sp7+kdyKl{gVgb z2`uP;8Mi{;LO?zhQp=#k(gn3@ZKCfT5egX&luCWH>J}z>> z_#b-IsF?v)S7X+^qa4l$1BjSVjoI5YC55BIq&v*}nJDa^OIH!mchfrRhIG`$E%R)X z0@UDL{-f3hr8dZkTV;U$F}=+l#JG7C`%AW@871RBA1J3M@?)Ihkpuu&eM5Qjl6UC@ zQ~OVO0`CRvtrGt<>Xm1)JN5;{e$GK@0ka{%wGb_We<=yj2cm!z)nRyf>SV3u#qUyy z;pQ(9<<1Rma{^-o=b&ZY*Udr zz*1-qz8Y?0M^c-K10d)aHI|!`BT6DMk5}>b_7?fH@&$mGZ?r%R9|ruJh=x;&ba;1u zVXI9=F?Yy?J21N*mMPEDvHiqn0!I&Y75*keM z>t6!VAX8fVUrgvPSRo&VkVWjppCk2%rL{6Ene zNc;k0|KAQtrvk=HN;8~HRMuzlG53FD`3`N)9{h#A!%sYQ0{@)ah0TyR^ip9m<{D*fr z{cO(|b^0}AwZsWWXEZ%9FhJM!)rs`gh5J2zY(@K<%6=M*>{x)=g9uU(-OrfOW4m%c z3qMLx4fe0Dso9gqP1!&|byMBcY~!Agzn@gUv-r3y^ zWBs!;6Mmj>d7WV$L=Og(4hyG`Hr(6SGrXa10>X_)_~Y(0L^KF8qIiYlyBF?4uL63l z5Gj6FP%IJwOT02xJhoL&o6=S4@^^K0nu%bbrKNS$`de99Ih?lfyV71#N*M^9U#`&X z#;Ov*=$h0{RQi2&z!+BP41z$ANcpyG+!9=;pM8!kV_WU?DUim!whNW@Koq3|BbGtj z$fwLQdvi7|9}pOg_^G;rTl$qouc3BGUVZuZ!dvlQT1F z_1;ereWQ=kNlU`@Gw34>_c2MZG=#zvyaLV_XcUEDyjO-w;O6S`NXebh!5$=x-WIqE zEtFsL!H_Z+-i>`+GQzN8G*Qwh={_ak0qu!@$*QXhv;ud@0pLX?^eXG9__&Hqu z&u_|hcUN8N@j@UJY(G-AD3Tl@Hy1^dr9D9l7>xsf#?254_cKA{u!bbf(L5mn~z! z;mO8Cbvi%2d@cq2*fjXBPV{P&i-YZVVWVY{rM29$n%Z3GZisrQ1eP*j=%4Y?Wu`F@ ztKdHNYhZA&@}7ef$-cZ;1d<*4D`7$fAeRO|;g!7ZZfg(&9Sq~L}{5C^Eymwc|KU@XKM=#1upY-N0QC-eY zCw4QsgYlB&mLPs+luIiEi+-!3?Dvbpn}cNR+qYQ2mcP%Ndz!xpW_PdaYb)e-EYES1 z)m8J)>E&k{0`zL3e}mW_gH2)ge|J&V=%CrVz*G_4k&f8AR|hfxd@}R<8>pE%^|20`B;}HE^8k-2kxM3p{crUS8EK8ao||f)vqJjHQ8i7CKx*tYD7k2l z*pkiW8aMmuGRcVMSU=L81`|qIyk!3~5*%eqPm0{~7yuoV(-7DQb0Fmj$>^jIwb}mZ z>EoZ6;(XG>F9F~21lX2y0x^SMf!9mY_1%#H!1dcn0*;@$+>d3F+P-40F(S*t>5GA5 zmh=R%(T}XbMmgdbcxfB$pXLt%(IegNqoA{-tjw+3D(dHw*@NdI=PcuOhl9NDkS^v! zI1>;u%C%0gjQ<5#S&!{j_6NY|qW=P}tjFFf`_rw}toQ!KX&Or($F_o_JS0BQIx7+0O66~dNg-QQDc8-c+TFptWRtQ*u&snFzS=3Dph2DRvpPhp? zor3Pv+&Dgx-S#GDsFIKewDHAv6ZL+}H}S{cP*3p3UBG7u0LIt)SfU~TorqI1_mOwr$ z_jYH9D)F6-K3_<{-T($p7XK8N8qsHO_2IKzi2h))MWsyw8b`#W1y|J61l$M+*t_yA zmlx2H3I9ZO8P8g{#6z~hc zV3w2>Qy`@2p_oZU7yAaG=IcLUUa7aaHI2Fr z-!nIVmMy$SCMx>!rO;WvXtHy+vY4<+CU$W*GP=8~wPHou zw>72(_2JcGyNWSXpQTeXL?@+&++NrdcxCK@%&RV&v&?6H-G^g#3l zvt>esmf{Z}>@plN zdLFYW7R))hp7Da1&dv7gI!scPjnan1f-sJ!mukn90>ZKkpFH^l@`J|{aqZ046|PRoym^=~1SWteh3!1fv~(Jj0}jU_Rq#6yjjG|M>ThPPW3#k`!B`7< z@q)v$P69F5!>4xUedY*#CEOGj1UAW`*{@C+Obb&!qbN?(I!I--J0?XV5jer5+5ewV zKh0CS5{rH2qca4XgC|@aewlf1Uj8(-4<49Y{jt7RRc}_KGYCRAPk>U3KxuurqRE-n z*?=rco^UD%-(YCG8iQpYj!8u#`i_;xn}?MGY<$yi*j|x{!BQC?e>J;!?nN0bP6Y@X z6AH29(QirXiAehPHmY7f#jMQc_%T~l4H-J1kK3vQxFQeEuB59R4;z-21G^kdB|QKA z7tZYlgUUT&W>4O(eW0KP%XNljsr;r2s8Kyvb>!(m!tl?m3rEDIvo`VWNfB=jdFfl% zp94!q)4CoD0){*7g2v!3iZ~nlkwsrKNCCN>uTuP4%mit#8%JBpP|4E z&Zs$%$86|zH(}Py0W2lA7BJtxXB*yZn1 zM`~Zb^~6AIpjH#p)1fxZnk+bH-@bGgI4>m+h+jCQ`d!@J)2P1+gFLxSqv^r6=!4(&90>m z(`@0DS0F}rj8o>TQWd!t*~+hz(sk~|_jFXcMF&Z++w|-2VhjNv3ha(!b~zhagyB3Dh$|Wa5n1rpq@0W1_VTcY1xOtnQQ={#ba{6iZ zGm8-y^ZoTw7EHSS_t`&?-8;}2);WHTaNG4L17!c|V# zn};+a|DOI|sZ0IkO5yhZ{yp{b1Bke8#){(we=@4m<;nPp8Bc~&)<_gxZ;$);p2ElB z`8wmWRR7lYd*B!;=p^L*Z^SDP59e`@$b_NcT~f)?dH z$yR6jx4`$v3&+TR3#3V1rRFjYBRi6AwQK*kOYEgN5mAnZHs18k>^=8QP8ZQC=X3yz z{5P3qk-<#re;e>+Oz;13Dk{8>bgSQB_2p8T6zt8`w<|8|lFU0%4l3{(x*ziqf& zQHQGRHi(#YWb3K@w^JH^;!RtyX>v~!Fzbu|W^KBZUuWyY+NSt#9vV9pL%Ru&Qpo?k z%J0wcMG6SCh>S1YzZw0?bwheWYE&z=+mZjK`}*;~_Hs}5&xn7&@~7UO)saXfo&8JG z^_zBP6~~vn4Wi25D*t=r{iQ<6(lgFT%{b`){?L(EUC?7n?(}c-8Qgi+dw>Yluv5lU z|9b~pM}Mlq6!qGi__NNZ|3-PcZAxweUrLbu_YMeRdzC_M;s4sgvCAlE=dKwhpT?o{ z?Y~d_dm ze5u>6BKP0B_6vq~6Oo$I(0?Z_>dv}8|9@W_dOd^c^O&_>ndIVsr{HRnIB{YDH_~G` zU0p7j8|1F`YI21MZNk4d=-N>9+^Oaz_w18OqP}rDtZ5vN+#bSP6{#t6cu67`n``qt1p~*Qo!7w~h2CS#dcvpk;9r#uy7=dqsg|me2F~|SI$WG{tBI18!h)AhM~V9)fU@t5=fS+Dd4}}< z_e_iWq@@51PoH=kwf5;$6Vg*Anq|Rfgik+1Ha3U4(HYN;Ld(#=ZyKLUHl(1Sx(vGK z2!F3<7=P8p5+IZ-nr~RSuj9Qtjez}?7ouqm?f82HZZfu;%UjpA!^uKZ=Di%88};ZB zz_?sIJ=J)hI2afhXaN1I{#~jEnhxK6@vvNReufBMM{sa(%#f#1i&LC!^^QhPLqb~a zi`%@;qyRu$?o1=((Vgnd+f0^brh9S#NeqhxfYB^@Ch(yIUuW+Mzqa`jdbsfNJ^S)7XMoW5B#Q4SLnjwyp=<3<5N$@8_;o{fXe_B!M%fNa>(Vn*Dx z+;wjN9PG5xDBV{wXTN}eO!LC2sXBz0z!HTgJh-Z=N`kG+4_J;0d)U&XYyq`|+o#Fg z!n`~u_Fpu1@I5-iqvqcmKeMcIDl01|mG++O2QpAtCw5JT9j;Cj`Y9o@a&oKYg=t`a zC=eWmo|@y3ssF&TY`~O!xFmH{LtP{07qCUw$ZEmVsk$P z_NAlrVxlfR%^U|1JB$6%q1~Eqi860;>4vsc^!GQTkgC>2m%ijRf>^alVf~Mt)uvvvTah0Ut1e{R71)aM?qvu4rm zC1;);PEY`D@V749mR9Pxb3`yF2SQBp~&)oPTrXyX6tm=x>O=0`~v(ELql**5uu)k@CfGPE-UppDh zpuMwYpux9IA9Ir7B(;;9;g*uU=`Z6JP*PDD$B|ZBP$2aAp8q9qzb<=Acw+&845~(4 z20xWhr1LCLxl_vL$9DJ}{di;z=wSJL=OZdjl!u@pXTetb_8qtNKwe@Mxjwop71p;@ zRwA@YDyq1iU?PJ-b7$N0Q?+*Ao+?;{AMdChcR#l$3>p`1li-a*(X?zqSVPW~3NGzX z6Zrqf5J^(d|Jor!vvZdfqs88C9Pqa~u+&ia)^@4LhcYuzW93K#*R%O7fDK`(;vv&Ev!=UA8oN$D?j`X^6hQ&&h{ zT67h*fJ68J@*e-rXU|j|q-TW>vwFQdN#em>vXh!{cAQnR6QUnAi zeH+TDznhXRTn~C5gbpeNWFXeCKuw$H9*ToJ(zD)`%t%k%l#*%klYt&OaW7?D#xUq_ z7L_SesUT=TEHUO)(>o&35nC7C+kce zEI7FwDMq>%!+!cq*Ru@&kj?zpyR=B(^M!wWhB8E`_5NSWX$2DfS6d&i=V42;woy!0 z7;tK-1tlJwYy5gdL9mSaHl%uLW#dPcBRBu| zHPw1|y{O>)lY7vXE~zA)Q@`S8u0t>cuf0mRpjc#PNvTCL;6HzF$=i8WG(ibAQRR9Z zV8r$r_aNta7Djdl`S|8Rnh?6FzT#}8o?!3qB)(@ubmC`r$#yA6$joCm9$sC6T#M}3s3ayyd{kiEmswwve(ChJ5mGBZzSbV@ z$+@mkpd4`=?;wEj$B2ZO+~Pce35^q1HmJ=Vv%f5%_6xa*)Skg+{d)4Q8S#aSsVG<^ z`t9J34Y}-G_~>2k_GNnLBE2EpfAc=X=3SYl^4ZKMT&W@H*S*MgDAw!jf5>EX5e34T zlgXc>&S4(eS$v+_i@36m-xx=!V5IC=zMvJ`L}lJLzM5%akA+@@vvud@zXS zAB-$@Jp5|kc%njCDuH}g_99Si-Q?pycq0D05EJAgrf5Z|A~eDllKIiggB_JLw7Yn& z>{x8wGC{U8!9gEIyt7K+tCbkm?fxa=5?AP|5;emUfmgqmVYsG5;E2y+)7!T|IY(-J z=JFy}OLCuugap5ocFanXr5&{d-{FnWofOzxXY1 z<=>YhIJ}j-6`Esd35)gLu2Ch+zx0JgQn~){zFBc>aa4@jv9q;OI7eAINC&xOYpmx4%@M2ZT4;j_+c$2+f8r`h=9`K*HWcL2Q8Y?sVAo|W4ZfnmN|ny>aPx^F{(NtVsW)L ziZ4eq&sjzCUZ4*v&9AVm(x)ZxZ)I)}C7v35R7*Xu9Ky>DFFrUBR|}L2xiD}qkBg9X zl-VYfx;HP>ekc=Gjt<^XAXXqE(AXAq<~N}aALip5K9IG16S8p;!Z5Wxi(76yUn`>2 zVep3=dVd$fF#0k;ipOE^?nc?&`A28eUJ=!1Gky@sgGQIgW_kY(qDJG&_ApEtymRW^ z^cbe4pd7mlrghA&_}9u4kI^u>E># z@TUwV^aw|Gk(~Djk;&)!i_xFIH`Ic(($wA(8$@O501K(+x&0%1HbB87e!Fk$1%T5J zrB-zVW~Ejx7j2%*wY*z@ocUum&1Rw}}&iWz7!Zxj%1Ec>CLC z>SNBhikgaGq3x}8buWYY@R7qqZ{KF!tth3ClDs+M`#<;s9hGCI&kVNbEeaP61ka?> zSD)#LD^qah?!WcbccH7hYp)?u%-rmNQ%d-=OI*KBU z+M|FLYyiI#o%3GisKT6=T}zJF*3h*h{SyHKasE&tU?{GYINE}oj(>MGq<6TPFp=qc zEw1)t$cwm8h>fO~$(T!yDsw^*3eQ|+`*_4ippYgDo5GJV5iFDGLbe`Q)`brlhtN_H zaRA}O-M|^ zq-nft%HU7>%FOKGpVd@Te}QJ|`jvD->Zt9y+`Gi1Pq*Ka@6;6eA5HztZ%#*e*wP}@Nt#pag&6V#iyV{(Bd zqr~Hd)ECP&Esgv-d5p#j6mG|m(G`4Krvt2c&^P@2fvTDBwPsti5=)HgOT4YGs9GKS z>Ku`q5GIa^|J5y)fP8^hHYv@%I;)2>+*hp;h%LESYT=eJ>C@>@a<_27A$UIAEbLy+ ziZ^$r%82fzZXd1)FV^O|n@CH1l~sI@^*b4ZVMTc^utv4oZhM=<*?UYsrUlOn5zA)DpovqPiAqN#++v+6-^vPwrX(t{gS zjyN-s8r~-EXV$I_69=Z@tt~j>C*Q`2$5a@t&>IVu4HX{SBPU;ng%vJ5dv#?x%}gT3 z6<-xOlUsrKW%O?9Wc!4>Y!`OucC=TDc=jt_^w^Y z!>F*1_M1Y@^qqmgrr3TDusR`YzKyv@6Z&ZfOW<5|81mFDTzq`=DUbf1QW(UW??f<| zqhT+YL;L)H1vQ-ltKva!U1~RXEUV>%QqeTFhZT~|#BuK#M2V5-0%Jt*s4y&bhBLe* z7tT+~)R9rR>gr<}TqY2*`gi1Cx>$RW|TvP-2@dmu+uRp|dA&yHuc#X*Sce9U!6Xw7|c-L2nZH2tl0N=Ye+acrb3rX3-9k~_17$3_K;81vQwp+y(RPOU5kqIAhgr(vk1B< zp|tK%wr})b$$2-Up!LifRr!7JkcX*vQ@V0IVhVyJI&TKWS7Zri21^eUXA%nFwa=!7 zlDQ-n@(Tszx)QJi^5MhLVdFLCFqPri?3VtvBg+C@xiu`i+dCW1YL zNr&h*`?jUO#xT?}a;tEvc=s-*?}pXZxghmQ%bY)5Ze@9H44VXtfx(z>^gC4Q4m;eY z^5-)R7yqQnT1Vm!{U8knVurhykA~=6p6xeJKP9Sk_r@iry(OyjasJpp;9NoXm2K#o zzbS0anC&Fw?xnXj&S(Uj+esMZ`e$X4fmhY`18JM1mhl7unFGm0 z8BX;g!i{bD?(}pkY{qY~bO!6>ADclTvn8G(5(88wPL_YtKUSU(-%`n&-Pk8|vKeFt zj9?xQICjc~y|ta%V+=~YUx39KOx*S=IB4DIj^IjFk)*F8Qic6~Knw#mbmmYu#I}6% zRvv9jlm+=P>0^M2+2M|IT*gOKo+qQXo+sytEUq~15xXE2N>zMnNsU!Argbp8|k^T&%Is~6pnfwS(-w%``Y%X>tD|>x-JN|I1Acx zZYUJLAf)b06H-m;2KTIv-Yr@Xh+k||f9xqX=WH(>_XYd=Hv0~I@oO;##Pa)eM7z~8&__;sp2BBGUI$@n&#+2P=P){L~Wz!w@Zlc2IbM)DMVZ-Y(r<> zew%w={>_XW>fO@EiDLe zEP3(*rfcCxw3k$UR}VYQC$5`}^FF|i%@71DW-;+Ox;ZIiAy2oskM#GN2*eR8m_zBD zSI~lE7P8B&##(nZX4a*s@Hj)Xj&aufGj-;p-SEuiD4#yp`VH!V7X)f9*g{$=CEm^i%O0GFkG#gdvbWiD2|+AIugm#a(}by&3azUb@uI@f?`HB> z4CaX&pWx@4llHa<_;l%Q7Zu0(AoZr>iR$u083P%*p#q)un-?8gJ|Bn>i~taLAhpg$ zoF&+9yz0;%vRv_3c?{b;;~veI?{8c6p2Jh?xLH@j=fh&p6C&w@`Oya8FU?%)pAB-!l-bmuWUq8% zJbOerKq6zuLoDpajMn;i6p@?7)at6Y?^IxI zT2M+!BxriT1i=!ITL$Eex*1Wnxt~;;4Pm* zK13eE&HA;SI6x;-8M(yK*@onq7tgu9dBeV~*pewo@802~u9jSPu(Pbd@=)5On#ceb ziSd2m@*&D?siAZ(lAwt!Yc)XaD`V18-+f{|bwN^riclr{O_I@1w6G(w)blrYrX4O9$xd74J)bK=^QnWaQIWg^8Wq4thalG2dX8aL7~ArmsJj}?*kZz z5YoT4ha27)Ig5@fe8@lTLK^tb-o=-zniv%6pJ;8S97gcYJD1K#wM*Zm1iGwU0ye=R!`P0rno_7;zC}%95TL}of4rDsS#m77ETp**UMIzHomOWhSKUK zUW?LOHOxX7i5gM?Em^M^t}t_B>#QY#!LmsCG8sSaXD#m)1}7C<&aaDwy!$B`NmW7ZAybY-Nk&}E^KYh|l9V~8 z9i?;n+{ZM{Q7#-|lz%sW)};3TtTFm3W`9kzYAXk0250_{1mgfPa8_EU#CUv?ccB@l z36TkU?25yoLiutc9G{brLOs*l|NGaU6qLb%m zCEYc_hZ3cR)lc!qoOWMAn7yR->JfA8c`%vV?@vKUBYQOE3B}`t00=L|9v%o)j!r0v zI&ixurVag8O?kYZypd(9rt*!>?NnFN3sJpXWBY`_k}m4OK~#65l)l<2tC^iOmDY+P z4Rj%csM#O5$xC3OpM??3kWAqVsA}u=mu7*V8D^VWZSLNC1Pr29Z|8buOMLPPcQ)ko2){@*G#%<)2 z%Ff<@b{8B|ScioaFy&`LqDAae073_L!PK#gVhuV&R_ln1N2PviqMXV>z7Qm})Bunz zFDset8JihA$}2+Q>7?R`bmuM7{bOVVg}GxL>s5D_o-gTOtT2iKdM2j}cYIwbh;hf{ zppL?*%GzUNOe*+EP_N_X3Wy>tf#N?r@D(v0m=mcr`_AX5e1m*8H;4^+2*VS2%d=Q0 z5gI~kwf96zkSR4T|MGp166le5P@Tp>0jgKsB{7$AQ-4A2XE!MycYS2z)k|%O5t=mW z#@K1Mq-qH9ObB$!1j~&aqVl8Q%_U`fKrv=1z{&@DJZYX{uzhuIJu6kTw6t!mQYsS5 zY?`-nFHCZ-gkEQ?{jZsN@a~az9mAAXf|L%3YB-*Qb}78f9UEo6JDH*Hm&S)^Jl zVht#p3V^7C9MdF7DBNR@66ND-0LkG%6#Ml%EzcfAdO>Z;YU~@Y#2@6Q8nj z83VEpip=_y_RL|*bWhAcWv>mVf@esG6&W)l z(zuvgQFY(aEYDDAJru*|y%%d@Cv!&F<@4oCG~OPLhDcRLQwpDWZ~g&n<|OQ>@C86^ ztmInP#V}*`x36ZTR+AHyys5ouYX=BzLMAWb@;T-eZRXR6)8M$4ES-AY%@e&CD9~)! zU$?uzgWP466I-;Nv^`tKT3WE+b*>I`kicniwM-pC&I95mY zmwHM<$Z;3Q4Tf70o>nFq;EOz?3=(XhJw?V}p5t2{D|xpWDnC}n-Ib{{fj+$?{4rCN zolHPq?uU;`QQBFgMK94VZF#WHyYGIx0H2J~eY#nl!$eg1bwuJ*Yt%bJ8$yz54@3AN zH1GkeH4OnXF=(I^Zq8}6hm-D!zXL4t14IZQ%&T(e)S*WgY6sNmrnUuTMiOL)tKGf= zn%$za6C7sTi`Zc600Vrp*izj)^0ikZV#L3kkN7~QWz-~3Z70zT+Z#r`!vwWoHhh1> zWWe+p(nCz>ChDuD)3Zy-$iKv|PRSoaE!W0st)W}(11yx?2xC7b(spY-?))b@Xuu7>VS2bjIfmRl0Ec!p## zX|bak5?xM9O`Nb%Nb&&|{Mn@^95{ce;9VA3g;~;~J{IbP^W=qzxO}!>o2pv|^-_(r z$x zP@H#FB0MJ7UM}|3xeY!_e~_CL6&1Zl`*gJuTHml2Lb0fl=%=SKXp*AVH1?k#Gt)f= z6~gvg_R-^gNzIxGGD`QMR@~fEq zzYUhCxo|Q(^y-`JKUEO|(P2rTK|ho?c>a~pF66F0Hxv8&>gsCpXO|R*m1^j{`Mss2 zu3*17o+F(wpjxIz-T_rJCz~YZ`5n4Ca5pKM&otI#Wz8jrsI_Dp3acbqf0gMk%y&5l z3W3EUwj$$@*4JBdKjq~uz)-EA?iQ?N8Fto4u&}+o{Wu+QXG0xfi(gyw?7b$4ykJ#m zS`Qcy44VDKJHQ^YYWDA?AujTx>0y<4XU5zidI5ctm&j1E+1S+yz%UIbVvEd5CYI6; zJ&kwc|KK!k>tq?$s=dx0%oYyPV%EGk8%{hp z<59GnVWx#WpFt0OveA71rXZ`Q@%6d6E`Oi+1YSA&w8czpXlUrMb08qTV%x6?Grli> z2uBzj802=TqIh}7|u<54cpT$jK7gqPTa0>K>)oHRGH(I?k9pixf zXDxA9EWP@>|#q80)z_z!6zG#b7H@qd*8vwPA z$(%$8KX?La^}_7gnlI(7=h6wgr0~rvHjC_hbshK$*mfGm26COLFZIrm=yw07tz;r;fXTKwLsvO z=*Z)*GM`h3b$-jo=WyxlQ_KasP|#L|3iKkVbw0oV4h^o|XC;lq^yY^h&c2;bxCSEC zbAY#335ZOT_mjI9Y^=pu%d`PeU5cKIHW{(jgrpELxe0nj1zES6g_uXW5|7`|JYb6= zo=KzmH8m9iCt~yBGM7hti1g#2(FIur?v|Pb(LGE3_1eR=YgeV zDKy3(RFxSDH>wX_cQSVI@|r~}?Y+Nr*0Kko>}TLRYW|IeSlT7W1|D(z;s=#b_BtR#8Zhwdh+WyM>!@BHTn;gi__yum8aZUXR*9etfu(k8@^ zt0Pzmw<}W#>Rv6tW5N$UEdTkFY%ZGUN!AQ>bGS3Z=bxWQXpkJJK0%`l;tJm62#Y$h zqrIQo*HSB9YVukH&9c_&mrG07Ukr7C`!W$66y!-h!=rh@cf=I}VW82ub;GpZ$)W?s zX3y5X-4w0Dt=Z3$*agbAT6-)$wvl;Kc^c1H{txj;NGdH}y zAgBNH6K9bH$9VLsr?l+DbLx*pMQ#~L33AF}7NEfbAVg}bM(?@rbq+jfKTmn{_j*Cg zj3{LK{V-2>!LX>h5s2P|MoX=OCD(h49hM|B(JkbPJw0k^e&QPCKG^n-H*NFNeRX)c z{3CEb)amtr?wGjPt|<1N-XyF_x%`80W?xJ|VPHw2D3-EBKh+k%nB9#ZBc!cz`gK)7x)9 z0^xaI|FM`^@uQU%ux$$T3MkN+VojdfkuB;F)HA|BQB{jzl~d~oiLC~PKmg3-IS10O43p2qmFcYy>VLCXq)LH&njvx3rRFpTq3@FuH{BrTj9x&6*RE%m;q-atjY~^HSLx3Sg%qxZg z)p`-WONVj9JkJ7OuR>!>6TCi^%N1?#@D&V#PBh;ap@|>0e7NCnf1fIvaFIk4dQx-F$Q&UHGfN2C=X;2QgiyOM81qgm7pgR;K zU|uN6of6Q->yV*9yOsb_Hk)Su9DJ7xqvXRyR0DUQcwpZWX!Y+>jg*8^WT^7%>@!Bp z+~>H?b3G7ETvMowd00+Z0qBLuM{2fBVbESATy6aUZYn3wSj1eI+2*2Ve>YY!O#TZf z5_14V#eG9`YBlIv;nG*v+T*?qVvTD>iDHSm58}`OU^wB=v}_W-0!J*hL}M27s@F7RIlD{(e$1Sd)mWxG7SM(3x18-o0=QROYe#d5I+q18jO= z$R0TQzL;Fa7z2(2ArxhVVoavo+tJsKw}B?u?jIScR`-@&2c%lvyn#~oqI5(7>}Sj6 z8w^KR##P39P8xtmZ=5x@+KJsHxg1O6cRpNAf8oUgoD{6Pc<@g5PVp_H$0f)T@?Fl(q4OZ5cUO}d8&$-0WzM~V@A`dANL$WK zNAN0CGX2p!bL%+RsRX9DkL=`ZBjWgAr$N9O zSl`Y?&sTSVi=__jjw|>;vu;yOzpI*X=El0|rlK=RufDqW^o@3S0rZ2pk{MYTMI)`h zT>rh|7oUD8)u>JX9!8hKKLhVljra>hUmiCY6425)v&;9JLjtjjy{X+f^%)%QDlTw; z-F;txT&Bx)l^F0utHOd9C}44PL&IusEJp(T{XKa;fgph(y^huGh+C}R#NAs`^M-Fe zvbMIap}W$a0^EVMwAP*kSsiPVId1^w8}bBHBT3QBn&gd@ijaY?CL_o5yNGf;FP-~Q z1vXaBo~@LMRS9Q#$U6&)u=#@A`ofjd+#vRwe|lE%Vb^EZ(h!Ka=gwo{>R3fj-@dst z&J|oz^GU0Q$SjEh4m`s~WE1F0A#Ml#!l=$smTh{o)(PAGE|y-dJrbBt*gj3bod``q zQsqyazvy}ZEI6R#W?$W#NN30)2m)Bz9{Ok_Xlb88FG4;b$BV#2)qa(=|1JK#kuUsO zbflC9^zhewf_K#;7JWyod* zo3&HKRrAY9NRTIT$K?7iiDKAD_%*c=^R&vZWn&&nIPA9E4fsf$ikd1b?jpO`U(nnx zB2%03K2L{F)rp?Vl8lgf3NrYs$5icxIP9(fUw&fUJ~$Xk#Vk7i+1QEpuZT@>}7CM7RN$O#eyHYC9D94J_mWMIxd%5UT3fZ zP?&~rnm!2`IH@TL1cZ;UhzEejtxP5Q05}e`tzVcPkuQ4A0=7 zzZ$Hi9mXKjDHu977*S2bT~{qC96<-NJ!jxX1-90i0e8Q?z5RB?^nvrcxKW|lwwqaB zCYfm=aQRA7NpL1`t{zi_MJ}3?-2iU!!!J6c3<xA#iSKpae|ev21FTh+C@y`8q2HYu^hR!k}eIIBXY;=u2QAN~@s zS({}E#Z4iKxX(Yu2%%=7^NOX4`%Yaqz!VY=b(F7z$y#eXd8fTd%7B2fXb04{zB=;g z_4;L#Ng;vwhi1(Yh)QUWyIGaYlEd`$bmE1$S@P&r;6=m$u)r4d=BZpLlcG^)2DS?b z>ctUDlza@d0KQu#@f&D%@sO)yL2h=}#my}>Lr?fxz+|<@X*tx%RC@Vkt$V`8`@bSLOiSYYZ?jLPc7?HVgnl1)ZHhQj4HDC>LXP*`U zq^dxy=_(N0YiFMx_pY#P$5fDIx*zd8;g7+3QQcUlShs<>A^Os<4{)P;JkLJ0WAP?| zEJ^^SLXKyH%4M4js{y3|UhhieG{9}zAS9q_0;mKzLi4pM6`BwL$4I^xuZrtlQ2U#9 ze&fwvlGjQ=>bM6=4&30;=c&ML^8l*LoD5iS2jJ^XjgRkR2J-GWdjRP&#Z76YA-qaQ zR^;x3sG6G~x}GHb+RRL}@=<>_If&I~>BT8*Zs4bM!#jkm>9vfu=g#5OCvJ>TxSdU`f9DvtGlf@zo?TKM%zNk|4bgj}Tc8H&C-M0{Q5 zycErzUthWxTnie2{qO-=JE*i5ZCv=c7Z{{4jWfX4kUaMkLkY?xz~;5kZ8$J>CLGAV z(z8aaSFwicH$$`qxu?n|-lQQ`;UkcvtnYJ_cEn3(;mC1++|)^59XHD0Fg-j2QQGJ~ z3EJTF!mHgVY-B3jlaPsSWgH|lCK9o>t{4;kw#6tw0GE1Z#RU!;&cyuK`>=nLEeicM z`%h19Pl(+$Z|j!53aaC44nV{EgYJUc|3?7R0xbPGJ9g|i-8pwoLqo&+!Z2*vv17-U zAPBB%XlQux>eZ{)Rr`E~;Bw$g!0|xEq$vo3Y52sao7B30i8C>n8>7!LB6mD6#aN&}s zrlxbv?89LgeiPW@oO|5NE?Tf)!BHDFZ1_0-d+`{)=Ioh45ZqGjO$R}6Jb;<~Xb_)~ zh=_;`MHq%F!!WdbK7Z;!W!u}^UlfMn!(kX&dwYA;emg@kM*WTm!|`$Ek1Dn{r5kSbvz=&p`=Mf zCbU(nR&4=Z3s?{YzfVT}Rn*?zz9lt*v{7VR%zMpMPG~v5ba%KEF>spT9Sm dY*VD}{eMg*0YrT(d>{Y-002ovPDHLkV1j3l?=b)X literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurve.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurve.png new file mode 100644 index 0000000000000000000000000000000000000000..5c395170128f03c37be917ccc56cde0c1e481549 GIT binary patch literal 59181 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 be8(1Fam^hKQen<61iVh-tg)1T+;9FLv&^#* literal 0 HcmV?d00001 diff --git a/3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurveSimple.png b/3rdparty/rbdl/doc/images/fig_MuscleAddon_TorqueMuscleFunctionFactory_TendonTorqueAngleCurveSimple.png new file mode 100644 index 0000000000000000000000000000000000000000..696cd0ea62f16489acdf1fdc90a528525e9729ae GIT binary patch literal 38669 zcmX_n2RNJG`@XHjs8V~hwwgtaS{-V)R*{bxyK2^my{n3%c5ACu1WE0kP^)Sev4f&! zv_`D(f2;rR&vixQ^`7%S>pbT^&;2Au@0mI^B`YNX0Rgq9hN=Mp0g*27vbshFTydb2 zJOtiIydG&jzXtpSU9)=&e5UZw_|J=gfV1=Oi_mSvM;W-t;{62dZRqac?Q896PvGn8 zE9T_p>}6~1VK3(H>6o>v$VxzPi$GKL;d8(2?RozX>_*eL-`I_hum{lk_pe8kG|^A4 zzal49h3d)B-F)~k9sV|vO}YA&x+>#;&sCoQzk{w{r_=Lrq$eff{h0guI+0cw$u(X( zhoQ4xizg;i&ktpk=EtQvhJ6O_%^Q_gJToyenfIypeB0+CN%(=`T$J=;c5eG^>Y)^d z7^wqs^{N>YA6;B9M22Q&<*3?*DTT`tvXNsm2s(&&6N@>>v&n)g!%x_x&1~VNo zgcB`6hHWv){NeB}yoRD)9JyOmMb1c25$VK6=tyKnj%6ap=7AOxNgW8eh!!|?X1yUb z=1fe)p#%v%2?GUoAeX!4j8;rEVmmPj;~s8%Ly7#O(<=;wqGilOIIBd3fE~ zqs81rl82r>-;*bwBT=Mz#|odL*5&Fw>XOwE_g9MUyf}RrV;_81!^=QPN)B0~f9%Hf z($jKtRX(2{En3HhSfyvPRr@#63Sm%cT(!dWu7p6B$O={O zm7LqRwQytLJ_yV#Qe#9_C-ck|jor{jjte;j*kH$xoS2{{^IB(Kf^3Ot$mrtEE&b`i zb;v#Rj9f6SoQz+vqzB=~`qvRDFa@TM@Vkk&-rf&p6(s9i_&li<808os6ugu^ISUVyc^VE708otKc6*1aMjzn9si;ZodK@gCN4E!(FB?XITxN2BUL z$`rMq)i7=G<_FK18^YgQOc7m{jMO_wdpxL-yr;^pUSNVFM$+FCS)n5frClK`#*`sK z$jg^<0ZF z@rz%`W*1b7n#oqUkh=!HsD#2pI0`S;U2b5RSR6_|na&;Hk`v`4&XssiYyM_SqhfR& z@j2b3SE+F*=wfq2J86ohXbxPd^{fwXlr48Z z!?x_c3@jDX0oKELQO@EU&}hZWYYSZAJCT~`5$cpMcS2e9^fL#{XK-c*ah`5}Z{ICE z1tMaO zVZDvh-#v4AyOS|5L?s<4K3V0%v`pWjBe@99@PQCZAMN+pnR|;ZoYcw$a`uUut@jrf>byF=zxOUu z8;M2S3MOLw5yC8dTY>*eRc!%fkIfm@e)|*4!pVIY9oAsGsKtNV>&&{he>};DJ)Sh# z*H#u}i@@;A8Pnd~a|O#J{OmPsrPccQI>#@si|7*_>Of{Yuo4+D=67?E37(ah!Lv2T z-tzE-9V+!j=0k`^XMk>(+v{{<0h?YF4e|*%zlQFp

Gr4)a?|H)sT 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