diff --git a/.github/ci/packages.apt b/.github/ci/packages.apt
index 8bcba178ad..c8809eb33d 100644
--- a/.github/ci/packages.apt
+++ b/.github/ci/packages.apt
@@ -23,6 +23,7 @@ libsdformat15-dev
libtinyxml2-dev
libxi-dev
libxmu-dev
+libpython3-dev
python3-distutils
python3-gz-math8
python3-gz-msgs11
diff --git a/examples/standalone/marker/marker.cc b/examples/standalone/marker/marker.cc
index 5cf7e725ba..68334cffc3 100644
--- a/examples/standalone/marker/marker.cc
+++ b/examples/standalone/marker/marker.cc
@@ -172,7 +172,7 @@ int main(int _argc, char **_argv)
gz::msgs::Set(markerMsg.add_point(),
gz::math::Vector3d(0, 0, 0.05));
double radius = 2;
- for (double t = 0; t <= M_PI; t+= 0.01)
+ for (double t = 0; t <= GZ_PI; t+= 0.01)
{
gz::msgs::Set(markerMsg.add_point(),
gz::math::Vector3d(radius * cos(t), radius * sin(t), 0.05));
diff --git a/examples/worlds/CMakeLists.txt b/examples/worlds/CMakeLists.txt
index fee3a9e41c..258ee1529e 100644
--- a/examples/worlds/CMakeLists.txt
+++ b/examples/worlds/CMakeLists.txt
@@ -2,4 +2,8 @@ file(GLOB files "*.sdf")
install(FILES ${files}
DESTINATION ${GZ_DATA_INSTALL_DIR}/worlds)
+file(GLOB csv_files "*.csv")
+install(FILES ${csv_files}
+ DESTINATION ${GZ_DATA_INSTALL_DIR}/worlds)
+
add_subdirectory(thumbnails)
diff --git a/examples/worlds/advanced_lift_drag_system.sdf b/examples/worlds/advanced_lift_drag_system.sdf
new file mode 100644
index 0000000000..8b21318720
--- /dev/null
+++ b/examples/worlds/advanced_lift_drag_system.sdf
@@ -0,0 +1,686 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ 0 0 10 0 0 0
+ 1 1 1 1
+ 0.5 0.5 0.5 1
+
+ 1000
+ 0.9
+ 0.01
+ 0.001
+
+ -0.5 0.1 -0.9
+
+
+
+ true
+
+
+
+
+ 0 0 1
+ 100 100
+
+
+
+
+
+
+ 0 0 1
+ 100 100
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+
+
+
+ 0 0 0.246 0 0 0
+
+ 0 0 0 0 0 0
+
+ 0 0 0 0 0 0
+ 1
+
+ 0.197563
+ 0
+ 0
+ 0.1458929
+ 0
+ 0.1477
+
+
+
+ 0 0 -0.07 0 0 0
+
+
+ 0.47 0.47 0.11
+
+
+
+
+
+ 10
+ 0.01
+
+
+
+
+
+
+
+
+ 0.07 0 -0.08 0 0 0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/body.dae
+
+
+
+ .175 .175 .175 1.0
+ .175 .175 .175 1.0
+
+
+ 1
+
+ 0
+
+
+ 1
+ 250
+
+
+
+ 1
+ 50
+
+
+
+ 0
+ 0.01
+
+
+
+
+
+
+
+ 0.3 0 0.0 0 1.57 0
+
+ 0 0 0 0 0 0
+ 0.005
+
+ 9.75e-07
+ 0
+ 0
+ 0.000166704
+ 0
+ 0.000167604
+
+
+
+ 0.0 0 0.0 0 0 0
+
+
+ 0.005
+ 0.1
+
+
+
+
+
+
+
+
+
+
+
+
+ 0 0 -0.09 0 0 0
+
+
+ 1 1 1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/iris_prop_ccw.dae
+
+
+
+ .175 .175 .175 1.0
+ .175 .175 .175 1.0
+
+
+ 1
+
+ 0
+
+
+
+ rotor_puller
+ base_link
+
+ 1 0 0
+
+ -1e+16
+ 1e+16
+
+
+ 0
+ 0
+
+ 1
+
+
+
+
+
+ 0.00000001
+
+ 0.000001
+ 0.0
+ 0.000001
+ 0.0
+ 0.0
+ 0.000001
+
+ 0 0.3 0 0.00 0 0.0
+
+
+ 0.07 0.0 -0.08 0.00 0 0.0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/left_aileron.dae
+
+
+
+ 1 0 0 1.0
+ 1 0 0 1.0
+
+
+
+
+
+
+ 0.00000001
+
+ 0.000001
+ 0.0
+ 0.000001
+ 0.0
+ 0.0
+ 0.000001
+
+ 0 -0.3 0 0.00 0 0.0
+
+
+ 0.07 0.0 -0.08 0.00 0 0.0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/right_aileron.dae
+
+
+
+ 1 0 0 1.0
+ 1 0 0 1.0
+
+
+
+
+
+
+ 0.00000001
+
+ 0.000001
+ 0.0
+ 0.000001
+ 0.0
+ 0.0
+ 0.000001
+
+ 0 0.15 0 0.00 0 0.0
+
+
+ 0.07 0.0 -0.08 0.00 0 0.0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/left_flap.dae
+
+
+
+ 1 0 0 1.0
+ 1 0 0 1.0
+
+
+
+
+
+
+ 0.00000001
+
+ 0.000001
+ 0.0
+ 0.000001
+ 0.0
+ 0.0
+ 0.000001
+
+ 0 -0.15 0 0.00 0 0.0
+
+
+ 0.07 0.0 -0.08 0.00 0 0.0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/right_flap.dae
+
+
+
+ 1 0 0 1.0
+ 1 0 0 1.0
+
+
+
+
+
+
+ 0.00000001
+
+ 0.000001
+ 0.0
+ 0.000001
+ 0.0
+ 0.0
+ 0.000001
+
+ -0.5 0 0 0.00 0 0.0
+
+
+ 0.07 0.0 -0.08 0.00 0 0.0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/elevators.dae
+
+
+
+ 1 0 0 1.0
+ 1 0 0 1.0
+
+
+
+
+
+
+ 0.00000001
+
+ 0.000001
+ 0.0
+ 0.000001
+ 0.0
+ 0.0
+ 0.000001
+
+ -0.5 0 0.05 0 0 0
+
+
+ 0.07 0.0 -0.08 0.00 0 0.0
+
+
+ 0.1 0.1 0.1
+ https://fuel.gazebosim.org/1.0/frede791/models/Advanced%20Plane/tip/files/meshes/rudder.dae
+
+
+
+ 1 0 0 1.0
+ 1 0 0 1.0
+
+
+
+
+
+ base_link
+ left_elevon
+ -0.07 0.4 0.08 0.00 0 0.0
+
+ 0 1 0
+
+
+ -0.53
+ 0.53
+
+
+ 1.000
+
+
+
+
+ 1
+
+
+
+
+
+ servo_0
+ servo_0
+ 10
+ 0
+ 0
+
+
+
+ base_link
+ right_elevon
+ -0.07 -0.4 0.08 0.00 0 0.0
+
+ 0 1 0
+
+
+ -0.53
+ 0.53
+
+
+ 1.000
+
+
+
+
+ 1
+
+
+
+
+
+ servo_1
+ servo_1
+ 10
+ 0
+ 0
+
+
+
+ base_link
+ left_flap
+ -0.07 0.2 0.08 0.00 0 0.0
+
+ 0 1 0
+
+
+ -0.53
+ 0.53
+
+
+ 1.000
+
+
+
+
+ 1
+
+
+
+
+
+ servo_4
+ servo_4
+ 10
+ 0
+ 0
+
+
+
+ base_link
+ right_flap
+ -0.07 -0.2 0.08 0.00 0 0.0
+
+ 0 1 0
+
+
+ -0.53
+ 0.53
+
+
+ 1.000
+
+
+
+
+ 1
+
+
+
+
+
+ servo_5
+ servo_5
+ 10
+ 0
+ 0
+
+
+
+ base_link
+ elevator
+ -0.5 0 0 0 0 0
+
+ 0 1 0
+
+
+ -0.53
+ 0.53
+
+
+ 1.000
+
+
+
+
+ 1
+
+
+
+
+
+ servo_2
+ servo_2
+ 10
+ 0
+ 0
+
+
+
+ base_link
+ rudder
+ -0.5 0 0.05 0.00 0 0.0
+
+ 0 0 1
+
+
+ -0.53
+ 0.53
+
+
+ 1.000
+
+
+
+
+ 1
+
+
+
+
+
+ servo_3
+ servo_3
+ 10
+ 0
+ 0
+
+
+
+
+
+
+ 0.0
+ 0.15188
+ 6.5
+ 0.97
+ 5.015
+ 0.029
+ 0.075
+ -0.463966
+ -0.258244
+ -0.039250
+ 0.100826
+ 0.0
+ 0.065861
+ 0.0
+ -0.487407
+ 0.0
+ -0.040416
+ 0.055166
+ 0.0
+ 7.971792
+ 0.0
+ -12.140140
+ 0.0
+ 0.0
+ 0.230299
+ 0.0
+ 0.078165
+ 0.0
+ -0.089947
+ 0.3391428111
+ -3.85
+ -0.9233984055
+ 0
+ -0.12 0.0 0.0
+ 0.34
+ 0.22
+ 1.2041
+ 1 0 0
+ 0 0 1
+ base_link
+ 4
+
+ servo_0
+ 0
+ 1
+ -0.000059
+ 0.000171
+ -0.011940
+ -0.003331
+ 0.001498
+ -0.000057
+
+
+ servo_1
+ 1
+ 1
+ -0.000059
+ -0.000171
+ -0.011940
+ 0.003331
+ 0.001498
+ 0.000057
+
+
+ servo_2
+ -1
+ 2
+ 0.000274
+ 0
+ 0.010696
+ 0.0
+ -0.025798
+ 0.0
+
+
+ servo_3
+ 1
+ 3
+ 0.0
+ -0.003913
+ 0.0
+ -0.000257
+ 0.0
+ 0.001613
+
+
+
+
+ rotor_puller_joint
+ rotor_puller
+ cw
+ 0.0125
+ 0.025
+ 3500
+ 8.54858e-06
+ 0.01
+ command/motor_speed
+ 0
+ 8.06428e-05
+ 1e-06
+ 10
+ velocity
+
+
+ servo_0
+
+
+
+
diff --git a/examples/worlds/elevator.sdf b/examples/worlds/elevator.sdf
index f50ee17c59..0cbcd8702e 100644
--- a/examples/worlds/elevator.sdf
+++ b/examples/worlds/elevator.sdf
@@ -10,6 +10,12 @@
gz topic -e -t /model/elevator/state
+ Note that when commanding the lift to the ground floor:
+
+ gz topic -t "/model/elevator/cmd" -m gz.msgs.Int32 -p "data: 0"
+
+ The output of the topic echo command will stop as protobuf does not
+ distinguish between the un-set value and zero for integer fields.
-->
diff --git a/examples/worlds/environmental_sensor.sdf b/examples/worlds/environmental_sensor.sdf
index e1947c8591..8ef8ed35f1 100644
--- a/examples/worlds/environmental_sensor.sdf
+++ b/examples/worlds/environmental_sensor.sdf
@@ -1,5 +1,12 @@
-
+
+
@@ -104,6 +112,7 @@
1
30
+ sensors/humidity
diff --git a/include/gz/sim/Link.hh b/include/gz/sim/Link.hh
index d67a17d4de..842e287025 100644
--- a/include/gz/sim/Link.hh
+++ b/include/gz/sim/Link.hh
@@ -22,6 +22,7 @@
#include
#include
+#include
#include
#include
#include
@@ -173,6 +174,13 @@ namespace gz
public: std::optional WorldPose(
const EntityComponentManager &_ecm) const;
+ /// \brief Get the world inertia of the link.
+ /// \param[in] _ecm Entity-component manager.
+ /// \return Inertia of the link pose in world frame or nullopt
+ /// if the link does not have the component components::Inertial.
+ public: std::optional WorldInertial(
+ const EntityComponentManager &_ecm) const;
+
/// \brief Get the world pose of the link inertia.
/// \param[in] _ecm Entity-component manager.
/// \return Inertial pose in world frame or nullopt if the
diff --git a/python/src/gz/sim/Link.cc b/python/src/gz/sim/Link.cc
index 51a6b15807..1c8bb8381d 100644
--- a/python/src/gz/sim/Link.cc
+++ b/python/src/gz/sim/Link.cc
@@ -79,6 +79,9 @@ void defineSimLink(py::object module)
.def("world_pose", &gz::sim::Link::WorldPose,
py::arg("ecm"),
"Get the pose of the link frame in the world coordinate frame.")
+ .def("world_inertial", &gz::sim::Link::WorldInertial,
+ py::arg("ecm"),
+ "Get the inertia of the link in the world frame.")
.def("world_inertial_pose", &gz::sim::Link::WorldInertialPose,
py::arg("ecm"),
"Get the world pose of the link inertia.")
diff --git a/python/test/link_TEST.py b/python/test/link_TEST.py
index 35071869e1..4d6be57631 100755
--- a/python/test/link_TEST.py
+++ b/python/test/link_TEST.py
@@ -18,7 +18,7 @@
from gz_test_deps.common import set_verbosity
from gz_test_deps.sim import K_NULL_ENTITY, TestFixture, Link, Model, World, world_entity
-from gz_test_deps.math import Matrix3d, Vector3d, Pose3d
+from gz_test_deps.math import Inertiald, Matrix3d, Vector3d, Pose3d
class TestModel(unittest.TestCase):
post_iterations = 0
@@ -59,6 +59,10 @@ def on_pre_udpate_cb(_info, _ecm):
# Visuals Test
self.assertNotEqual(K_NULL_ENTITY, link.visual_by_name(_ecm, 'visual_test'))
self.assertEqual(1, link.visual_count(_ecm))
+ # World Inertial Test
+ self.assertEqual(Pose3d(), link.world_inertial(_ecm).pose())
+ self.assertEqual(Matrix3d(1, 0, 0, 0, 1, 0, 0, 0, 1), link.world_inertial(_ecm).moi())
+ self.assertEqual(10.0, link.world_inertial(_ecm).mass_matrix().mass())
# World Inertial Pose Test.
self.assertEqual(Pose3d(), link.world_inertial_pose(_ecm))
# World Velocity Test
@@ -76,7 +80,7 @@ def on_pre_udpate_cb(_info, _ecm):
self.assertEqual(Vector3d(), link.world_linear_acceleration(_ecm))
self.assertEqual(Vector3d(), link.world_angular_acceleration(_ecm))
# World Inertia Matrix Test
- self.assertEqual(Matrix3d(1,0,0,0,1,0,0,0,1), link.world_inertia_matrix(_ecm))
+ self.assertEqual(Matrix3d(1, 0, 0, 0, 1, 0, 0, 0, 1), link.world_inertia_matrix(_ecm))
# World Kinetic Energy Test
self.assertEqual(0, link.world_kinetic_energy(_ecm))
link.enable_velocity_checks(_ecm, False)
diff --git a/src/Link.cc b/src/Link.cc
index 426c57e780..4fef211baf 100644
--- a/src/Link.cc
+++ b/src/Link.cc
@@ -15,7 +15,6 @@
*
*/
-#include
#include
#include
#include
@@ -190,6 +189,23 @@ std::optional Link::WorldPose(
.value_or(sim::worldPose(this->dataPtr->id, _ecm));
}
+//////////////////////////////////////////////////
+std::optional Link::WorldInertial(
+ const EntityComponentManager &_ecm) const
+{
+ auto inertial = _ecm.Component(this->dataPtr->id);
+ auto worldPose = _ecm.ComponentData(this->dataPtr->id)
+ .value_or(sim::worldPose(this->dataPtr->id, _ecm));
+
+ if (!inertial)
+ return std::nullopt;
+
+ const math::Pose3d &comWorldPose =
+ worldPose * inertial->Data().Pose();
+ return std::make_optional(
+ math::Inertiald(inertial->Data().MassMatrix(), comWorldPose));
+}
+
//////////////////////////////////////////////////
std::optional Link::WorldInertialPose(
const EntityComponentManager &_ecm) const
diff --git a/src/SimulationRunner.cc b/src/SimulationRunner.cc
index 4f628a3906..4ee08be76a 100644
--- a/src/SimulationRunner.cc
+++ b/src/SimulationRunner.cc
@@ -80,6 +80,9 @@ struct MaybeGilScopedRelease
#else
struct MaybeGilScopedRelease
{
+ // The empty constructor is needed to avoid an "unused variable" warning
+ // when an instance of this class is used.
+ MaybeGilScopedRelease(){}
};
#endif
}
diff --git a/src/gui/plugins/environment_loader/EnvironmentLoader.cc b/src/gui/plugins/environment_loader/EnvironmentLoader.cc
index 6ac8b464cf..cb688b3a8a 100644
--- a/src/gui/plugins/environment_loader/EnvironmentLoader.cc
+++ b/src/gui/plugins/environment_loader/EnvironmentLoader.cc
@@ -19,10 +19,11 @@
#include
#include
-#include
#include
+#include
#include
+#include
#include
#include
@@ -33,6 +34,11 @@
#include
#include
+#include
+
+#include
+#include
+
using namespace gz;
using namespace sim;
@@ -42,6 +48,11 @@ namespace sim
{
inline namespace GZ_SIM_VERSION_NAMESPACE
{
+const char* preload_plugin_name{
+ "gz::sim::systems::EnvironmentPreload"};
+const char* preload_plugin_filename{
+ "gz-sim-environment-preload-system"};
+using Units = msgs::DataLoadPathOptions_DataAngularUnits;
/// \brief Private data class for EnvironmentLoader
class EnvironmentLoaderPrivate
{
@@ -65,7 +76,7 @@ class EnvironmentLoaderPrivate
public: int zIndex{-1};
/// \brief Index of data dimension to be used as units.
- public: QString unit;
+ public: QString unit{"radians"};
public: using ReferenceT = math::SphericalCoordinates::CoordinateType;
@@ -76,12 +87,12 @@ class EnvironmentLoaderPrivate
{QString("ecef"), math::SphericalCoordinates::ECEF}};
/// \brief Map of supported spatial units.
- public: const QMap
+ public: const QMap
unitMap{
{QString("degree"),
- components::EnvironmentalData::ReferenceUnits::DEGREES},
+ Units::DataLoadPathOptions_DataAngularUnits_DEGREES},
{QString("radians"),
- components::EnvironmentalData::ReferenceUnits::RADIANS}
+ Units::DataLoadPathOptions_DataAngularUnits_RADIANS}
};
/// \brief Spatial reference.
@@ -92,6 +103,12 @@ class EnvironmentLoaderPrivate
/// \brief Whether to attempt an environmental data load.
public: std::atomic needsLoad{false};
+
+ /// \brief Gz transport node
+ public: transport::Node node;
+
+ /// \brief publisher
+ public: std::optional pub{std::nullopt};
};
}
}
@@ -123,38 +140,54 @@ void EnvironmentLoader::LoadConfig(const tinyxml2::XMLElement *)
void EnvironmentLoader::Update(const UpdateInfo &,
EntityComponentManager &_ecm)
{
- if (this->dataPtr->needsLoad)
+ auto world = worldEntity(_ecm);
+
+ if (!this->dataPtr->pub.has_value())
{
- std::lock_guard lock(this->dataPtr->mutex);
- this->dataPtr->needsLoad = false;
-
- /// TODO(arjo): Handle the case where we are loading a file in windows.
- /// Should SDFormat files support going from windows <=> unix paths?
- std::ifstream dataFile(this->dataPtr->dataPath.toStdString());
- gzmsg << "Loading environmental data from "
- << this->dataPtr->dataPath.toStdString()
- << std::endl;
- try
+ auto topic = transport::TopicUtils::AsValidTopic(
+ scopedName(world, _ecm) + "/" + "environment");
+ this->dataPtr->pub =
+ {this->dataPtr->node.Advertise(topic)};
+ }
+
+ static bool warned = false;
+ if (!this->dataPtr->pub->HasConnections() && !warned)
+ {
+ warned = true;
+ gzwarn << "Could not find a subscriber for the environment. "
+ << "Attempting to load environmental preload plugin."
+ << std::endl;
+
+ auto nameComp = _ecm.Component(world);
+ if (nullptr == nameComp) {
+ gzerr << "Failed to get world name" << std::endl;
+ return;
+ }
+ auto worldName = nameComp->Data();
+ msgs::EntityPlugin_V req;
+ req.mutable_entity()->set_id(world);
+ auto plugin = req.add_plugins();
+ plugin->set_name(preload_plugin_name);
+ plugin->set_filename(preload_plugin_filename);
+ plugin->set_innerxml("");
+ msgs::Boolean res;
+ bool result;
+ const unsigned int timeout = 5000;
+ const auto service = transport::TopicUtils::AsValidTopic(
+ "/world/" + worldName + "/entity/system/add");
+ if (service.empty())
+ {
+ gzerr << "Unable to request " << service << std::endl;
+ return;
+ }
+
+ if (this->dataPtr->node.Request(service, req, timeout, res, result))
{
- using ComponentDataT = components::EnvironmentalData;
- auto data = ComponentDataT::MakeShared(
- common::IO::ReadFrom(
- common::CSVIStreamIterator(dataFile),
- common::CSVIStreamIterator(),
- this->dataPtr->timeIndex, {
- static_cast(this->dataPtr->xIndex),
- static_cast(this->dataPtr->yIndex),
- static_cast(this->dataPtr->zIndex)}),
- this->dataPtr->referenceMap[this->dataPtr->reference],
- this->dataPtr->unitMap[this->dataPtr->unit]);
-
- using ComponentT = components::Environment;
- _ecm.CreateComponent(worldEntity(_ecm), ComponentT{std::move(data)});
+ gzdbg << "Added plugin successfully" << std::endl;
}
- catch (const std::invalid_argument &exc)
+ else
{
- gzerr << "Failed to load environmental data" << std::endl
- << exc.what() << std::endl;
+ gzerr << "Failed to load plugin" << std::endl;
}
}
}
@@ -162,7 +195,25 @@ void EnvironmentLoader::Update(const UpdateInfo &,
/////////////////////////////////////////////////
void EnvironmentLoader::ScheduleLoad()
{
- this->dataPtr->needsLoad = this->IsConfigured();
+ if(this->IsConfigured() && this->dataPtr->pub.has_value())
+ {
+ msgs::DataLoadPathOptions data;
+ data.set_path(this->dataPtr->dataPath.toStdString());
+ data.set_time(
+ this->dataPtr->dimensionList[this->dataPtr->timeIndex].toStdString());
+ data.set_x(
+ this->dataPtr->dimensionList[this->dataPtr->xIndex].toStdString());
+ data.set_y(
+ this->dataPtr->dimensionList[this->dataPtr->yIndex].toStdString());
+ data.set_z(
+ this->dataPtr->dimensionList[this->dataPtr->zIndex].toStdString());
+ auto referenceFrame = this->dataPtr->referenceMap[this->dataPtr->reference];
+
+ data.set_coordinate_type(msgs::ConvertCoord(referenceFrame));
+ data.set_units(this->dataPtr->unitMap[this->dataPtr->unit]);
+
+ this->dataPtr->pub->Publish(data);
+ }
}
/////////////////////////////////////////////////
diff --git a/src/gui/plugins/environment_visualization/EnvironmentVisualization.cc b/src/gui/plugins/environment_visualization/EnvironmentVisualization.cc
index 3ed5f6f9ce..c9c040f454 100644
--- a/src/gui/plugins/environment_visualization/EnvironmentVisualization.cc
+++ b/src/gui/plugins/environment_visualization/EnvironmentVisualization.cc
@@ -36,9 +36,7 @@
#include
-#include
-#include
-#include
+#include
#include
using namespace gz;
@@ -51,201 +49,44 @@ namespace sim
inline namespace GZ_SIM_VERSION_NAMESPACE
{
/// \brief Private data class for EnvironmentVisualization
-class EnvironmentVisualizationPrivate
+class EnvironmentVisualizationTool
{
- public: EnvironmentVisualizationPrivate()
- {
- this->pcPub =
- this->node.Advertise("/point_cloud");
- }
- /// \brief To synchronize member access.
- public: std::mutex mutex;
-
- /// \brief first load we need to scan for existing data sensor
- public: bool first {true};
-
- public: std::atomic resample{true};
/////////////////////////////////////////////////
- public: void CreatePointCloudTopics(
- std::shared_ptr data) {
- this->pubs.clear();
- this->sessions.clear();
- for (auto key : data->frame.Keys())
- {
- this->pubs.emplace(key, node.Advertise(key));
- gz::msgs::Float_V msg;
- this->floatFields.emplace(key, msg);
- this->sessions.emplace(key, data->frame[key].CreateSession());
- }
- }
-
- /////////////////////////////////////////////////
- public: void Step(
- const UpdateInfo &_info,
- std::shared_ptr &data,
- const EntityComponentManager& _ecm,
- double xSamples, double ySamples, double zSamples)
+ public: void Initiallize(
+ const EntityComponentManager &_ecm)
{
- auto now = std::chrono::steady_clock::now();
- std::chrono::duration dt(now - this->lastTick);
-
- if (this->resample)
- {
- this->CreatePointCloudTopics(data);
- this->ResizeCloud(data, _ecm, xSamples, ySamples, zSamples);
- this->resample = false;
- this->lastTick = now;
- }
-
- for (auto &it : this->sessions)
- {
- auto res =
- data->frame[it.first].StepTo(it.second,
- std::chrono::duration(_info.simTime).count());
- if (res.has_value())
- {
- it.second = res.value();
- }
- }
-
- // Publish at 2 hz for now. In future make reconfigureable.
- if (dt.count() > 0.5)
- {
- this->Visualize(data, xSamples, ySamples, zSamples);
- this->Publish();
- lastTick = now;
- }
+ auto world = worldEntity(_ecm);
+ auto topic =
+ common::joinPaths(
+ scopedName(world, _ecm), "environment", "visualize", "res");
+ std::lock_guard lock(this->mutex);
+ this->pcPub = node.Advertise(topic);
}
/////////////////////////////////////////////////
public: void Visualize(
- std::shared_ptr data,
- double xSamples, double ySamples, double zSamples) {
-
- for (auto key : data->frame.Keys())
- {
- const auto session = this->sessions[key];
- auto frame = data->frame[key];
- auto [lower_bound, upper_bound] = frame.Bounds(session);
- auto step = upper_bound - lower_bound;
- auto dx = step.X() / xSamples;
- auto dy = step.Y() / ySamples;
- auto dz = step.Z() / zSamples;
- std::size_t idx = 0;
- for (std::size_t x_steps = 0; x_steps < ceil(xSamples); x_steps++)
- {
- auto x = lower_bound.X() + x_steps * dx;
- for (std::size_t y_steps = 0; y_steps < ceil(ySamples); y_steps++)
- {
- auto y = lower_bound.Y() + y_steps * dy;
- for (std::size_t z_steps = 0; z_steps < ceil(zSamples); z_steps++)
- {
- auto z = lower_bound.Z() + z_steps * dz;
- auto res = frame.LookUp(session, math::Vector3d(x, y, z));
-
- if (res.has_value())
- {
- this->floatFields[key].mutable_data()->Set(idx,
- static_cast(res.value()));
- }
- else
- {
- this->floatFields[key].mutable_data()->Set(idx, std::nanf(""));
- }
- idx++;
- }
- }
- }
- }
- }
-
- /////////////////////////////////////////////////
- public: void Publish()
+ double xSamples, double ySamples, double zSamples)
{
- pcPub.Publish(this->pcMsg);
- for(auto &[key, pub] : this->pubs)
- {
- pub.Publish(this->floatFields[key]);
- }
+ std::lock_guard lock(this->mutex);
+ this->vec = msgs::Convert(math::Vector3d(xSamples, ySamples, zSamples));
+ this->pcPub.Publish(vec);
}
- /////////////////////////////////////////////////
- public: void ResizeCloud(
- std::shared_ptr data,
- const EntityComponentManager& _ecm,
- unsigned int xSamples, unsigned int ySamples, unsigned int zSamples)
- {
- assert (pubs.size() > 0);
-
- // Assume all data have same point cloud.
- gz::msgs::InitPointCloudPacked(pcMsg, "some_frame", true,
- {{"xyz", gz::msgs::PointCloudPacked::Field::FLOAT32}});
- auto numberOfPoints = xSamples * ySamples * zSamples;
- std::size_t dataSize{
- static_cast(numberOfPoints * pcMsg.point_step())};
- pcMsg.mutable_data()->resize(dataSize);
- pcMsg.set_height(1);
- pcMsg.set_width(numberOfPoints);
-
- auto session = this->sessions[this->pubs.begin()->first];
- auto frame = data->frame[this->pubs.begin()->first];
- auto [lower_bound, upper_bound] =
- frame.Bounds(session);
+ /// \brief The sample resolution
+ public: gz::msgs::Vector3d vec;
- auto step = upper_bound - lower_bound;
- auto dx = step.X() / xSamples;
- auto dy = step.Y() / ySamples;
- auto dz = step.Z() / zSamples;
-
- // Populate point cloud
- gz::msgs::PointCloudPackedIterator xIter(pcMsg, "x");
- gz::msgs::PointCloudPackedIterator yIter(pcMsg, "y");
- gz::msgs::PointCloudPackedIterator zIter(pcMsg, "z");
-
- for (std::size_t x_steps = 0; x_steps < xSamples; x_steps++)
- {
- auto x = lower_bound.X() + x_steps * dx;
- for (std::size_t y_steps = 0; y_steps < ySamples; y_steps++)
- {
- auto y = lower_bound.Y() + y_steps * dy;
- for (std::size_t z_steps = 0; z_steps < zSamples; z_steps++)
- {
- auto z = lower_bound.Z() + z_steps * dz;
- auto coords = getGridFieldCoordinates(
- _ecm, math::Vector3d{x, y, z},
- data);
+ /// \brief Publisher to publish sample resolution
+ public: transport::Node::Publisher pcPub;
- if (!coords.has_value())
- {
- continue;
- }
+ /// \brief Gz transport node
+ public: transport::Node node;
- auto pos = coords.value();
- *xIter = pos.X();
- *yIter = pos.Y();
- *zIter = pos.Z();
- ++xIter;
- ++yIter;
- ++zIter;
- }
- }
- }
- for (auto key : data->frame.Keys())
- {
- this->floatFields[key].mutable_data()->Resize(
- numberOfPoints, std::nanf(""));
- }
- }
+ /// \brief To synchronize member access.
+ public: std::mutex mutex;
- public: transport::Node::Publisher pcPub;
- public: std::unordered_map pubs;
- public: std::unordered_map floatFields;
- public: transport::Node node;
- public: gz::msgs::PointCloudPacked pcMsg;
- public: std::unordered_map> sessions;
- public: std::chrono::time_point lastTick;
+ /// \brief first load we need to scan for existing data sensor
+ public: bool first{true};
};
}
}
@@ -253,10 +94,14 @@ class EnvironmentVisualizationPrivate
/////////////////////////////////////////////////
EnvironmentVisualization::EnvironmentVisualization()
- : GuiSystem(), dataPtr(new EnvironmentVisualizationPrivate)
+ : GuiSystem(), dataPtr(new EnvironmentVisualizationTool)
{
gui::App()->Engine()->rootContext()->setContextProperty(
"EnvironmentVisualization", this);
+ this->qtimer = new QTimer(this);
+ connect(qtimer, &QTimer::timeout,
+ this, &EnvironmentVisualization::ResamplePointcloud);
+ this->qtimer->start(1000);
}
/////////////////////////////////////////////////
@@ -274,38 +119,23 @@ void EnvironmentVisualization::LoadConfig(const tinyxml2::XMLElement *)
}
/////////////////////////////////////////////////
-void EnvironmentVisualization::Update(const UpdateInfo &_info,
+void EnvironmentVisualization::Update(const UpdateInfo &,
EntityComponentManager &_ecm)
{
- _ecm.EachNew(
- [this](
- const Entity &/*_entity*/,
- const components::Environment* /*environment*/
- ) {
- this->dataPtr->resample = true;
- return true;
- }
- );
-
- auto environData =
- _ecm.Component(
- worldEntity(_ecm));
-
- if (environData == nullptr)
+ if (this->dataPtr->first)
{
- return;
+ this->dataPtr->Initiallize(_ecm);
+ this->dataPtr->first = false;
+ this->ResamplePointcloud();
}
-
- this->dataPtr->Step(
- _info, environData->Data(), _ecm,
- this->xSamples, this->ySamples, this->zSamples
- );
}
/////////////////////////////////////////////////
void EnvironmentVisualization::ResamplePointcloud()
{
- this->dataPtr->resample = true;
+ this->dataPtr->Visualize(
+ this->xSamples, this->ySamples, this->zSamples
+ );
}
diff --git a/src/gui/plugins/environment_visualization/EnvironmentVisualization.hh b/src/gui/plugins/environment_visualization/EnvironmentVisualization.hh
index 91cd420eb9..e5a3eb4397 100644
--- a/src/gui/plugins/environment_visualization/EnvironmentVisualization.hh
+++ b/src/gui/plugins/environment_visualization/EnvironmentVisualization.hh
@@ -30,7 +30,7 @@ namespace sim
// Inline bracket to help doxygen filtering.
inline namespace GZ_SIM_VERSION_NAMESPACE
{
- class EnvironmentVisualizationPrivate;
+ class EnvironmentVisualizationTool;
/// \class EnvironmentVisualization EnvironmentVisualization.hh
/// gz/sim/systems/EnvironmentVisualization.hh
@@ -66,13 +66,15 @@ inline namespace GZ_SIM_VERSION_NAMESPACE
/// \internal
/// \brief Pointer to private data
- private: std::unique_ptr dataPtr;
+ private: std::unique_ptr dataPtr;
public: unsigned int xSamples{10};
public: unsigned int ySamples{10};
public: unsigned int zSamples{10};
+
+ private: QTimer* qtimer;
};
}
}
diff --git a/src/systems/CMakeLists.txt b/src/systems/CMakeLists.txt
index 7eb7786908..d75a20663d 100644
--- a/src/systems/CMakeLists.txt
+++ b/src/systems/CMakeLists.txt
@@ -96,6 +96,7 @@ endfunction()
add_subdirectory(ackermann_steering)
add_subdirectory(acoustic_comms)
+add_subdirectory(advanced_lift_drag)
add_subdirectory(air_pressure)
add_subdirectory(air_speed)
add_subdirectory(altimeter)
diff --git a/src/systems/ackermann_steering/AckermannSteering.hh b/src/systems/ackermann_steering/AckermannSteering.hh
index f82e0cec20..3d829d8902 100644
--- a/src/systems/ackermann_steering/AckermannSteering.hh
+++ b/src/systems/ackermann_steering/AckermannSteering.hh
@@ -36,106 +36,106 @@ namespace systems
/// \brief Ackermann steering controller which can be attached to a model
/// with any number of left and right wheels.
///
- /// # System Parameters
+ /// ## System Parameters
///
- /// ``: Boolean used to only control the steering angle
+ /// - ``: Boolean used to only control the steering angle
/// only. Calculates the angles of wheels from steering_limit, wheel_base,
/// and wheel_separation. Uses gz::msg::Double on default topic name
/// `/model/{name_of_model}/steer_angle`. Automatically set True when
/// `` is True, uses defaults topic name "/actuators".
///
- /// `` True to enable the use of actutor message
+ /// - `` True to enable the use of actutor message
/// for steering angle command. Relies on `` for the
/// index of the position actuator and defaults to topic "/actuators".
///
- /// `` used with `` to set
+ /// - `` used with `` to set
/// the index of the steering angle position actuator.
///
- /// ``: Float used to control the steering angle P gain.
+ /// - ``: Float used to control the steering angle P gain.
/// Only used for when in steering_only mode.
///
- /// ``: Name of a joint that controls a left wheel. This
+ /// - ``: Name of a joint that controls a left wheel. This
/// element can appear multiple times, and must appear at least once.
///
- /// ``: Name of a joint that controls a right wheel. This
+ /// - ``: Name of a joint that controls a right wheel. This
/// element can appear multiple times, and must appear at least once.
///
- /// ``: Name of a joint that controls steering for
- /// left wheel. This element must appear once. Vehicles that steer
- /// rear wheels are not currently correctly supported.
+ /// - ``: Name of a joint that controls steering for
+ /// left wheel. This element must appear once. Vehicles that steer
+ /// rear wheels are not currently correctly supported.
///
- /// ``: Name of a joint that controls steering for
- /// right wheel. This element must appear once.
+ /// - ``: Name of a joint that controls steering for
+ /// right wheel. This element must appear once.
///
- /// ``: Distance between wheels, in meters. This element
+ /// - ``: Distance between wheels, in meters. This element
/// is optional, although it is recommended to be included with an
/// appropriate value. The default value is 1.0m.
///
- /// ``: Distance between wheel kingpins, in meters. This
+ /// - ``: Distance between wheel kingpins, in meters. This
/// element is optional, although it is recommended to be included with an
/// appropriate value. The default value is 0.8m. Generally a bit smaller
/// than the wheel_separation.
///
- /// ``: Distance between front and rear wheels, in meters. This
+ /// - ``: Distance between front and rear wheels, in meters. This
/// element is optional, although it is recommended to be included with an
/// appropriate value. The default value is 1.0m.
///
- /// ``: Value to limit steering to. Can be calculated by
+ /// - ``: Value to limit steering to. Can be calculated by
/// measuring the turning radius and wheel_base of the real vehicle.
/// steering_limit = asin(wheel_base / turning_radius)
/// The default value is 0.5 radians.
///
- /// ``: Wheel radius in meters. This element is optional,
+ /// - ``: Wheel radius in meters. This element is optional,
/// although it is recommended to be included with an appropriate value. The
/// default value is 0.2m.
///
- /// ``: Odometry publication frequency. This
+ /// - ``: Odometry publication frequency. This
/// element is optional, and the default value is 50Hz.
///
- /// ``: Minimum velocity [m/s], usually <= 0.
- /// ``: Maximum velocity [m/s], usually >= 0.
- /// ``: Minimum acceleration [m/s^2], usually <= 0.
- /// ``: Maximum acceleration [m/s^2], usually >= 0.
- /// ``: jerk [m/s^3], usually <= 0.
- /// ``: jerk [m/s^3], usually >= 0.
+ /// - ``: Minimum velocity [m/s], usually <= 0.
+ /// - ``: Maximum velocity [m/s], usually >= 0.
+ /// - ``: Minimum acceleration [m/s^2], usually <= 0.
+ /// - ``: Maximum acceleration [m/s^2], usually >= 0.
+ /// - ``: jerk [m/s^3], usually <= 0.
+ /// - ``: jerk [m/s^3], usually >= 0.
///
- /// ``: Custom topic that this system will subscribe to in order to
+ /// - ``: Custom topic that this system will subscribe to in order to
/// receive command messages. This element is optional, and the
/// default value is `/model/{name_of_model}/cmd_vel` or when steering_only
/// is true `/model/{name_of_model}/steer_angle`.
///
- /// ``: Custom sub_topic that this system will subscribe to in
+ /// - ``: Custom sub_topic that this system will subscribe to in
/// order to receive command messages. This element is optional, and
/// creates a topic `/model/{name_of_model}/{sub_topic}`
///
- /// ``: Custom topic on which this system will publish odometry
+ /// - ``: Custom topic on which this system will publish odometry
/// messages. This element if optional, and the default value is
/// `/model/{name_of_model}/odometry`.
///
- /// ``: Custom topic on which this system will publish the
+ /// - ``: Custom topic on which this system will publish the
/// transform from `frame_id` to `child_frame_id`. This element is optional,
/// and the default value is `/model/{name_of_model}/tf`.
///
- /// ``: Custom `frame_id` field that this system will use as the
+ /// - ``: Custom `frame_id` field that this system will use as the
/// origin of the odometry transform in both the ``
/// `gz.msgs.Pose_V` message and the ``
/// `gz.msgs.Odometry` message. This element if optional, and the
/// default value is `{name_of_model}/odom`.
///
- /// ``: Custom `child_frame_id` that this system will use as
+ /// - ``: Custom `child_frame_id` that this system will use as
/// the target of the odometry transform in both the ``
- /// `gz.msgs.Pose_V` message and the ``
- /// `gz.msgs.Odometry` message. This element if optional,
+ /// gz.msgs.Pose_V message and the ``
+ /// gz.msgs.Odometry message. This element is optional,
/// and the default value is `{name_of_model}/{name_of_link}`.
///
/// A robot with rear drive and front steering would have one each
/// of left_joint, right_joint, left_steering_joint and
- /// right_steering_joint
+ /// right_steering_joint.
///
/// References:
- /// https://github.com/gazebosim/gz-sim/tree/main/src/systems/ackermann_steering
- /// https://www.auto.tuwien.ac.at/bib/pdf_TR/TR0183.pdf
- /// https://github.com/froohoo/ackermansteer/blob/master/ackermansteer/
+ /// - https://github.com/gazebosim/gz-sim/tree/main/src/systems/ackermann_steering
+ /// - https://www.auto.tuwien.ac.at/bib/pdf_TR/TR0183.pdf
+ /// - https://github.com/froohoo/ackermansteer/blob/master/ackermansteer/
class AckermannSteering
diff --git a/src/systems/acoustic_comms/AcousticComms.hh b/src/systems/acoustic_comms/AcousticComms.hh
index 2845436cef..45e7db8845 100644
--- a/src/systems/acoustic_comms/AcousticComms.hh
+++ b/src/systems/acoustic_comms/AcousticComms.hh
@@ -76,6 +76,7 @@ namespace systems
/// * : Seed value to be used for random sampling.
///
/// Here's an example:
+ /// ```
///
@@ -93,6 +94,7 @@ namespace systems
///
///
///
+ /// ```
class AcousticComms:
public gz::sim::comms::ICommsModel
diff --git a/src/systems/advanced_lift_drag/AdvancedLiftDrag.cc b/src/systems/advanced_lift_drag/AdvancedLiftDrag.cc
new file mode 100644
index 0000000000..a7c60109fd
--- /dev/null
+++ b/src/systems/advanced_lift_drag/AdvancedLiftDrag.cc
@@ -0,0 +1,852 @@
+/*
+ * Copyright (C) 2019 Open Source Robotics Foundation
+ *
+ * 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.
+ *
+ * @author: Karthik Srivatsan, Frederik Markus
+ * @version: 1.1
+ *
+ * @brief: this plugin models the lift and drag of an aircraft
+ * as a single body, using stability, aerodynamic, and control derivatives.
+ * It takes in a specified number of control surfaces and control
+ * derivatives are defined/specified with respect to the deflection
+ * of individual control surfaces. Coefficients for this plugin can be
+ * obtained using Athena Vortex Lattice (AVL) by Mark Drela
+ * https://nps.edu/web/adsc/aircraft-aerodynamics2
+ * The sign conventions used in this plugin are therefore written
+ * in a way to be compatible with AVL.
+ * Force equations are computed in the body, while
+ * moment equations are computed in the stability frame.
+ * Has been adapted for Gazebo (Ignition) using the ECS.
+ *
+ *
+ */
+
+#include "AdvancedLiftDrag.hh"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "gz/sim/Link.hh"
+#include "gz/sim/Model.hh"
+#include "gz/sim/Util.hh"
+
+#include "gz/sim/components/AngularVelocity.hh"
+#include "gz/sim/components/Inertial.hh"
+#include "gz/sim/components/Joint.hh"
+#include "gz/sim/components/JointPosition.hh"
+#include "gz/sim/components/LinearVelocity.hh"
+#include "gz/sim/components/Link.hh"
+#include "gz/sim/components/Name.hh"
+#include "gz/sim/components/ExternalWorldWrenchCmd.hh"
+#include "gz/sim/components/Pose.hh"
+#include "gz/sim/components/Wind.hh"
+
+using namespace gz;
+using namespace sim;
+using namespace systems;
+
+class gz::sim::systems::AdvancedLiftDragPrivate
+{
+ // Initialize the system
+ public: void Load(const EntityComponentManager &_ecm,
+ const sdf::ElementPtr &_sdf);
+
+ /// \brief Initializes lift and drag forces in order to later
+ /// update the corresponding components
+ /// \param[in] _ecm Immutable reference to the EntityComponentManager
+ public: void Update(EntityComponentManager &_ecm);
+
+ /// \brief Compute Control Surface effects
+ /// \param[in] _ecm Immutable reference to the EntityComponentManager
+ public: void Comp_Ctrl_surf_eff(EntityComponentManager &_ecm);
+
+ /// \brief Model interface
+ public: Model model{kNullEntity};
+
+ /// \brief Lift Coefficient at zero angle of attack
+ public: double CL0 = 0.0;
+
+ /// \brief Drag coefficient at zero angle of attack
+ public: double CD0 = 0.0;
+
+ /// \brief Pitching moment coefficient at zero angle of attack
+ public: double Cem0 = 0.0;
+
+ // Get angle-of-attack (alpha) derivatives
+ /// \brief dCL/da (slope of CL-alpha curve)
+ public: double CLa = 0.0;
+
+ /// \brief dCy/da (sideforce slope wrt alpha)
+ public: double CYa = 0.0;
+
+ /// \brief dCl/da (roll moment slope wrt alpha)
+ public: double Cella = 0.0;
+
+ /// \brief dCm/da (pitching moment slope wrt alpha - before stall)
+ public: double Cema = 0.0;
+
+ /// \brief dCn/da (yaw moment slope wrt alpha)
+ public: double Cena = 0.0;
+
+ // Get sideslip angle (beta) derivatives
+ /// \brief dCL/dbeta (lift coefficient slope wrt beta)
+ public: double CLb = 0.0;
+
+ /// \brief dCY/dbeta (side force slope wrt beta)
+ public: double CYb = 0.0;
+
+ /// \brief dCl/dbeta (roll moment slope wrt beta)
+ public: double Cellb = 0.0;
+
+ /// \brief dCm/dbeta (pitching moment slope wrt beta)
+ public: double Cemb = 0.0;
+
+ /// \brief dCn/dbeta (yaw moment slope wrt beta)
+ public: double Cenb = 0.0;
+
+ /// \brief angle of attack when airfoil stalls
+ public: double alphaStall = GZ_PI_2;
+
+ /// \brief The angle of attack
+ public: double alpha = 0.0;
+
+ /// \brief The sideslip angle
+ public: double beta = 0.0;
+
+ /// \brief Slope of the Cm-alpha curve after stall
+ public: double CemaStall = 0.0;
+
+ /// \brief AVL reference point (it replaces the center of pressure
+ /// in the original LiftDragPlugin)
+ public: gz::math::Vector3d cp = math::Vector3d::Zero;
+
+ // Get the derivatives with respect to non-dimensional rates.
+ // In the next few lines, if you see "roll/pitch/yaw rate", remember it is in
+ // a non-dimensional form- it is not the actual body rate.
+ // Also, keep in mind that this CDp is not parasitic drag: that is CD0.
+
+ /// \brief dCD/dp (drag coefficient slope wrt roll rate)
+ public: double CDp = 0.0;
+
+ /// \brief dCY/dp (sideforce slope wrt roll rate)
+ public: double CYp = 0.0;
+
+ /// \brief dCL/dp (lift coefficient slope wrt roll rate)
+ public: double CLp = 0.0;
+
+ /// \brief dCl/dp (roll moment slope wrt roll rate)
+ public: double Cellp = 0.0;
+
+ /// \brief dCm/dp (pitching moment slope wrt roll rate)
+ public: double Cemp = 0.0;
+
+ /// \brief dCn/dp (yaw moment slope wrt roll rate)
+ public: double Cenp = 0.0;
+
+ /// \brief dCD/dq (drag coefficient slope wrt pitching rate)
+ public: double CDq = 0.0;
+
+ /// \brief dCY/dq (side force slope wrt pitching rate)
+ public: double CYq = 0.0;
+
+ /// \brief dCL/dq (lift coefficient slope wrt pitching rate)
+ public: double CLq = 0.0;
+
+ /// \brief dCl/dq (roll moment slope wrt pitching rate)
+ public: double Cellq = 0.0;
+
+ /// \brief dCm/dq (pitching moment slope wrt pitching rate)
+ public: double Cemq = 0.0;
+
+ /// \brief dCn/dq (yaw moment slope wrt pitching rate)
+ public: double Cenq = 0.0;
+
+ /// \brief dCD/dr (drag coefficient slope wrt yaw rate)
+ public: double CDr = 0.0;
+
+ /// \brief dCY/dr (side force slope wrt yaw rate)
+ public: double CYr = 0.0;
+
+ /// \brief dCL/dr (lift coefficient slope wrt yaw rate)
+ public: double CLr = 0.0;
+
+ /// \brief dCl/dr (roll moment slope wrt yaw rate)
+ public: double Cellr = 0.0;
+
+ /// \brief dCm/dr (pitching moment slope wrt yaw rate)
+ public: double Cemr = 0.0;
+
+ /// \brief dCn/dr (yaw moment slope wrt yaw rate)
+ public: double Cenr = 0.0;
+
+ /// \brief Number of present control surfaces
+ public: int num_ctrl_surfaces = 0;
+
+ /// Initialize storage of control surface properties
+ /// \brief Joint that the control surface connects to
+ public: std::vector controlJoints;
+
+ /// \brief Direction the control surface deflects to
+ public: std::vector ctrl_surface_direction;
+
+ /// \brief Effect of the control surface's deflection on drag
+ public: std::vector CD_ctrl;
+
+ /// \brief Effect of the control surface's deflection on side force
+ public: std::vector CY_ctrl;
+
+ /// \brief Effect of the control surface's deflection on lift
+ public: std::vector CL_ctrl;
+
+ /// \brief Effect of the control surface's deflection on roll moment
+ public: std::vector Cell_ctrl;
+
+ /// \brief Effect of the control surface's deflection on pitching moment
+ public: std::vector Cem_ctrl;
+
+ /// \brief Effect of the control surface's deflection on yaw moment
+ public: std::vector Cen_ctrl;
+
+ /// \brief Add aspect ratio (should that be computed?)
+ public: double AR = 0.0;
+
+ /// \brief Add mean-aerodynamic chord
+ public: double mac = 0.0;
+
+ /// \brief Add wing efficiency (Oswald efficiency factor for a 3D wing)
+ public: double eff = 0.0;
+
+ /// \brief The sigmoid blending parameter
+ public: double M = 15.0;
+
+ /// \brief coefficients for the flat plate drag model
+ public: double CD_fp_k1 = -0.224;
+ public: double CD_fp_k2 = -0.115;
+
+
+ /// \brief air density
+ /// at 25 deg C it's about 1.1839 kg/m^3
+ /// At 20 °C and 101.325 kPa, dry air has a density of 1.2041 kg/m3.
+ public: double rho = 1.2041;
+
+ /// \brief if the shape is aerodynamically radially symmetric about
+ /// the forward direction. Defaults to false for wing shapes.
+ /// If set to true, the upward direction is determined by the
+ /// angle of attack.
+ public: bool radialSymmetry = false;
+
+ /// \brief effective planeform surface area
+ public: double area = 1.0;
+
+ /// \brief Normally, this is taken as a direction parallel to the chord
+ /// of the airfoil in zero angle of attack forward flight.
+ public: math::Vector3d forward = math::Vector3d::UnitX;
+
+ /// \brief A vector in the lift/drag plane, perpendicular to the forward
+ /// vector. Inflow velocity orthogonal to forward and upward vectors
+ /// is considered flow in the wing sweep direction.
+ public: math::Vector3d upward = math::Vector3d::UnitZ;
+
+ /// \brief how much to change CL per radian of control surface joint
+ /// value.
+ public: double controlJointRadToCL = 4.0;
+
+ /// \brief Link entity targeted this plugin.
+ public: Entity linkEntity;
+
+ /// \brief Joint entity that actuates a control surface for this lifting body
+ public: Entity controlJointEntity;
+
+ /// \brief Set during Load to true if the configuration for the system is
+ /// valid and the post-update can run
+ public: bool validConfig{false};
+
+ /// \brief Copy of the sdf configuration used for this plugin
+ public: sdf::ElementPtr sdfConfig;
+
+ /// \brief Initialization flag
+ public: bool initialized{false};
+};
+
+//////////////////////////////////////////////////
+void AdvancedLiftDragPrivate::Load(const EntityComponentManager &_ecm,
+ const sdf::ElementPtr &_sdf)
+{
+ this->CL0 = _sdf->Get("CL0", this->CL0).first;
+ this->CD0 = _sdf->Get("CD0", this->CD0).first;
+ this->Cem0 = _sdf->Get("Cem0", this->Cem0).first;
+ this->CLa = _sdf->Get("CLa", this->CLa).first;
+ this->CYa = _sdf->Get("CYa", this->CYa).first;
+ this->Cella = _sdf->Get("Cella", this->Cella).first;
+ this->Cema = _sdf->Get("Cema", this->Cema).first;
+ this->Cena = _sdf->Get("Cena", this->Cena).first;
+ this->CLb = _sdf->Get("CLb", this->CLb).first;
+ this->CYb = _sdf->Get("CYb", this->CYb).first;
+ this->Cellb = _sdf->Get("Cellb", this->Cellb).first;
+ this->Cemb = _sdf->Get("Cemb", this->Cemb).first;
+ this->Cenb = _sdf->Get("Cenb", this->Cenb).first;
+ this->alphaStall = _sdf->Get("alpha_stall", this->alphaStall).first;
+ this->CemaStall = _sdf->Get("Cema_stall", this->CemaStall).first;
+ this->cp = _sdf->Get("cp", this->cp).first;
+ this->CDp = _sdf->Get("CDp", this->CDp).first;
+ this->CYp = _sdf->Get("CYp", this->CYp).first;
+ this->CLp = _sdf->Get("CLp", this->CLp).first;
+ this->Cellp = _sdf->Get("Cellp", this->Cellp).first;
+ this->Cemp = _sdf->Get("Cemp", this->Cemp).first;
+ this->Cenp = _sdf->Get("Cenp", this->Cenp).first;
+
+ this->CDq = _sdf->Get("CDq", this->CDq).first;
+ this->CYq = _sdf->Get("CYq", this->CYq).first;
+ this->CLq = _sdf->Get("CLq", this->CLq).first;
+ this->Cellq = _sdf->Get("Cellq", this->Cellq).first;
+ this->Cemq = _sdf->Get("Cemq", this->Cemq).first;
+ this->Cenq = _sdf->Get("Cenq", this->Cenq).first;
+ this->CDr = _sdf->Get("CDr", this->CDr).first;
+ this->CYr = _sdf->Get("CYr", this->CYr).first;
+ this->CLr = _sdf->Get("CLr", this->CLr).first;
+ this->Cellr = _sdf->Get("Cellr", this->Cellr).first;
+ this->Cemr = _sdf->Get("Cemr", this->Cemr).first;
+ this->Cenr = _sdf->Get("Cenr", this->Cenr).first;
+ this->num_ctrl_surfaces = _sdf->Get(
+ "num_ctrl_surfaces", this->num_ctrl_surfaces).first;
+
+ /*
+ Next, get the properties of each control surface: which joint it connects to,
+ which direction it deflects in, and the effect of its deflection on the
+ coefficient of drag, side force, lift, roll moment, pitching moment, and yaw moment.
+ */
+
+ while( _sdf->HasElement("control_surface") )
+ {
+ auto curr_ctrl_surface = _sdf->GetElement("control_surface");
+ auto ctrl_surface_name = curr_ctrl_surface->GetElement(
+ "name")->Get();
+ auto entities =
+ entitiesFromScopedName(ctrl_surface_name, _ecm, this->model.Entity());
+
+ if (entities.empty())
+ {
+ gzerr << "Joint with name[" << ctrl_surface_name << "] not found. "
+ << "The LiftDrag will not generate forces.\n";
+ this->validConfig = false;
+ return;
+ }
+ else if (entities.size() > 1)
+ {
+ gzwarn << "Multiple joint entities with name[" << ctrl_surface_name
+ << "] found. Using the first one.\n";
+ }
+
+ controlJointEntity = *entities.begin();
+ if (!_ecm.EntityHasComponentType(this->controlJointEntity,
+ components::Joint::typeId))
+ {
+ this->controlJointEntity = kNullEntity;
+ gzerr << "Entity with name[" << ctrl_surface_name
+ << "] is not a joint.\n";
+ this->validConfig = false;
+ return;
+ }
+
+ this->controlJoints.push_back(controlJointEntity);
+ this->ctrl_surface_direction.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "direction"))->GetValue())->GetAsString()));
+ this->CD_ctrl.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "CD_ctrl"))->GetValue())->GetAsString()));
+ this->CY_ctrl.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "CY_ctrl"))->GetValue())->GetAsString()));
+ this->CL_ctrl.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "CL_ctrl"))->GetValue())->GetAsString()));
+ this->Cell_ctrl.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "Cell_ctrl"))->GetValue())->GetAsString()));
+ this->Cem_ctrl.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "Cem_ctrl"))->GetValue())->GetAsString()));
+ this->Cen_ctrl.push_back(
+ std::stod(((curr_ctrl_surface->GetElement(
+ "Cen_ctrl"))->GetValue())->GetAsString()));
+ _sdf->RemoveChild(curr_ctrl_surface);
+ }
+
+ this->AR = _sdf->Get("AR", this->AR).first;
+ this->mac = _sdf->Get("mac", this->mac).first;
+ this->eff = _sdf->Get("eff", this->eff).first;
+ this->rho = _sdf->Get("air_density", this->rho).first;
+ this->radialSymmetry = _sdf->Get("radial_symmetry",
+ this->radialSymmetry).first;
+ this->area = _sdf->Get("area", this->area).first;
+
+ // blade forward (-drag) direction in link frame
+ this->forward =
+ _sdf->Get("forward", this->forward).first;
+ if(std::fabs(this->forward.Length()) >= 1e-9){
+ this->forward.Normalize();
+ }
+
+ else
+ {
+ gzerr << "Forward vector length is zero. This is not valid.\n";
+ this->validConfig = false;
+ return;
+ }
+
+ // blade upward (+lift) direction in link frame
+ this->upward = _sdf->Get(
+ "upward", this->upward) .first;
+ this->upward.Normalize();
+
+ this->controlJointRadToCL = _sdf->Get(
+ "control_joint_rad_to_cl", this->controlJointRadToCL).first;
+
+ if (_sdf->HasElement("link_name"))
+ {
+ sdf::ElementPtr elem = _sdf->GetElement("link_name");
+ auto linkName = elem->Get();
+ auto entities =
+ entitiesFromScopedName(linkName, _ecm, this->model.Entity());
+
+ if (entities.empty())
+ {
+ gzerr << "Link with name[" << linkName << "] not found. "
+ << "The AdvancedLiftDrag will not generate forces.\n";
+ this->validConfig = false;
+ return;
+ }
+ else if (entities.size() > 1)
+ {
+ gzwarn << "Multiple link entities with name[" << linkName << "] found. "
+ << "Using the first one.\n";
+ }
+
+ this->linkEntity = *entities.begin();
+ if (!_ecm.EntityHasComponentType(this->linkEntity,
+ components::Link::typeId))
+ {
+ this->linkEntity = kNullEntity;
+ gzerr << "Entity with name[" << linkName << "] is not a link.\n";
+ this->validConfig = false;
+ return;
+ }
+ }
+ else
+ {
+ gzerr << "AdvancedLiftDrag system requires the 'link_name' parameter.\n";
+ this->validConfig = false;
+ return;
+ }
+
+ // If we reached here, we have a valid configuration
+ this->validConfig = true;
+}
+
+//////////////////////////////////////////////////
+AdvancedLiftDrag::AdvancedLiftDrag()
+ : System(), dataPtr(std::make_unique())
+{
+}
+
+//////////////////////////////////////////////////
+void AdvancedLiftDragPrivate::Update(EntityComponentManager &_ecm)
+{
+ GZ_PROFILE("AdvancedLiftDragPrivate::Update");
+ // get linear velocity at cp in world frame
+ const auto worldLinVel =
+ _ecm.Component(this->linkEntity);
+ const auto worldAngVel =
+ _ecm.Component(this->linkEntity);
+ const auto worldPose =
+ _ecm.Component(this->linkEntity);
+
+ // get wind as a component from the _ecm
+ components::WorldLinearVelocity *windLinearVel = nullptr;
+ if(_ecm.EntityByComponents(components::Wind()) != kNullEntity){
+ Entity windEntity = _ecm.EntityByComponents(components::Wind());
+ windLinearVel =
+ _ecm.Component(windEntity);
+ }
+
+ std::vector controlJointPosition_vec(
+ this->num_ctrl_surfaces);
+ // Generate a new vector that contains the current positions for all joints
+ for(int i = 0; i < this->num_ctrl_surfaces; i++){
+ components::JointPosition *controlJointPosition = nullptr;
+ if(this->controlJoints[i] != kNullEntity){
+ controlJointPosition = _ecm.Component(
+ this->controlJoints[i]);
+ controlJointPosition_vec[i] = controlJointPosition;
+ }
+ }
+
+ if (!worldLinVel || !worldAngVel || !worldPose)
+ return;
+
+ const auto &pose = worldPose->Data();
+ const auto cpWorld = pose.Rot().RotateVector(this->cp);
+ auto air_velocity = worldLinVel->Data() + worldAngVel->Data().Cross(
+ cpWorld);
+ if (windLinearVel != nullptr){
+ air_velocity = worldLinVel->Data() + worldAngVel->Data().Cross(
+ cpWorld) - windLinearVel->Data();
+ }
+
+ // Define body frame: X forward, Z downward, Y out the right wing
+ gz::math::Vector3d body_x_axis = pose.Rot().RotateVector(this->forward);
+ gz::math::Vector3d body_z_axis = -1*(pose.Rot().RotateVector(this->upward));
+ gz::math::Vector3d body_y_axis = body_z_axis.Cross(body_x_axis);
+
+ // Get the in-plane velocity (remove spanwise velocity from the velocity
+ // vector air_velocity)
+ gz::math::Vector3d velInLDPlane = air_velocity - air_velocity.Dot(
+ body_y_axis)*body_y_axis;
+
+ // Compute dynamic pressure
+ const double speedInLDPlane = velInLDPlane.Length();
+
+ // Define stability frame: X is in-plane velocity, Y is the same as body Y,
+ // and Z perpendicular to both
+ if(speedInLDPlane <= 1e-9){
+ return;
+ }
+ gz::math::Vector3d stability_x_axis = velInLDPlane/speedInLDPlane;
+ gz::math::Vector3d stability_y_axis = body_y_axis;
+ gz::math::Vector3d stability_z_axis = stability_x_axis.Cross(
+ stability_y_axis);
+
+ double span = std::sqrt(this->area*this->AR);
+ double epsilon = 1e-9;
+ if (fabs(this->mac - 0.0) <= epsilon){
+ // Computing the mean aerodynamic chord involves integrating the square of
+ // the chord along the span. If this parameter has not been input, this
+ // plugin will approximate MAC as mean chord. This works for rectangular
+ // and trapezoidal wings, but for more complex wing shapes, doing the
+ // integral is preferred.
+ this->mac = this->area/span;
+ }
+
+ // Get non-dimensional body rates. Gazebo uses ENU, so some have to be flipped
+ // gz::math::Vector3d body_rates = this->link->GetRelativeAngularVel();
+ components::AngularVelocity *AngVel = nullptr;
+ if (this->linkEntity != kNullEntity)
+ {
+ AngVel = _ecm.Component(this->linkEntity);
+ }
+ if(AngVel == nullptr){
+ gzerr << "Angular Velocity cannot be null.\n";
+ this->validConfig = false;
+ return;
+ }
+
+ double rr = AngVel->Data()[0]; // Roll rate
+ double pr = -1*AngVel->Data()[1]; // Pitch rate
+ double yr = -1*AngVel->Data()[2]; // Yaw rate
+
+ // Compute angle of attack, alpha, using the stability and body axes
+ // Project stability x onto body x and z, then take arctan to find alpha
+ double stabx_proj_bodyx = stability_x_axis.Dot(body_x_axis);
+ double stabx_proj_bodyz = stability_x_axis.Dot(body_z_axis);
+ this->alpha = atan2(stabx_proj_bodyz, stabx_proj_bodyx);
+
+ double sinAlpha = sin(this->alpha);
+ double cosAlpha = cos(this->alpha);
+
+ // Compute sideslip angle, beta
+ double velSW = air_velocity.Dot(body_y_axis);
+ double velFW = air_velocity.Dot(body_x_axis);
+ this->beta = (atan2(velSW, velFW));
+
+ // Compute dynamic pressure
+ double dyn_pres = 0.5 * this->rho * speedInLDPlane * speedInLDPlane;
+ double half_rho_vel = 0.5 * this->rho * speedInLDPlane;
+
+ // Compute CL at cp, check for stall
+ double CL{0.0};
+
+ // Use a sigmoid function to blend pre- and post-stall models
+ double sigma = (1+exp(-1*this->M*(this->alpha-this->alphaStall))+exp(
+ this->M*(this->alpha+this->alphaStall)))/((1+exp(
+ -1*this->M*(this->alpha-this->alphaStall)))*(1+exp(
+ this->M*(this->alpha+this->alphaStall)))); // blending function
+
+ // The lift coefficient (as well as all the other coefficients) are
+ // defined with the coefficient build-up method and using a quasi-steady
+ // approach. The first thing that is calculated is the CL of the wing in
+ // steady conditions (normal CL-alpha curve). Secondly, the effect of the
+ // roll, pitch, and yaw is added through the AVL stability coefficients.
+ // Finally, the effect of the control surfaces is added.
+
+ // The lift coefficient of the wing is defined as a combination of a linear
+ // function for the pre-stall regime and a combination of exponents a
+ // trigonometric functions for the post-stall regime. Both functions are
+ // blended in with the sigmoid function.
+ // CL_prestall = this->CL0 + this->CLa*ths->alpha
+ // CL_poststall = 2*(this->alpha/abs(this->alpha))*pow(sinAlpha,2.0)*cosAlpha
+
+ CL = (1-sigma)*(this->CL0 + this->CLa*this->alpha) + sigma*(
+ 2*(this->alpha/abs(this->alpha))*sinAlpha*sinAlpha*cosAlpha);
+
+ // Add sideslip effect, if any
+ CL = CL + this->CLb*this->beta;
+
+ // Compute control surface effects
+ double CL_ctrl_tot = 0;
+ double CD_ctrl_tot = 0;
+ double CY_ctrl_tot = 0;
+ double Cell_ctrl_tot = 0;
+ double Cem_ctrl_tot = 0;
+ double Cen_ctrl_tot = 0;
+
+ for(int i = 0; i < this->num_ctrl_surfaces; i++){
+ double controlAngle = 0.0;
+ if (controlJointPosition_vec[i] && !controlJointPosition_vec[
+ i]->Data().empty())
+ {
+ components::JointPosition *tmp_controlJointPosition =
+ controlJointPosition_vec[i];
+ controlAngle = tmp_controlJointPosition->Data()[0] * 180 / GZ_PI;
+ }
+
+ // AVL's and Gazebo's direction of "positive" deflection may be different.
+ // Future users, keep an eye on this.
+ CL_ctrl_tot += controlAngle*this->CL_ctrl[i]*
+ this->ctrl_surface_direction[i];
+ CD_ctrl_tot += controlAngle*this->CD_ctrl[i]*
+ this->ctrl_surface_direction[i];
+ CY_ctrl_tot += controlAngle*this->CY_ctrl[i]*
+ this->ctrl_surface_direction[i];
+ Cell_ctrl_tot += controlAngle*this->Cell_ctrl[i]*
+ this->ctrl_surface_direction[i];
+ Cem_ctrl_tot += controlAngle*this->Cem_ctrl[i]*
+ this->ctrl_surface_direction[i];
+ Cen_ctrl_tot += controlAngle*this->Cen_ctrl[i]*
+ this->ctrl_surface_direction[i];
+ }
+
+ // AVL outputs a "CZ_elev", but the Z axis is down. This plugin
+ // uses CL_elev, which is the negative of CZ_elev
+ CL = CL+CL_ctrl_tot;
+
+ // Compute lift force at cp
+ gz::math::Vector3d lift = (CL * dyn_pres + (this->CLp * (
+ rr*span/2) * half_rho_vel) + (this->CLq * (pr*this->mac/2) *
+ half_rho_vel) + (this->CLr * (yr*span/2) * half_rho_vel)) *
+ (this->area * (-1 * stability_z_axis));
+
+ // Compute CD at cp, check for stall
+ double CD{0.0};
+
+ // Add in quadratic model and a high-angle-of-attack (Newtonian) model
+ // The post stall model used below has two parts. The first part, the
+ // (estimated) flat plate drag, comes from the data in Ostowari and Naik,
+ // Post-Stall Wind Tunnel Data for NACA 44XX Series Airfoil Sections.
+ // https://www.nrel.gov/docs/legosti/old/2559.pdf
+ // The second part (0.5-0.5cos(2*alpha)) is fitted using data from
+ // Stringer et al,
+ // A new 360° airfoil model for predicting airfoil thrust potential in
+ // vertical-axis wind turbine designs.
+ // https://aip.scitation.org/doi/pdf/10.1063/1.5011207
+ // I halved the drag numbers to make sure it would work with my
+ // flat plate drag model.
+
+
+ // To estimate the flat plate coefficient of drag, I fit a sigmoid function
+ // to the data in Ostowari and Naik. The form I used was:
+ // CD_FP = 2/(1+exp(k1+k2*AR)).
+ // The coefficients k1 and k2 might need some tuning.
+ // I chose a sigmoid because the CD would initially increase quickly
+ // with aspect ratio, but that rate of increase would slow down as AR
+ // goes to infinity.
+
+ double CD_fp = 2 / (1 + exp(this->CD_fp_k1 + this->CD_fp_k2 * (
+ std::max(this->AR, 1 / this->AR))));
+ CD = (1 - sigma) * (this->CD0 + (CL*CL) / (GZ_PI * this->AR *
+ this->eff)) + sigma * abs(
+ CD_fp * (0.5 - 0.5 * cos(2 * this->alpha)));
+
+ // Add in control surface terms
+ CD = CD + CD_ctrl_tot;
+
+ // Place drag at cp
+ gz::math::Vector3d drag = (CD * dyn_pres + (this->CDp * (rr*span/2) *
+ half_rho_vel) + (
+ this->CDq * (pr*this->mac/2) * half_rho_vel) +
+ (this->CDr * (yr*span/2) * half_rho_vel)) * (this->area *
+ (-1*stability_x_axis));
+
+ // Compute sideforce coefficient, CY
+ // Start with angle of attack, sideslip, and control terms
+ double CY = this->CYa * this->alpha + this->CYb * this->beta + CY_ctrl_tot;
+
+ gz::math::Vector3d sideforce = (CY * dyn_pres + (this->CYp * (
+ rr*span/2) * half_rho_vel) + (this->CYq * (pr*this->mac/2) *
+ half_rho_vel) + (this->CYr * (yr*span/2) * half_rho_vel)) *
+ (this->area * stability_y_axis);
+
+ // The Cm is divided in three sections: alpha>stall angle, alpha<-stall
+ // angle-stall anglestall angle region the Cm is assumed to always be positive or
+ // zero, in the alpha<-stall angle the Cm is assumed to always be
+ // negative or zero.
+
+ double Cem{0.0};
+
+ if (alpha > this->alphaStall)
+ {
+ Cem = this->Cem0 + (this->Cema * this->alphaStall +
+ this->CemaStall * (this->alpha - this->alphaStall));
+ }
+ else if (alpha < -this->alphaStall)
+ {
+ Cem = this->Cem0 + (-this->Cema * this->alphaStall +
+ this->CemaStall * (this->alpha + this->alphaStall));
+ }
+ else
+ {
+ Cem = this->Cem0 + this->Cema * this->alpha;
+ }
+ // Add sideslip effect, if any
+ Cem = this->Cemb * this->beta;
+
+ Cem += Cem_ctrl_tot;
+
+ gz::math::Vector3d pm = ((Cem * dyn_pres) + (this->Cemp * (
+ rr*span/2) * half_rho_vel) + (this->Cemq * (pr*this->mac/2) *
+ half_rho_vel) + (this->Cemr * (yr*span/2) * half_rho_vel)) *
+ (this->area * this->mac * body_y_axis);
+
+ // Compute roll moment coefficient, Cell
+ // Start with angle of attack, sideslip, and control terms
+ double Cell = this-> Cella * this->alpha + this->Cellb *
+ this-> beta + Cell_ctrl_tot;
+ gz::math::Vector3d rm = ((Cell * dyn_pres) + (this->Cellp * (
+ rr*span/2) * half_rho_vel) + (this->Cellq * (pr*this->mac/2) *
+ half_rho_vel) + (this->Cellr * (yr*span/2) * half_rho_vel)) *
+ (this->area * span * body_x_axis);
+
+ // Compute yaw moment coefficient, Cen
+ // Start with angle of attack, sideslip, and control terms
+ double Cen = this->Cena * this->alpha + this->Cenb * this->beta +
+ Cen_ctrl_tot;
+ gz::math::Vector3d ym = ((Cen * dyn_pres) + (this->Cenp * (
+ rr*span/2) * half_rho_vel) + (this->Cenq * (pr*this->mac/2) *
+ half_rho_vel) + (this->Cenr * (yr*span/2) * half_rho_vel)) *
+ (this->area * span * body_z_axis);
+
+ // Compute moment (torque)
+ gz::math::Vector3d moment = pm+rm+ym;
+
+ // Compute force about cg in inertial frame
+ gz::math::Vector3d force = lift + drag + sideforce;
+ gz::math::Vector3d torque = moment;
+
+ // Correct for nan or inf
+ force.Correct();
+ this->cp.Correct();
+ torque.Correct();
+
+ // positions
+ const auto totalTorque = torque + cpWorld.Cross(force);
+ Link link(this->linkEntity);
+ link.AddWorldWrench(_ecm, force, totalTorque);
+}
+
+//////////////////////////////////////////////////
+void AdvancedLiftDrag::Configure(const Entity &_entity,
+ const std::shared_ptr &_sdf,
+ EntityComponentManager &_ecm, EventManager &)
+{
+ this->dataPtr->model = Model(_entity);
+ if (!this->dataPtr->model.Valid(_ecm))
+ {
+ gzerr << "Advanced LiftDrag system should be attached to a model entity."
+ << "Failed to initialize." << std::endl;
+ return;
+ }
+ this->dataPtr->sdfConfig = _sdf->Clone();
+}
+
+//////////////////////////////////////////////////
+void AdvancedLiftDrag::PreUpdate(const UpdateInfo &_info,
+ EntityComponentManager &_ecm)
+{
+ GZ_PROFILE("AdvancedLiftDrag::PreUpdate");
+
+ // \TODO(anyone) Support rewind
+ if (_info.dt < std::chrono::steady_clock::duration::zero())
+ {
+ gzwarn << "Detected jump back in time ["
+ << std::chrono::duration_cast(_info.dt).count()
+ << "s]. System may not work properly." << std::endl;
+ }
+
+ if (!this->dataPtr->initialized)
+ {
+ // We call Load here instead of Configure because we can't be guaranteed
+ // that all entities have been created when Configure is called
+ this->dataPtr->Load(_ecm, this->dataPtr->sdfConfig);
+ this->dataPtr->initialized = true;
+
+ if (this->dataPtr->validConfig)
+ {
+ Link link(this->dataPtr->linkEntity);
+ link.EnableVelocityChecks(_ecm, true);
+
+ for(int i = 0; i < this->dataPtr->num_ctrl_surfaces; i++){
+
+ if ((this->dataPtr->controlJoints[i]!= kNullEntity) &&
+ !_ecm.Component(this->dataPtr->
+ controlJoints[i]))
+ {
+ _ecm.CreateComponent(this->dataPtr->controlJoints[i],
+ components::JointPosition());
+ }
+ }
+ }
+ }
+
+ if (_info.paused)
+ return;
+
+ // This is not an "else" because "initialized" can be set in the if block
+ // above
+ if (this->dataPtr->initialized && this->dataPtr->validConfig)
+ {
+
+ this->dataPtr->Update(_ecm);
+ }
+}
+
+GZ_ADD_PLUGIN(AdvancedLiftDrag,
+ System,
+ AdvancedLiftDrag::ISystemConfigure,
+ AdvancedLiftDrag::ISystemPreUpdate)
+
+GZ_ADD_PLUGIN_ALIAS(AdvancedLiftDrag,
+ "gz::sim::systems::AdvancedLiftDrag")
diff --git a/src/systems/advanced_lift_drag/AdvancedLiftDrag.hh b/src/systems/advanced_lift_drag/AdvancedLiftDrag.hh
new file mode 100644
index 0000000000..31b420d745
--- /dev/null
+++ b/src/systems/advanced_lift_drag/AdvancedLiftDrag.hh
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2019 Open Source Robotics Foundation
+ *
+ * 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.
+ *
+ */
+
+#ifndef GZ_SIM_SYSTEMS_ADVANCED_LIFT_DRAG_HH_
+#define GZ_SIM_SYSTEMS_ADVANCED_LIFT_DRAG_HH_
+
+#include
+#include
+
+namespace gz
+{
+namespace sim
+{
+// Inline bracket to help doxygen filtering.
+inline namespace GZ_SIM_VERSION_NAMESPACE {
+namespace systems
+{
+ // Forward declaration
+ class AdvancedLiftDragPrivate;
+
+ /// \brief The LiftDrag system computes lift and drag forces enabling
+ /// simulation of aerodynamic robots.
+ ///
+ ///
+ /// A tool can be found at the link below that automatically generates
+ /// the Advanced Lift Drag plugin for you. All that is needed is to
+ /// provide some physical parameters of the model. The README contains
+ /// all necessary setup and usage steps.
+ ///
+ /// https://github.com/PX4/PX4-Autopilot/blob/main/Tools/simulation/
+ /// gz/tools/avl_automation/
+ ///
+ /// Note: Wind calculations can be enabled by setting the wind parameter
+ /// in the world file.
+ ///
+ /// ## System Parameters
+ ///
+ /// - ``: Name of the link affected by the group of lift/drag
+ /// properties. This can be a scoped name to reference links in
+ /// nested models. \sa entitiesFromScopedName to learn more
+ /// about scoped names.
+ /// - ``: Density of the fluid this model is suspended in.
+ /// - ``: Surface area of the link.
+ /// - ``: Lift Coefficient at zero angle of attack
+ /// - ``: Pitching moment coefficient at zero angle of attack
+ /// - ``: Drag Coefficient at zero angle of attack
+ /// - ``: dCL/da (slope of CL-alpha curve) Slope of the first
+ /// portion of the alpha-lift coefficient curve.
+ /// - ``: dCy/da (sideforce slope wrt alpha)
+ /// - ``: dCl/da (roll moment slope wrt alpha)
+ /// - ``: dCm/da (pitching moment slope wrt alpha - before stall)
+ /// - ``: dCn/da (yaw moment slope wrt alpha)
+ /// - ``: dCL/dbeta (lift coefficient slope wrt beta)
+ /// - ``: dCY/dbeta (side force slope wrt beta)
+ /// - ``: dCl/dbeta (roll moment slope wrt beta)
+ /// - ``: dCm/dbeta (pitching moment slope wrt beta)
+ /// - ``: dCn/dbeta (yaw moment slope wrt beta)
+ /// - ``: angle of attack when airfoil stalls
+ /// - ``: Slope of the Cm-alpha curve after stall
+ /// - ``: 3-vector replacing the center of pressure in the original
+ /// LiftDragPlugin.
+ /// - ``: dCD/dp (drag coefficient slope wrt roll rate)
+ /// - ``: dCY/dp (sideforce slope wrt roll rate)
+ /// - ``: dCL/dp (lift coefficient slope wrt roll rate)
+ /// - ``: dCl/dp (roll moment slope wrt roll rate)
+ /// - ``: dCn/dp (yaw moment slope wrt roll rate)
+ /// - ``: dCD/dq (drag coefficient slope wrt pitching rate)
+ /// - ``: dCY/dq (side force slope wrt pitching rate)
+ /// - ``: dCL/dq (lift coefficient slope wrt pitching rate)
+ /// - ``: dCl/dq (roll moment slope wrt pitching rate)
+ /// - ``: dCm/dq (pitching moment slope wrt pitching rate)
+ /// - ``: dCn/dq (yaw moment slope wrt pitching rate)
+ /// - ``: dCD/dr (drag coefficient slope wrt yaw rate)
+ /// - ``: dCY/dr (side force slope wrt yaw rate)
+ /// - ``: dCL/dr (lift coefficient slope wrt yaw rate)
+ /// - ``: dCl/dr (roll moment slope wrt yaw rate)
+ /// - ``: dCm/dr (pitching moment slope wrt yaw rate)
+ /// - ``: dCn/dr (yaw moment slope wrt yaw rate)
+ /// - ``: Number of control surfaces
+ /// - ``: Vector that points to the joints that connect to the
+ /// control surface
+ /// - `