diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index cf70ca6852..705db4f35d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,13 +14,12 @@ jobs:
uses: ignition-tooling/action-ignition-ci@bionic
with:
codecov-token: ${{ secrets.CODECOV_TOKEN }}
- # TODO(anyone) Enable Focal CI and fix failing tests
- # focal-ci:
- # runs-on: ubuntu-latest
- # name: Ubuntu Focal CI
- # steps:
- # - name: Checkout
- # uses: actions/checkout@v2
- # - name: Compile and test
- # id: ci
- # uses: ignition-tooling/action-ignition-ci@focal
+ focal-ci:
+ runs-on: ubuntu-latest
+ name: Ubuntu Focal CI
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Compile and test
+ id: ci
+ uses: ignition-tooling/action-ignition-ci@focal
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c80396d494..c662f6ac03 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,4 +1,4 @@
-cmake_minimum_required(VERSION 3.5.1 FATAL_ERROR)
+cmake_minimum_required(VERSION 3.10.2 FATAL_ERROR)
#============================================================================
# Initialize the project
@@ -38,7 +38,7 @@ endif()
# Search for project-specific dependencies
#============================================================================
-ign_find_package(sdformat11 REQUIRED)
+ign_find_package(sdformat11 REQUIRED VERSION 11.1)
set(SDF_VER ${sdformat11_VERSION_MAJOR})
#--------------------------------------
@@ -53,7 +53,7 @@ set(IGN_TRANSPORT_VER ${ignition-transport10_VERSION_MAJOR})
#--------------------------------------
# Find ignition-msgs
-ign_find_package(ignition-msgs7 REQUIRED)
+ign_find_package(ignition-msgs7 REQUIRED VERSION 7.1)
set(IGN_MSGS_VER ${ignition-msgs7_VERSION_MAJOR})
#--------------------------------------
diff --git a/Changelog.md b/Changelog.md
index 2629c4db0d..6df3c1c169 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -135,6 +135,78 @@
## Ignition Gazebo 4.x
+### Ignition Gazebo 4.x.x (202x-xx-xx)
+
+### Ignition Gazebo 4.8.0 (2021-04-22)
+
+1. Add odometry publisher system.
+ * [Pull Request 547](https://github.com/ignitionrobotics/ign-gazebo/pull/547)
+
+1. Patch particle emitter2 service.
+ * [Pull Request 777](https://github.com/ignitionrobotics/ign-gazebo/pull/777)
+
+### Ignition Gazebo 4.7.0 (2021-04-09)
+
+1. Particle emitter based on SDF.
+ * [Pull Request 730](https://github.com/ignitionrobotics/ign-gazebo/pull/730)
+
+1. Fix log playback for particle emitters.
+ * [Pull Request 745](https://github.com/ignitionrobotics/ign-gazebo/pull/745)
+
+1. ECM's ChangedState gets message with modified components.
+ * [Pull Request 742](https://github.com/ignitionrobotics/ign-gazebo/pull/742)
+
+1. Fixed collision visual bounding boxes.
+ * [Pull Request 746](https://github.com/ignitionrobotics/ign-gazebo/pull/746)
+
+1. Fix compute_rtfs arguments.
+ * [Pull Request 737](https://github.com/ignitionrobotics/ign-gazebo/pull/737)
+
+1. Validate step size and RTF parameters.
+ * [Pull Request 740](https://github.com/ignitionrobotics/ign-gazebo/pull/740)
+
+1. Use Protobuf_IMPORT_DIRS instead of PROTOBUF_IMPORT_DIRS for
+ compatibility with Protobuf CMake config.
+ * [Pull Request 715](https://github.com/ignitionrobotics/ign-gazebo/pull/715)
+
+1. Do not pass -Wno-unused-parameter to MSVC compiler.
+ * [Pull Request 716](https://github.com/ignitionrobotics/ign-gazebo/pull/716)
+
+1. Support configuring particle scatter ratio in particle emitter system.
+ * [Pull Request 674](https://github.com/ignitionrobotics/ign-gazebo/pull/674)
+
+1. Fix diffuse and ambient values for ackermann example.
+ * [Pull Request 707](https://github.com/ignitionrobotics/ign-gazebo/pull/707)
+
+1. Scenebroadcaster sensors.
+ * [Pull Request 698](https://github.com/ignitionrobotics/ign-gazebo/pull/698)
+
+1. Add thermal camera test for object temperature below 0.
+ * [Pull Request 621](https://github.com/ignitionrobotics/ign-gazebo/pull/621)
+
+1. [BULLET] Making GetContactsFromLastStepFeature optional in Collision Features
+ * [Pull Request 690](https://github.com/ignitionrobotics/ign-gazebo/pull/690)
+
+1. Fix joint controller GUI test.
+ * [Pull Request 697](https://github.com/ignitionrobotics/ign-gazebo/pull/697)
+
+1. Quiet warnings from Joint State Publisher.
+ * [Pull Request 696](https://github.com/ignitionrobotics/ign-gazebo/pull/696)
+
+1. Ackermann Steering Plugin.
+ * [Pull Request 618](https://github.com/ignitionrobotics/ign-gazebo/pull/618)
+
+1. Remove bounding box when model is deleted
+ * [Pull Request 675](https://github.com/ignitionrobotics/ign-gazebo/pull/675)
+
+1. Cache link poses to improve performance.
+ * [Pull Request 669](https://github.com/ignitionrobotics/ign-gazebo/pull/669)
+
+1. Check empty world name in Scene3d.
+ * [Pull Request 662](https://github.com/ignitionrobotics/ign-gazebo/pull/662)
+
+1. All changes up to 3.8.0.
+
### Ignition Gazebo 4.6.0 (2021-03-01)
1. Use a custom data structure to manage entity feature maps.
@@ -460,6 +532,115 @@
## Ignition Gazebo 3.x
+### Ignition Gazebo 3.X.X (202X-XX-XX)
+
+### Ignition Gazebo 3.8.0 (2021-03-17)
+
+1. Add joint position controller GUI, also enable tests for GUI plugins
+ * [Pull request #534](https://github.com/ignitionrobotics/ign-gazebo/pull/534)
+
+1. Remove visibility from headers that are not installed
+ * [Pull request #665](https://github.com/ignitionrobotics/ign-gazebo/pull/665)
+
+1. Added screenshot to toolbar
+ * [Pull request #588](https://github.com/ignitionrobotics/ign-gazebo/pull/588)
+
+1. Improve ign tool support on macOS
+ * [Pull request #477](https://github.com/ignitionrobotics/ign-gazebo/pull/477)
+
+1. change nullptr to a int ptr for qt 5.15.2 bug
+ * [Pull request #527](https://github.com/ignitionrobotics/ign-gazebo/pull/527)
+
+1. Kinetic energy monitor plugin
+ * [Pull request #492](https://github.com/ignitionrobotics/ign-gazebo/pull/492)
+
+1. Use a std::promise/std::future to avoid busy waiting the step ack messages in NetworkManagerPrimary
+ * [Pull request #470](https://github.com/ignitionrobotics/ign-gazebo/pull/470)
+
+1. clarified performer example
+ * [Pull request #390](https://github.com/ignitionrobotics/ign-gazebo/pull/390)
+
+1. Add tutorial tweaks
+ * [Pull request #380](https://github.com/ignitionrobotics/ign-gazebo/pull/380)
+
+1. Fix Qt5 warnings for using anchors
+ * [Pull request #363](https://github.com/ignitionrobotics/ign-gazebo/pull/363)
+
+1. Update codeowners
+ * [Pull request #305](https://github.com/ignitionrobotics/ign-gazebo/pull/305)
+
+1. Qt auto scale factor for HiDPI displays
+ * [Pull request #291](https://github.com/ignitionrobotics/ign-gazebo/pull/291)
+
+1. Fix yaw units
+ * [Pull request #238](https://github.com/ignitionrobotics/ign-gazebo/pull/238)
+
+1. Fixed docblock showGrid
+ * [Pull request #152](https://github.com/ignitionrobotics/ign-gazebo/pull/152)
+
+1. Fix entity tree for large worlds
+ * [Pull request #673](https://github.com/ignitionrobotics/ign-gazebo/pull/673)
+
+1. Master branch updates
+ * [Pull request #672](https://github.com/ignitionrobotics/ign-gazebo/pull/672)
+
+1. Backport #561: Use common::setenv
+ * [Pull request #666](https://github.com/ignitionrobotics/ign-gazebo/pull/666)
+
+1. Use a custom data structure to manage entity feature maps
+ * [Pull request #586](https://github.com/ignitionrobotics/ign-gazebo/pull/586)
+
+1. Limit scene broadcast publications when paused
+ * [Pull request #497](https://github.com/ignitionrobotics/ign-gazebo/pull/497)
+
+1. Fix flaky SceneBoradcaster test
+ * [Pull request #641](https://github.com/ignitionrobotics/ign-gazebo/pull/641)
+
+1. Add TF/Pose_V publisher in DiffDrive
+ * [Pull request #548](https://github.com/ignitionrobotics/ign-gazebo/pull/548)
+
+1. 👩🌾 Relax performance test
+ * [Pull request #640](https://github.com/ignitionrobotics/ign-gazebo/pull/640)
+
+1. 👩🌾 Improve velocity control test
+ * [Pull request #642](https://github.com/ignitionrobotics/ign-gazebo/pull/642)
+
+1. Add `laser_retro` support
+ * [Pull request #603](https://github.com/ignitionrobotics/ign-gazebo/pull/603)
+
+1. Fix pose of plane visual with non-default normal vector
+ * [Pull request #574](https://github.com/ignitionrobotics/ign-gazebo/pull/574)
+
+1. Add About dialog
+ * [Pull request #609](https://github.com/ignitionrobotics/ign-gazebo/pull/609)
+
+1. Make topics configurable for joint controllers
+ * [Pull request #584](https://github.com/ignitionrobotics/ign-gazebo/pull/584)
+
+1. Also use Ignition GUI render event
+ * [Pull request #598](https://github.com/ignitionrobotics/ign-gazebo/pull/598)
+
+1. Tutorial on migrating SDF files from Gazebo classic
+ * [Pull request #400](https://github.com/ignitionrobotics/ign-gazebo/pull/400)
+
+1. Visualize collisions
+ * [Pull request #531](https://github.com/ignitionrobotics/ign-gazebo/pull/531)
+
+1. Backport state update changes from pull request #486
+ * [Pull request #583](https://github.com/ignitionrobotics/ign-gazebo/pull/583)
+
+1. Publish all periodic change components in Scene Broadcaster
+ * [Pull request #544](https://github.com/ignitionrobotics/ign-gazebo/pull/544)
+
+1. added size to `ground_plane` in examples
+ * [Pull request #573](https://github.com/ignitionrobotics/ign-gazebo/pull/573)
+
+1. Parallelize State call in ECM
+ * [Pull request #451](https://github.com/ignitionrobotics/ign-gazebo/pull/451)
+
+1. Non-blocking paths request
+ * [Pull request #555](https://github.com/ignitionrobotics/ign-gazebo/pull/555)
+
### Ignition Gazebo 3.7.0 (2021-01-13)
1. Fix examples in migration plugins tutorial.
diff --git a/examples/worlds/particle_emitter.sdf b/examples/worlds/particle_emitter.sdf
index 58fb36c3be..bf372aeef2 100644
--- a/examples/worlds/particle_emitter.sdf
+++ b/examples/worlds/particle_emitter.sdf
@@ -1,5 +1,10 @@
+
+
+
+
+
+
+
+ 0.001
+ 1.0
+
+
+
+ libignition-physics-tpe-plugin.so
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+ https://fuel.ignitionrobotics.org/1.0/openrobotics/models/fog generator
+
+
+
+
+ true
+ 20 20 0 0 0 0
+
+
+
+ true
+ 10 10 0
+ 1 1 1
+ 25
+ 0.1
+ 0.2
+ 0.5
+ 5
+
+ 1.0 0.0 0.0
+ 1.0 0.0 0.0
+
+
+
+
+
+
+
+
diff --git a/examples/worlds/pendulum_links.sdf b/examples/worlds/pendulum_links.sdf
new file mode 100644
index 0000000000..2f62bab25c
--- /dev/null
+++ b/examples/worlds/pendulum_links.sdf
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+ 0.001
+ 1.0
+
+
+
+
+
+
+
+
+
+ true
+ 0 0 10 0 0 0
+ 0.8 0.8 0.8 1
+ 0.2 0.2 0.2 1
+
+ 1000
+ 0.9
+ 0.01
+ 0.001
+
+ -0.5 0.1 -0.9
+
+
+
+ true
+
+
+
+
+ 0 0 1
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+ 100
+
+
+ 0 0 0.01 0 0 0
+
+
+ 0.8
+ 0.02
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ -0.275 0 1.1 0 0 0
+
+
+ 0.2 0.2 2.2
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ 0 0 0.01 0 0 0
+
+
+ 0.8
+ 0.02
+
+
+
+
+ -0.275 0 1.1 0 0 0
+
+
+ 0.2 0.2 2.2
+
+
+
+
+
+
+ 0 0 2.1 -1.5708 0 0
+ 0
+
+ 0 0 0.5 0 0 0
+
+
+ -0.05 0 0 0 1.5708 0
+
+
+ 0.1
+ 0.3
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ 0 0 1.0 0 1.5708 0
+
+
+ 0.1
+ 0.2
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ 0 0 0.5 0 0 0
+
+
+ 0.1
+ 0.9
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ -0.05 0 0 0 1.5708 0
+
+
+ 0.1
+ 0.3
+
+
+
+
+ 0 0 1.0 0 1.5708 0
+
+
+ 0.1
+ 0.2
+
+
+
+
+ 0 0 0.5 0 0 0
+
+
+ 0.1
+ 0.9
+
+
+
+
+
+
+ 0.25 1.0 2.1 -2 0 0
+ 0
+
+ 0 0 0.5 0 0 0
+
+
+ 0 0 0 0 1.5708 0
+
+
+ 0.08
+ 0.3
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ 0 0 0.5 0 0 0
+
+
+ 0.1
+ 0.9
+
+
+
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+ 0.8 0.8 0.8 1
+
+
+
+ 0 0 0 0 1.5708 0
+
+
+ 0.08
+ 0.3
+
+
+
+
+ 0 0 0.5 0 0 0
+
+
+ 0.1
+ 0.9
+
+
+
+
+
+
+ base
+ upper_link
+
+ 1.0 0 0
+
+
+
+
+ upper_link
+ lower_link
+
+ 1.0 0 0
+
+
+
+
+ base
+ upper_link
+ lower_link
+
+
+
+
diff --git a/include/ignition/gazebo/Conversions.hh b/include/ignition/gazebo/Conversions.hh
index 53d3b025ac..827abab211 100644
--- a/include/ignition/gazebo/Conversions.hh
+++ b/include/ignition/gazebo/Conversions.hh
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -47,6 +48,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -639,6 +641,42 @@ namespace ignition
/// \return Axis aligned box object.
template<>
math::AxisAlignedBox convert(const msgs::AxisAlignedBox &_in);
+
+ /// \brief Generic conversion from a particle emitter SDF object to another
+ /// type.
+ /// \param[in] _in Particle emitter SDF object.
+ /// \return Conversion result.
+ /// \tparam Out Output type.
+ template
+ Out convert(const sdf::ParticleEmitter &/*_in*/)
+ {
+ Out::ConversionNotImplemented;
+ }
+
+ /// \brief Specialized conversion from a particle emitter SDF object to
+ /// a particle emitter message object.
+ /// \param[in] _in Particle emitter SDF object.
+ /// \return Particle emitter message.
+ template<>
+ msgs::ParticleEmitter convert(const sdf::ParticleEmitter &_in);
+
+ /// \brief Generic conversion from a particle emitter SDF object to another
+ /// type.
+ /// \param[in] _in Particle emitter SDF object.
+ /// \return Conversion result.
+ /// \tparam Out Output type.
+ template
+ Out convert(const msgs::ParticleEmitter &/*_in*/)
+ {
+ Out::ConversionNotImplemented;
+ }
+
+ /// \brief Specialized conversion from a particle emitter SDF object to
+ /// a particle emitter message object.
+ /// \param[in] _in Particle emitter SDF object.
+ /// \return Particle emitter message.
+ template<>
+ sdf::ParticleEmitter convert(const msgs::ParticleEmitter &_in);
}
}
}
diff --git a/include/ignition/gazebo/EntityComponentManager.hh b/include/ignition/gazebo/EntityComponentManager.hh
index e2098d5fa1..707c52ce2d 100644
--- a/include/ignition/gazebo/EntityComponentManager.hh
+++ b/include/ignition/gazebo/EntityComponentManager.hh
@@ -475,11 +475,9 @@ namespace ignition
/// \brief Get a message with the serialized state of all entities and
/// components that are changing in the current iteration
///
- /// Currently supported:
+ /// This includes:
/// * New entities and all of their components
/// * Removed entities and all of their components
- ///
- /// Future work:
/// * Entities which had a component added
/// * Entities which had a component removed
/// * Entities which had a component modified
@@ -536,11 +534,9 @@ namespace ignition
/// \brief Get a message with the serialized state of all entities and
/// components that are changing in the current iteration
///
- /// Currently supported:
+ /// This includes:
/// * New entities and all of their components
/// * Removed entities and all of their components
- ///
- /// Future work:
/// * Entities which had a component added
/// * Entities which had a component removed
/// * Entities which had a component modified
diff --git a/include/ignition/gazebo/SdfEntityCreator.hh b/include/ignition/gazebo/SdfEntityCreator.hh
index a3590c1da9..e5c2442279 100644
--- a/include/ignition/gazebo/SdfEntityCreator.hh
+++ b/include/ignition/gazebo/SdfEntityCreator.hh
@@ -26,6 +26,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -140,6 +141,13 @@ namespace ignition
/// \sa CreateEntities(const sdf::Model *)
public: Entity CreateEntities(const sdf::Sensor *_sensor);
+ /// \brief Create all entities that exist in the
+ /// sdf::ParticleEmitter object.
+ /// \param[in] _emitter SDF ParticleEmitter object.
+ /// \return ParticleEmitter entity.
+ /// \sa CreateEntities(const sdf::Link *)
+ public: Entity CreateEntities(const sdf::ParticleEmitter *_emitter);
+
/// \brief Request an entity deletion. This will insert the request
/// into a queue. The queue is processed toward the end of a simulation
/// update step.
diff --git a/include/ignition/gazebo/Util.hh b/include/ignition/gazebo/Util.hh
index cfa49e98d3..83c5ba7bef 100644
--- a/include/ignition/gazebo/Util.hh
+++ b/include/ignition/gazebo/Util.hh
@@ -155,6 +155,26 @@ namespace ignition
std::string IGNITION_GAZEBO_VISIBLE validTopic(
const std::vector &_topics);
+ /// \brief Helper function that returns a valid Ignition Transport topic
+ /// consisting of the scoped name for the provided entity.
+ ///
+ /// For example, if the provided entity has a scoped name of
+ /// `my_model::my_link::my_sensor` then the resulting topic name will
+ /// be `/model/my_model/link/my_link/sensor/my_sensor`. If _excludeWorld
+ /// is false, then the topic name will be prefixed by `/world/WORLD_NAME/`,
+ /// where `WORLD_NAME` is the name of the world.
+ ///
+ /// \param[in] _entity The entity to generate the topic name for.
+ /// \param[in] _ecm The entity component manager.
+ /// \param[in] _excludeWorld True to exclude the world name from the topic.
+ /// \return An Ignition Transport topic name based on the scoped name of
+ /// the provided entity, or empty string if a topic name could not be
+ /// generated.
+ std::string IGNITION_GAZEBO_VISIBLE topicFromScopedName(
+ const Entity &_entity,
+ const EntityComponentManager &_ecm,
+ bool _excludeWorld = true);
+
/// \brief Environment variable holding resource paths.
const std::string kResourcePathEnv{"IGN_GAZEBO_RESOURCE_PATH"};
@@ -168,6 +188,7 @@ namespace ignition
/// \brief Environment variable holding paths to custom rendering engine
/// plugins.
const std::string kRenderPluginPathEnv{"IGN_GAZEBO_RENDER_ENGINE_PATH"};
+
}
}
}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c83a3d1a67..9023a779ef 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -10,10 +10,12 @@ set_source_files_properties(
)
# Suppress compiler warnings in generated protobuf C++ code.
-set_source_files_properties(
- ${PROTO_PRIVATE_SRC}
- COMPILE_FLAGS -Wno-unused-parameter
-)
+if(NOT MSVC)
+ set_source_files_properties(
+ ${PROTO_PRIVATE_SRC}
+ COMPILE_FLAGS -Wno-unused-parameter
+ )
+endif()
set(network_sources
network/NetworkConfig.cc
diff --git a/src/Conversions.cc b/src/Conversions.cc
index 2adb6efe69..322b912221 100644
--- a/src/Conversions.cc
+++ b/src/Conversions.cc
@@ -1496,3 +1496,130 @@ math::AxisAlignedBox ignition::gazebo::convert(const msgs::AxisAlignedBox &_in)
out.Max() = msgs::Convert(_in.max_corner());
return out;
}
+
+//////////////////////////////////////////////////
+template<>
+IGNITION_GAZEBO_VISIBLE
+msgs::ParticleEmitter ignition::gazebo::convert(const sdf::ParticleEmitter &_in)
+{
+ msgs::ParticleEmitter out;
+ out.set_name(_in.Name());
+ switch(_in.Type())
+ {
+ default:
+ case sdf::ParticleEmitterType::POINT:
+ out.set_type(msgs::ParticleEmitter::POINT);
+ break;
+ case sdf::ParticleEmitterType::BOX:
+ out.set_type(msgs::ParticleEmitter::BOX);
+ break;
+ case sdf::ParticleEmitterType::CYLINDER:
+ out.set_type(msgs::ParticleEmitter::CYLINDER);
+ break;
+ case sdf::ParticleEmitterType::ELLIPSOID:
+ out.set_type(msgs::ParticleEmitter::ELLIPSOID);
+ break;
+ }
+
+ msgs::Set(out.mutable_pose(), _in.RawPose());
+ msgs::Set(out.mutable_size(), _in.Size());
+ msgs::Set(out.mutable_particle_size(), _in.ParticleSize());
+ out.mutable_rate()->set_data(_in.Rate());
+ out.mutable_duration()->set_data(_in.Duration());
+ out.mutable_emitting()->set_data(_in.Emitting());
+ out.mutable_lifetime()->set_data(_in.Lifetime());
+ if (_in.Material())
+ {
+ out.mutable_material()->CopyFrom(convert(*_in.Material()));
+ }
+ out.mutable_min_velocity()->set_data(_in.MinVelocity());
+ out.mutable_max_velocity()->set_data(_in.MaxVelocity());
+ msgs::Set(out.mutable_color_start(), _in.ColorStart());
+ msgs::Set(out.mutable_color_end(), _in.ColorEnd());
+ out.mutable_scale_rate()->set_data(_in.ScaleRate());
+ out.mutable_color_range_image()->set_data(_in.ColorRangeImage());
+
+ if (!_in.ColorRangeImage().empty())
+ {
+ std::string path = asFullPath(_in.ColorRangeImage(), _in.FilePath());
+
+ common::SystemPaths systemPaths;
+ systemPaths.SetFilePathEnv(kResourcePathEnv);
+ std::string absolutePath = systemPaths.FindFile(path);
+
+ if (!absolutePath.empty())
+ out.mutable_color_range_image()->set_data(absolutePath);
+ }
+
+ /// \todo(nkoenig) Modify the particle_emitter.proto file to
+ /// have a topic field.
+ if (!_in.Topic().empty())
+ {
+ auto header = out.mutable_header()->add_data();
+ header->set_key("topic");
+ header->add_value(_in.Topic());
+ }
+
+ return out;
+}
+
+//////////////////////////////////////////////////
+template<>
+IGNITION_GAZEBO_VISIBLE
+sdf::ParticleEmitter ignition::gazebo::convert(const msgs::ParticleEmitter &_in)
+{
+ sdf::ParticleEmitter out;
+ out.SetName(_in.name());
+ switch(_in.type())
+ {
+ default:
+ case msgs::ParticleEmitter::POINT:
+ out.SetType(sdf::ParticleEmitterType::POINT);
+ break;
+ case msgs::ParticleEmitter::BOX:
+ out.SetType(sdf::ParticleEmitterType::BOX);
+ break;
+ case msgs::ParticleEmitter::CYLINDER:
+ out.SetType(sdf::ParticleEmitterType::CYLINDER);
+ break;
+ case msgs::ParticleEmitter::ELLIPSOID:
+ out.SetType(sdf::ParticleEmitterType::ELLIPSOID);
+ break;
+ }
+ out.SetRawPose(msgs::Convert(_in.pose()));
+ out.SetSize(msgs::Convert(_in.size()));
+ out.SetParticleSize(msgs::Convert(_in.particle_size()));
+ out.SetMinVelocity(msgs::Convert(_in.min_velocity()));
+ out.SetMaxVelocity(msgs::Convert(_in.max_velocity()));
+ out.SetColorStart(msgs::Convert(_in.color_start()));
+ out.SetColorEnd(msgs::Convert(_in.color_end()));
+
+ if (_in.has_material())
+ {
+ out.SetMaterial(convert(_in.material()));
+ }
+
+ if (_in.has_rate())
+ out.SetRate(_in.rate().data());
+ if (_in.has_duration())
+ out.SetDuration(_in.duration().data());
+ if (_in.has_emitting())
+ out.SetEmitting(_in.emitting().data());
+ if (_in.has_lifetime())
+ out.SetLifetime(_in.lifetime().data());
+ if (_in.has_scale_rate())
+ out.SetScaleRate(_in.scale_rate().data());
+ if (_in.has_color_range_image())
+ out.SetColorRangeImage(_in.color_range_image().data());
+
+ for (int i = 0; i < _in.header().data_size(); ++i)
+ {
+ auto data = _in.header().data(i);
+ if (data.key() == "topic" && data.value_size() > 0)
+ {
+ out.SetTopic(data.value(0));
+ }
+ }
+
+ return out;
+}
diff --git a/src/Conversions_TEST.cc b/src/Conversions_TEST.cc
index 9d3a1ba5f2..e957851bda 100644
--- a/src/Conversions_TEST.cc
+++ b/src/Conversions_TEST.cc
@@ -941,3 +941,83 @@ TEST(Conversions, Actor)
EXPECT_EQ(math::Pose3d(6, 5, 4, 0, 0, 0),
newActor.TrajectoryByIndex(0)->WaypointByIndex(0)->Pose());
}
+
+/////////////////////////////////////////////////
+TEST(Conversions, ParticleEmitter)
+{
+ sdf::ParticleEmitter emitter;
+ emitter.SetName("my_emitter");
+ emitter.SetType(sdf::ParticleEmitterType::BOX);
+ emitter.SetEmitting(false);
+ emitter.SetDuration(12);
+ emitter.SetLifetime(56);
+ emitter.SetRate(0.5);
+ emitter.SetScaleRate(1.2);
+ emitter.SetMinVelocity(0.1);
+ emitter.SetMaxVelocity(0.2);
+ emitter.SetSize(math::Vector3d(1, 2, 3));
+ emitter.SetParticleSize(math::Vector3d(4, 5, 6));
+ emitter.SetColorStart(math::Color(0.1, 0.2, 0.3));
+ emitter.SetColorEnd(math::Color(0.4, 0.5, 0.6));
+ emitter.SetColorRangeImage("range_image");
+ emitter.SetTopic("my_topic");
+ emitter.SetRawPose(math::Pose3d(1, 2, 3, 0, 0, 0));
+
+ sdf::Material material;
+ sdf::Pbr pbr;
+ sdf::PbrWorkflow workflow;
+ workflow.SetType(sdf::PbrWorkflowType::METAL);
+ workflow.SetAlbedoMap("albedo_map.png");
+ pbr.SetWorkflow(workflow.Type(), workflow);
+ material.SetPbrMaterial(pbr);
+
+ emitter.SetMaterial(material);
+
+ // Convert SDF to a message.
+ msgs::ParticleEmitter emitterMsg = convert(emitter);
+
+ EXPECT_EQ("my_emitter", emitterMsg.name());
+ EXPECT_EQ(msgs::ParticleEmitter::BOX, emitterMsg.type());
+ EXPECT_FALSE(emitterMsg.emitting().data());
+ EXPECT_NEAR(12, emitterMsg.duration().data(), 1e-3);
+ EXPECT_NEAR(56, emitterMsg.lifetime().data(), 1e-3);
+ EXPECT_NEAR(0.5, emitterMsg.rate().data(), 1e-3);
+ EXPECT_NEAR(1.2, emitterMsg.scale_rate().data(), 1e-3);
+ EXPECT_NEAR(0.1, emitterMsg.min_velocity().data(), 1e-3);
+ EXPECT_NEAR(0.2, emitterMsg.max_velocity().data(), 1e-3);
+ EXPECT_EQ(math::Vector3d(1, 2, 3), msgs::Convert(emitterMsg.size()));
+ EXPECT_EQ(math::Vector3d(4, 5, 6), msgs::Convert(emitterMsg.particle_size()));
+ EXPECT_EQ(math::Color(0.1, 0.2, 0.3),
+ msgs::Convert(emitterMsg.color_start()));
+ EXPECT_EQ(math::Color(0.4, 0.5, 0.6), msgs::Convert(emitterMsg.color_end()));
+ EXPECT_EQ("range_image", emitterMsg.color_range_image().data());
+
+ auto header = emitterMsg.header().data(0);
+ EXPECT_EQ("topic", header.key());
+ EXPECT_EQ("my_topic", header.value(0));
+
+ EXPECT_EQ(math::Pose3d(1, 2, 3, 0, 0, 0), msgs::Convert(emitterMsg.pose()));
+
+ auto pbrMsg = emitterMsg.material().pbr();
+ EXPECT_EQ(msgs::Material::PBR::METAL, pbrMsg.type());
+ EXPECT_EQ("albedo_map.png", pbrMsg.albedo_map());
+
+ // Convert the message back to SDF.
+ sdf::ParticleEmitter emitter2 = convert(emitterMsg);
+ EXPECT_EQ(emitter2.Name(), emitter.Name());
+ EXPECT_EQ(emitter2.Type(), emitter.Type());
+ EXPECT_EQ(emitter2.Emitting(), emitter.Emitting());
+ EXPECT_NEAR(emitter2.Duration(), emitter.Duration(), 1e-3);
+ EXPECT_NEAR(emitter2.Lifetime(), emitter.Lifetime(), 1e-3);
+ EXPECT_NEAR(emitter2.Rate(), emitter.Rate(), 1e-3);
+ EXPECT_NEAR(emitter2.ScaleRate(), emitter.ScaleRate(), 1e-3);
+ EXPECT_NEAR(emitter2.MinVelocity(), emitter.MinVelocity(), 1e-3);
+ EXPECT_NEAR(emitter2.MaxVelocity(), emitter.MaxVelocity(), 1e-3);
+ EXPECT_EQ(emitter2.Size(), emitter.Size());
+ EXPECT_EQ(emitter2.ParticleSize(), emitter.ParticleSize());
+ EXPECT_EQ(emitter2.ColorStart(), emitter.ColorStart());
+ EXPECT_EQ(emitter2.ColorEnd(), emitter.ColorEnd());
+ EXPECT_EQ(emitter2.ColorRangeImage(), emitter.ColorRangeImage());
+ EXPECT_EQ(emitter2.Topic(), emitter.Topic());
+ EXPECT_EQ(emitter2.RawPose(), emitter.RawPose());
+}
diff --git a/src/EntityComponentManager.cc b/src/EntityComponentManager.cc
index 11047a896c..72299ac516 100644
--- a/src/EntityComponentManager.cc
+++ b/src/EntityComponentManager.cc
@@ -72,6 +72,12 @@ class ignition::gazebo::EntityComponentManagerPrivate
msgs::SerializedStateMap &_msg,
const std::unordered_set &_types = {});
+ /// \brief Add newly modified (created/modified/removed) components to
+ /// modifiedComponents list. The entity is added to the list when it is not
+ /// a newly created entity or is not an entity to be removed
+ /// \param[in] _entity Entity that has component newly modified
+ public: void AddModifiedComponent(const Entity &_entity);
+
/// \brief Map of component storage classes. The key is a component
/// type id, and the value is a pointer to the component storage.
public: std::unordered_map toRemoveEntities;
+ /// \brief Entities that have components newly modified
+ /// (created/modified/removed) but are not entities that have been
+ /// newly created or removed (ie. newlyCreatedEntities or toRemoveEntities).
+ /// This is used for the ChangedState functions
+ public: std::unordered_set modifiedComponents;
+
/// \brief Flag that indicates if all entities should be removed.
public: bool removeAllEntities{false};
@@ -105,7 +117,7 @@ class ignition::gazebo::EntityComponentManagerPrivate
/// NOTE: Any modification of this data structure must be followed
/// by setting `entityComponentsDirty` to true.
public: std::unordered_map> entityComponents;
+ std::unordered_map> entityComponents;
/// \brief A vector of iterators to evenly distributed spots in the
/// `entityComponents` map. Threads in the `State` function use this
@@ -113,7 +125,7 @@ class ignition::gazebo::EntityComponentManagerPrivate
/// is recalculated if `entityComponents` is changed (when
/// `entityComponentsDirty` == true).
public: std::vector>::iterator>
+ std::unordered_map>::iterator>
entityComponentIterators;
/// \brief A mutex to protect newly created entities.
@@ -305,8 +317,7 @@ void EntityComponentManager::ProcessRemoveEntityRequests()
{
for (const auto &key : entityIter->second)
{
- this->dataPtr->components.at(key.second.first)->Remove(
- key.second.second);
+ this->dataPtr->components.at(key.first)->Remove(key.second);
}
// Remove the entry in the entityComponent map
@@ -354,6 +365,8 @@ bool EntityComponentManager::RemoveComponent(
this->UpdateViews(_entity);
+ this->dataPtr->AddModifiedComponent(_entity);
+
// Add component to map of removed components
{
std::lock_guard lock(this->dataPtr->removedComponentsMutex);
@@ -424,12 +437,14 @@ ComponentState EntityComponentManager::ComponentState(const Entity _entity,
if (typeKey == ecIter->second.end())
return result;
- if (this->dataPtr->oneTimeChangedComponents.find(typeKey->second) !=
+ ComponentKey key{_typeId, typeKey->second};
+
+ if (this->dataPtr->oneTimeChangedComponents.find(key) !=
this->dataPtr->oneTimeChangedComponents.end())
{
result = ComponentState::OneTimeChange;
}
- else if (this->dataPtr->periodicChangedComponents.find(typeKey->second) !=
+ else if (this->dataPtr->periodicChangedComponents.find(key) !=
this->dataPtr->periodicChangedComponents.end())
{
result = ComponentState::PeriodicChange;
@@ -529,6 +544,8 @@ ComponentKey EntityComponentManager::CreateComponentImplementation(
}
}
+ this->dataPtr->AddModifiedComponent(_entity);
+
// Instantiate the new component.
std::pair componentIdPair =
this->dataPtr->components[_componentTypeId]->Create(_data);
@@ -536,7 +553,7 @@ ComponentKey EntityComponentManager::CreateComponentImplementation(
ComponentKey componentKey{_componentTypeId, componentIdPair.first};
this->dataPtr->entityComponents[_entity].insert(
- {_componentTypeId, componentKey});
+ {_componentTypeId, componentIdPair.first});
this->dataPtr->oneTimeChangedComponents.insert(componentKey);
this->dataPtr->entityComponentsDirty = true;
@@ -581,7 +598,7 @@ ComponentId EntityComponentManager::EntityComponentIdFromType(
auto typeIter = ecIter->second.find(_type);
if (typeIter != ecIter->second.end())
- return typeIter->second.second;
+ return typeIter->second;
return -1;
}
@@ -599,8 +616,8 @@ const components::BaseComponent
auto typeIter = ecIter->second.find(_type);
if (typeIter != ecIter->second.end())
- return this->dataPtr->components.at(typeIter->second.first)->Component(
- typeIter->second.second);
+ return this->dataPtr->components.at(_type)->Component(
+ typeIter->second);
return nullptr;
}
@@ -616,8 +633,7 @@ components::BaseComponent *EntityComponentManager::ComponentImplementation(
auto typeIter = ecIter->second.find(_type);
if (typeIter != ecIter->second.end())
- return this->dataPtr->components.at(typeIter->second.first)->Component(
- typeIter->second.second);
+ return this->dataPtr->components.at(_type)->Component(typeIter->second);
return nullptr;
}
@@ -879,16 +895,14 @@ void EntityComponentManager::AddEntityToMessage(msgs::SerializedState &_msg,
for (const ComponentTypeId type : types)
{
// If the entity does not have the component, continue
- std::unordered_map::iterator typeIter =
- iter->second.find(type);
+ auto typeIter = iter->second.find(type);
if (typeIter == iter->second.end())
{
continue;
}
- ComponentKey comp = (typeIter->second);
auto compMsg = entityMsg->add_components();
- auto compBase = this->ComponentImplementation(_entity, comp.first);
+ auto compBase = this->ComponentImplementation(_entity, type);
compMsg->set_type(compBase->TypeId());
std::ostringstream ostr;
@@ -946,16 +960,16 @@ void EntityComponentManager::AddEntityToMessage(msgs::SerializedStateMap &_msg,
// Empty means all types
for (const ComponentTypeId type : types)
{
- std::unordered_map::iterator typeIter =
- iter->second.find(type);
+ auto typeIter = iter->second.find(type);
if (typeIter == iter->second.end())
{
continue;
}
- ComponentKey comp = typeIter->second;
const components::BaseComponent *compBase =
- this->ComponentImplementation(_entity, comp.first);
+ this->ComponentImplementation(_entity, type);
+
+ ComponentKey comp = {type, typeIter->second};
// If not sending full state, skip unchanged components
if (!_full &&
@@ -1021,7 +1035,11 @@ ignition::msgs::SerializedState EntityComponentManager::ChangedState() const
this->AddEntityToMessage(stateMsg, entity);
}
- // TODO(anyone) New / removed / changed components
+ // New / removed / changed components
+ for (const auto &entity : this->dataPtr->modifiedComponents)
+ {
+ this->AddEntityToMessage(stateMsg, entity);
+ }
return stateMsg;
}
@@ -1042,7 +1060,11 @@ void EntityComponentManager::ChangedState(
this->AddEntityToMessage(_state, entity);
}
- // TODO(anyone) New / removed / changed components
+ // New / removed / changed components
+ for (const auto &entity : this->dataPtr->modifiedComponents)
+ {
+ this->AddEntityToMessage(_state, entity);
+ }
}
//////////////////////////////////////////////////
@@ -1257,6 +1279,10 @@ void EntityComponentManager::SetState(
// values.
// igndbg << *comp << " " << *newComp.get() << std::endl;
// *comp = *newComp.get();
+
+ // When above TODO is addressed, uncomment AddModifiedComponent below
+ // unless calling SetChanged (which already calls AddModifiedComponent)
+ // this->dataPtr->AddModifiedComponent(entity);
}
}
}
@@ -1390,6 +1416,7 @@ void EntityComponentManager::SetAllComponentsUnchanged()
{
this->dataPtr->periodicChangedComponents.clear();
this->dataPtr->oneTimeChangedComponents.clear();
+ this->dataPtr->modifiedComponents.clear();
}
/////////////////////////////////////////////////
@@ -1406,21 +1433,25 @@ void EntityComponentManager::SetChanged(
if (typeIter == ecIter->second.end())
return;
+ ComponentKey key{_type, typeIter->second};
+
if (_c == ComponentState::PeriodicChange)
{
- this->dataPtr->periodicChangedComponents.insert(typeIter->second);
- this->dataPtr->oneTimeChangedComponents.erase(typeIter->second);
+ this->dataPtr->periodicChangedComponents.insert(key);
+ this->dataPtr->oneTimeChangedComponents.erase(key);
}
else if (_c == ComponentState::OneTimeChange)
{
- this->dataPtr->periodicChangedComponents.erase(typeIter->second);
- this->dataPtr->oneTimeChangedComponents.insert(typeIter->second);
+ this->dataPtr->periodicChangedComponents.erase(key);
+ this->dataPtr->oneTimeChangedComponents.insert(key);
}
else
{
- this->dataPtr->periodicChangedComponents.erase(typeIter->second);
- this->dataPtr->oneTimeChangedComponents.erase(typeIter->second);
+ this->dataPtr->periodicChangedComponents.erase(key);
+ this->dataPtr->oneTimeChangedComponents.erase(key);
}
+
+ this->dataPtr->AddModifiedComponent(_entity);
}
/////////////////////////////////////////////////
@@ -1453,3 +1484,19 @@ void EntityComponentManager::SetEntityCreateOffset(uint64_t _offset)
this->dataPtr->entityCount = _offset;
}
+
+/////////////////////////////////////////////////
+void EntityComponentManagerPrivate::AddModifiedComponent(const Entity &_entity)
+{
+ if (this->newlyCreatedEntities.find(_entity)
+ != this->newlyCreatedEntities.end() ||
+ this->toRemoveEntities.find(_entity) != this->toRemoveEntities.end() ||
+ this->modifiedComponents.find(_entity) != this->modifiedComponents.end())
+ {
+ // modified component is already in newlyCreatedEntities
+ // or toRemoveEntities list
+ return;
+ }
+
+ this->modifiedComponents.insert(_entity);
+}
diff --git a/src/EntityComponentManager_TEST.cc b/src/EntityComponentManager_TEST.cc
index 829637bdf5..76d61174b4 100644
--- a/src/EntityComponentManager_TEST.cc
+++ b/src/EntityComponentManager_TEST.cc
@@ -1808,7 +1808,7 @@ TEST_P(EntityComponentManagerFixture, State)
{
// Check the changed state
auto changedStateMsg = manager.ChangedState();
- EXPECT_EQ(2, changedStateMsg.entities_size());
+ EXPECT_EQ(4, changedStateMsg.entities_size());
const auto &e4Msg = changedStateMsg.entities(0);
EXPECT_EQ(e4, e4Msg.id());
@@ -1890,6 +1890,74 @@ TEST_P(EntityComponentManagerFixture, State)
}
}
+/////////////////////////////////////////////////
+TEST_P(EntityComponentManagerFixture, ChangedStateComponents)
+{
+ // Entity and component
+ Entity e1{1};
+ int e1c0{123};
+ std::string e1c1{"string"};
+
+ // Fill manager with entity
+ EXPECT_EQ(e1, manager.CreateEntity());
+ EXPECT_EQ(1u, manager.EntityCount());
+
+ // Component
+ manager.CreateComponent(e1, IntComponent(e1c0));
+
+ // Serialize into a message
+ msgs::SerializedStateMap stateMsg;
+ manager.State(stateMsg);
+ ASSERT_EQ(1, stateMsg.entities_size());
+
+ // Mark entities/components as not new
+ manager.RunClearNewlyCreatedEntities();
+ auto changedStateMsg = manager.ChangedState();
+ EXPECT_EQ(0, changedStateMsg.entities_size());
+
+ // create component
+ auto compKey =
+ manager.CreateComponent(e1, StringComponent(e1c1));
+ changedStateMsg = manager.ChangedState();
+ EXPECT_EQ(1, changedStateMsg.entities_size());
+ manager.State(stateMsg);
+
+ // Mark components as not new
+ manager.RunSetAllComponentsUnchanged();
+ changedStateMsg = manager.ChangedState();
+ EXPECT_EQ(0, changedStateMsg.entities_size());
+
+ // modify component
+ auto iter = stateMsg.mutable_entities()->find(e1);
+ ASSERT_TRUE(iter != stateMsg.mutable_entities()->end());
+
+ msgs::SerializedEntityMap &e1Msg = iter->second;
+
+ auto compIter = e1Msg.mutable_components()->find(compKey.first);
+ ASSERT_TRUE(compIter != e1Msg.mutable_components()->end());
+
+ msgs::SerializedComponent &e1c1Msg = compIter->second;
+ EXPECT_EQ(e1c1, e1c1Msg.component());
+ e1c1Msg.set_component("test");
+ EXPECT_EQ("test", e1c1Msg.component());
+ (*e1Msg.mutable_components())[e1c1Msg.type()] = e1c1Msg;
+
+ manager.SetState(stateMsg);
+ changedStateMsg = manager.ChangedState();
+ EXPECT_EQ(1, changedStateMsg.entities_size());
+
+ // Mark components as not new
+ manager.RunSetAllComponentsUnchanged();
+ changedStateMsg = manager.ChangedState();
+ EXPECT_EQ(0, changedStateMsg.entities_size());
+
+ // remove component
+ manager.RemoveComponent(e1, StringComponent::typeId);
+
+ changedStateMsg = manager.ChangedState();
+ EXPECT_EQ(1, changedStateMsg.entities_size());
+}
+
/////////////////////////////////////////////////
TEST_P(EntityComponentManagerFixture, Descendants)
{
@@ -2190,6 +2258,11 @@ TEST_P(EntityComponentManagerFixture, SetEntityCreateOffset)
manager.SetEntityCreateOffset(1000);
Entity entity2 = manager.CreateEntity();
EXPECT_EQ(1001u, entity2);
+
+ // Apply a lower offset, prints warning but goes through.
+ manager.SetEntityCreateOffset(500);
+ Entity entity3 = manager.CreateEntity();
+ EXPECT_EQ(501u, entity3);
}
//////////////////////////////////////////////////
diff --git a/src/SdfEntityCreator.cc b/src/SdfEntityCreator.cc
index 91a8aed645..d7c98189c6 100644
--- a/src/SdfEntityCreator.cc
+++ b/src/SdfEntityCreator.cc
@@ -57,6 +57,7 @@
#include "ignition/gazebo/components/Name.hh"
#include "ignition/gazebo/components/ParentLinkName.hh"
#include "ignition/gazebo/components/ParentEntity.hh"
+#include
#include "ignition/gazebo/components/Physics.hh"
#include "ignition/gazebo/components/Pose.hh"
#include "ignition/gazebo/components/RgbdCamera.hh"
@@ -536,6 +537,16 @@ Entity SdfEntityCreator::CreateEntities(const sdf::Link *_link)
this->SetParent(sensorEntity, linkEntity);
}
+ // Particle emitters
+ for (uint64_t emitterIndex = 0; emitterIndex < _link->ParticleEmitterCount();
+ ++emitterIndex)
+ {
+ auto emitter = _link->ParticleEmitterByIndex(emitterIndex);
+ auto emitterEntity = this->CreateEntities(emitter);
+
+ this->SetParent(emitterEntity, linkEntity);
+ }
+
return linkEntity;
}
@@ -671,6 +682,25 @@ Entity SdfEntityCreator::CreateEntities(const sdf::Visual *_visual)
return visualEntity;
}
+//////////////////////////////////////////////////
+Entity SdfEntityCreator::CreateEntities(const sdf::ParticleEmitter *_emitter)
+{
+ IGN_PROFILE("SdfEntityCreator::CreateEntities(sdf::ParticleEmitter)");
+
+ // Entity
+ Entity emitterEntity = this->dataPtr->ecm->CreateEntity();
+
+ // Components
+ this->dataPtr->ecm->CreateComponent(emitterEntity,
+ components::ParticleEmitter(convert(*_emitter)));
+ this->dataPtr->ecm->CreateComponent(emitterEntity,
+ components::Pose(ResolveSdfPose(_emitter->SemanticPose())));
+ this->dataPtr->ecm->CreateComponent(emitterEntity,
+ components::Name(_emitter->Name()));
+
+ return emitterEntity;
+}
+
//////////////////////////////////////////////////
Entity SdfEntityCreator::CreateEntities(const sdf::Collision *_collision)
{
diff --git a/src/SimulationRunner.cc b/src/SimulationRunner.cc
index 923241007d..4bb9d7a58f 100644
--- a/src/SimulationRunner.cc
+++ b/src/SimulationRunner.cc
@@ -355,20 +355,32 @@ void SimulationRunner::UpdatePhysicsParams()
if (newStepSize != this->stepSize ||
std::abs(newRTF - this->desiredRtf) > eps)
{
- this->SetStepSize(
- std::chrono::duration_cast(
- newStepSize));
- this->desiredRtf = newRTF;
- this->updatePeriod = std::chrono::nanoseconds(
- static_cast(this->stepSize.count() / this->desiredRtf));
-
- this->simTimes.clear();
- this->realTimes.clear();
- // Update physics components
- physicsComp->Data().SetMaxStepSize(physicsParams.max_step_size());
- physicsComp->Data().SetRealTimeFactor(newRTF);
- this->entityCompMgr.SetChanged(worldEntity, components::Physics::typeId,
- ComponentState::OneTimeChange);
+ bool updated = false;
+ // Make sure the values are valid before setting them
+ if (newStepSize.count() > 0.0)
+ {
+ this->SetStepSize(
+ std::chrono::duration_cast(
+ newStepSize));
+ physicsComp->Data().SetMaxStepSize(physicsParams.max_step_size());
+ updated = true;
+ }
+ if (newRTF > 0.0)
+ {
+ this->desiredRtf = newRTF;
+ this->updatePeriod = std::chrono::nanoseconds(
+ static_cast(this->stepSize.count() / this->desiredRtf));
+ physicsComp->Data().SetRealTimeFactor(newRTF);
+ updated = true;
+ }
+ if (updated)
+ {
+ this->simTimes.clear();
+ this->realTimes.clear();
+ // Set as OneTimeChange to make sure the update is not missed
+ this->entityCompMgr.SetChanged(worldEntity, components::Physics::typeId,
+ ComponentState::OneTimeChange);
+ }
}
this->entityCompMgr.RemoveComponent(worldEntity);
}
diff --git a/src/Util.cc b/src/Util.cc
index 5f2e4740f9..d6af1281ea 100644
--- a/src/Util.cc
+++ b/src/Util.cc
@@ -43,6 +43,7 @@
#include "ignition/gazebo/components/Model.hh"
#include "ignition/gazebo/components/Name.hh"
#include "ignition/gazebo/components/ParentEntity.hh"
+#include "ignition/gazebo/components/ParticleEmitter.hh"
#include "ignition/gazebo/components/Pose.hh"
#include "ignition/gazebo/components/Sensor.hh"
#include "ignition/gazebo/components/Visual.hh"
@@ -168,6 +169,10 @@ ComponentTypeId entityTypeId(const Entity &_entity,
{
type = components::Actor::typeId;
}
+ else if (_ecm.Component(_entity))
+ {
+ type = components::ParticleEmitter::typeId;
+ }
return type;
}
@@ -214,6 +219,10 @@ std::string entityTypeStr(const Entity &_entity,
{
type = "actor";
}
+ else if (_ecm.Component(_entity))
+ {
+ type = "particle_emitter";
+ }
return type;
}
@@ -435,6 +444,23 @@ ignition::gazebo::Entity topLevelModel(const Entity &_entity,
return modelEntity;
}
+//////////////////////////////////////////////////
+std::string topicFromScopedName(const Entity &_entity,
+ const EntityComponentManager &_ecm, bool _excludeWorld)
+{
+ std::string topic = scopedName(_entity, _ecm, "/", true);
+
+ if (_excludeWorld)
+ {
+ // Exclude the world name. If the entity is a world, then return an
+ // empty string.
+ topic = _ecm.Component(_entity) ? "" :
+ removeParentScope(removeParentScope(topic, "/"), "/");
+ }
+
+ return transport::TopicUtils::AsValidTopic("/" + topic);
+}
+
//////////////////////////////////////////////////
std::string validTopic(const std::vector &_topics)
{
diff --git a/src/Util_TEST.cc b/src/Util_TEST.cc
index f9edf2543b..15e12002c9 100644
--- a/src/Util_TEST.cc
+++ b/src/Util_TEST.cc
@@ -28,6 +28,7 @@
#include "ignition/gazebo/components/Model.hh"
#include "ignition/gazebo/components/Name.hh"
#include "ignition/gazebo/components/ParentEntity.hh"
+#include "ignition/gazebo/components/ParticleEmitter.hh"
#include "ignition/gazebo/components/Sensor.hh"
#include "ignition/gazebo/components/Visual.hh"
#include "ignition/gazebo/components/World.hh"
@@ -272,6 +273,10 @@ TEST_F(UtilTest, EntityTypeId)
entity = ecm.CreateEntity();
ecm.CreateComponent(entity, components::Actor());
EXPECT_EQ(components::Actor::typeId, entityTypeId(entity, ecm));
+
+ entity = ecm.CreateEntity();
+ ecm.CreateComponent(entity, components::ParticleEmitter());
+ EXPECT_EQ(components::ParticleEmitter::typeId, entityTypeId(entity, ecm));
}
/////////////////////////////////////////////////
@@ -317,6 +322,10 @@ TEST_F(UtilTest, EntityTypeStr)
entity = ecm.CreateEntity();
ecm.CreateComponent(entity, components::Actor());
EXPECT_EQ("actor", entityTypeStr(entity, ecm));
+
+ entity = ecm.CreateEntity();
+ ecm.CreateComponent(entity, components::ParticleEmitter());
+ EXPECT_EQ("particle_emitter", entityTypeStr(entity, ecm));
}
/////////////////////////////////////////////////
@@ -510,3 +519,93 @@ TEST_F(UtilTest, ValidTopic)
EXPECT_EQ("not_bad", validTopic({fixable, invalid, good}));
EXPECT_EQ("good", validTopic({invalid, good, fixable}));
}
+
+/////////////////////////////////////////////////
+TEST_F(UtilTest, TopicFromScopedName)
+{
+ EntityComponentManager ecm;
+
+ // world
+ // - modelA
+ // - linkA
+ // - modelB
+ // - linkB
+ // - emitterB
+ // - modelC
+
+ // World
+ auto worldEntity = ecm.CreateEntity();
+ ecm.CreateComponent(worldEntity, components::World());
+ ecm.CreateComponent(worldEntity, components::Name("world_name"));
+
+ // Model A
+ auto modelAEntity = ecm.CreateEntity();
+ ecm.CreateComponent(modelAEntity, components::Model());
+ ecm.CreateComponent(modelAEntity, components::Name("modelA_name"));
+ ecm.CreateComponent(modelAEntity, components::ParentEntity(worldEntity));
+
+ // Link A - Child of Model A
+ auto linkAEntity = ecm.CreateEntity();
+ ecm.CreateComponent(linkAEntity, components::Link());
+ ecm.CreateComponent(linkAEntity, components::Name("linkA_name"));
+ ecm.CreateComponent(linkAEntity, components::ParentEntity(modelAEntity));
+
+ // Model B - nested inside Model A
+ auto modelBEntity = ecm.CreateEntity();
+ ecm.CreateComponent(modelBEntity, components::Model());
+ ecm.CreateComponent(modelBEntity, components::Name("modelB_name"));
+ ecm.CreateComponent(modelBEntity, components::ParentEntity(modelAEntity));
+
+ // Link B - child of Model B
+ auto linkBEntity = ecm.CreateEntity();
+ ecm.CreateComponent(linkBEntity, components::Link());
+ ecm.CreateComponent(linkBEntity, components::Name("linkB_name"));
+ ecm.CreateComponent(linkBEntity, components::ParentEntity(modelBEntity));
+
+ // Emitter B - child of Link B
+ auto emitterBEntity = ecm.CreateEntity();
+ ecm.CreateComponent(emitterBEntity, components::ParticleEmitter());
+ ecm.CreateComponent(emitterBEntity, components::Name("emitterB_name"));
+ ecm.CreateComponent(emitterBEntity, components::ParentEntity(linkBEntity));
+
+ // Model C
+ auto modelCEntity = ecm.CreateEntity();
+ ecm.CreateComponent(modelCEntity, components::Model());
+ ecm.CreateComponent(modelCEntity, components::Name("modelC_name"));
+ ecm.CreateComponent(modelCEntity, components::ParentEntity(worldEntity));
+
+ std::string testName = "/model/modelA_name";
+ std::string worldName = "/world/world_name";
+ // model A, link A, model B, link B and visual B should have
+ // model A as the top level model
+ EXPECT_EQ(testName, topicFromScopedName(modelAEntity, ecm));
+ EXPECT_EQ(worldName + testName,
+ topicFromScopedName(modelAEntity, ecm, false));
+
+ testName += "/link/linkA_name";
+ EXPECT_EQ(testName, topicFromScopedName(linkAEntity, ecm));
+ EXPECT_EQ(worldName + testName, topicFromScopedName(linkAEntity, ecm, false));
+
+ testName = "/model/modelA_name/model/modelB_name";
+ EXPECT_EQ(testName, topicFromScopedName(modelBEntity, ecm));
+ EXPECT_EQ(worldName + testName,
+ topicFromScopedName(modelBEntity, ecm, false));
+
+ testName +="/link/linkB_name";
+ EXPECT_EQ(testName, topicFromScopedName(linkBEntity, ecm));
+ EXPECT_EQ(worldName + testName, topicFromScopedName(linkBEntity, ecm, false));
+
+ testName += "/particle_emitter/emitterB_name";
+ EXPECT_EQ(testName,
+ topicFromScopedName(emitterBEntity, ecm));
+ EXPECT_EQ(worldName + testName,
+ topicFromScopedName(emitterBEntity, ecm, false));
+
+ testName = "/model/modelC_name";
+ EXPECT_EQ(testName, topicFromScopedName(modelCEntity, ecm));
+ EXPECT_EQ(worldName + testName,
+ topicFromScopedName(modelCEntity, ecm, false));
+
+ EXPECT_TRUE(topicFromScopedName(worldEntity, ecm).empty());
+ EXPECT_EQ(worldName, topicFromScopedName(worldEntity, ecm, false));
+}
diff --git a/src/rendering/SceneManager.cc b/src/rendering/SceneManager.cc
index 276b07898a..e802951738 100644
--- a/src/rendering/SceneManager.cc
+++ b/src/rendering/SceneManager.cc
@@ -1298,6 +1298,21 @@ rendering::ParticleEmitterPtr SceneManager::UpdateParticleEmitter(Entity _id,
if (_emitter.has_pose())
emitter->SetLocalPose(msgs::Convert(_emitter.pose()));
+ // particle scatter ratio
+ if (_emitter.has_header())
+ {
+ for (int i = 0; i < _emitter.header().data_size(); ++i)
+ {
+ const auto &data = _emitter.header().data(i);
+ const std::string key = "particle_scatter_ratio";
+ if (data.key() == "particle_scatter_ratio" && data.value_size() > 0)
+ {
+ emitter->SetParticleScatterRatio(math::parseFloat(data.value(0)));
+ break;
+ }
+ }
+ }
+
return emitter;
}
diff --git a/src/systems/CMakeLists.txt b/src/systems/CMakeLists.txt
index f8b1d3d031..2e2f830236 100644
--- a/src/systems/CMakeLists.txt
+++ b/src/systems/CMakeLists.txt
@@ -114,8 +114,10 @@ add_subdirectory(magnetometer)
add_subdirectory(mecanum_drive)
add_subdirectory(multicopter_motor_model)
add_subdirectory(multicopter_control)
+add_subdirectory(odometry_publisher)
add_subdirectory(optical_tactile_plugin)
add_subdirectory(particle_emitter)
+add_subdirectory(particle_emitter2)
add_subdirectory(performer_detector)
add_subdirectory(physics)
add_subdirectory(pose_publisher)
diff --git a/src/systems/joint_state_publisher/JointStatePublisher.cc b/src/systems/joint_state_publisher/JointStatePublisher.cc
index 656095649d..9830edfbd7 100644
--- a/src/systems/joint_state_publisher/JointStatePublisher.cc
+++ b/src/systems/joint_state_publisher/JointStatePublisher.cc
@@ -169,6 +169,8 @@ void JointStatePublisher::PostUpdate(const UpdateInfo &_info,
if (pose)
msgs::Set(msg.mutable_pose(), pose->Data());
+ static bool hasWarned {false};
+
// Process each joint
for (const Entity &joint : this->joints)
{
@@ -190,11 +192,18 @@ void JointStatePublisher::PostUpdate(const UpdateInfo &_info,
for (size_t i = 0; i < jointPositions->Data().size(); ++i)
{
if (i == 0)
+ {
jointMsg->mutable_axis1()->set_position(jointPositions->Data()[i]);
+ }
else if (i == 1)
+ {
jointMsg->mutable_axis2()->set_position(jointPositions->Data()[i]);
- else
+ }
+ else if (!hasWarned)
+ {
ignwarn << "Joint state publisher only supports two joint axis\n";
+ hasWarned = true;
+ }
}
}
@@ -206,11 +215,18 @@ void JointStatePublisher::PostUpdate(const UpdateInfo &_info,
for (size_t i = 0; i < jointVelocity->Data().size(); ++i)
{
if (i == 0)
+ {
jointMsg->mutable_axis1()->set_velocity(jointVelocity->Data()[i]);
+ }
else if (i == 1)
+ {
jointMsg->mutable_axis2()->set_velocity(jointVelocity->Data()[i]);
- else
+ }
+ else if (!hasWarned)
+ {
ignwarn << "Joint state publisher only supports two joint axis\n";
+ hasWarned = true;
+ }
}
}
@@ -222,11 +238,18 @@ void JointStatePublisher::PostUpdate(const UpdateInfo &_info,
for (size_t i = 0; i < jointForce->Data().size(); ++i)
{
if (i == 0)
+ {
jointMsg->mutable_axis1()->set_force(jointForce->Data()[i]);
+ }
else if (i == 1)
+ {
jointMsg->mutable_axis2()->set_force(jointForce->Data()[i]);
- else
+ }
+ else if (!hasWarned)
+ {
ignwarn << "Joint state publisher only supports two joint axis\n";
+ hasWarned = true;
+ }
}
}
}
diff --git a/src/systems/log/LogPlayback.cc b/src/systems/log/LogPlayback.cc
index e7b557e44d..60769353fe 100644
--- a/src/systems/log/LogPlayback.cc
+++ b/src/systems/log/LogPlayback.cc
@@ -44,6 +44,7 @@
#include "ignition/gazebo/components/Geometry.hh"
#include "ignition/gazebo/components/LogPlaybackStatistics.hh"
#include "ignition/gazebo/components/Material.hh"
+#include "ignition/gazebo/components/ParticleEmitter.hh"
#include "ignition/gazebo/components/Pose.hh"
#include "ignition/gazebo/components/World.hh"
@@ -124,6 +125,9 @@ class ignition::gazebo::systems::LogPlaybackPrivate
/// \brief Saves which entity poses have changed according to the latest
/// LogPlaybackPrivate::Parse call.
public: std::unordered_map recentEntityPoseUpdates;
+
+ // \brief Saves which particle emitter emitting components have changed
+ public: std::unordered_map prevParticleEmitterCmds;
};
bool LogPlaybackPrivate::started{false};
@@ -636,6 +640,31 @@ void LogPlayback::Update(const UpdateInfo &_info, EntityComponentManager &_ecm)
return true;
});
+ // particle emitters
+ _ecm.Each(
+ [&](const Entity &_entity,
+ const components::ParticleEmitterCmd *_emitter) -> bool
+ {
+ if (this->dataPtr->prevParticleEmitterCmds.find(_entity) ==
+ this->dataPtr->prevParticleEmitterCmds.end())
+ {
+ this->dataPtr->prevParticleEmitterCmds[_entity]
+ = _emitter->Data().emitting().data();
+ return true;
+ }
+
+ if (this->dataPtr->prevParticleEmitterCmds[_entity] !=
+ _emitter->Data().emitting().data())
+ {
+ this->dataPtr->prevParticleEmitterCmds[_entity]
+ = _emitter->Data().emitting().data();
+ _ecm.SetChanged(_entity, components::ParticleEmitterCmd::typeId,
+ ComponentState::OneTimeChange);
+ }
+
+ return true;
+ });
+
// for seek back in time only
// remove entities that should not be present in the current time step
for (auto entity : entitiesToRemove)
diff --git a/src/systems/odometry_publisher/CMakeLists.txt b/src/systems/odometry_publisher/CMakeLists.txt
new file mode 100644
index 0000000000..1b12fd3fde
--- /dev/null
+++ b/src/systems/odometry_publisher/CMakeLists.txt
@@ -0,0 +1,6 @@
+gz_add_system(odometry-publisher
+ SOURCES
+ OdometryPublisher.cc
+ PUBLIC_LINK_LIBS
+ ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER}
+)
diff --git a/src/systems/odometry_publisher/OdometryPublisher.cc b/src/systems/odometry_publisher/OdometryPublisher.cc
new file mode 100644
index 0000000000..61a6312c0d
--- /dev/null
+++ b/src/systems/odometry_publisher/OdometryPublisher.cc
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2021 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.
+ *
+ */
+
+#include "OdometryPublisher.hh"
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "ignition/gazebo/components/Pose.hh"
+#include "ignition/gazebo/components/JointPosition.hh"
+#include "ignition/gazebo/Model.hh"
+#include "ignition/gazebo/Util.hh"
+
+using namespace ignition;
+using namespace gazebo;
+using namespace systems;
+
+class ignition::gazebo::systems::OdometryPublisherPrivate
+{
+ /// \brief Calculates odometry and publishes an odometry message.
+ /// \param[in] _info System update information.
+ /// \param[in] _ecm The EntityComponentManager of the given simulation
+ /// instance.
+ public: void UpdateOdometry(const ignition::gazebo::UpdateInfo &_info,
+ const ignition::gazebo::EntityComponentManager &_ecm);
+
+ /// \brief Ignition communication node.
+ public: transport::Node node;
+
+ /// \brief Model interface
+ public: Model model{kNullEntity};
+
+ /// \brief Name of the world-fixed coordinate frame for the odometry message.
+ public: std::string odomFrame;
+
+ /// \brief Name of the coordinate frame rigidly attached to the mobile
+ /// robot base.
+ public: std::string robotBaseFrame;
+
+ /// \brief Update period calculated from .
+ public: std::chrono::steady_clock::duration odomPubPeriod{0};
+
+ /// \brief Last sim time odom was published.
+ public: std::chrono::steady_clock::duration lastOdomPubTime{0};
+
+ /// \brief Diff drive odometry message publisher.
+ public: transport::Node::Publisher odomPub;
+
+ /// \brief Rolling mean accumulators for the linear velocity
+ public: std::pair linearMean;
+
+ /// \brief Rolling mean accumulators for the angular velocity
+ public: math::RollingMean angularMean;
+
+ /// \brief Initialized flag.
+ public: bool initialized{false};
+
+ /// \brief Current pose of the model in the odom frame.
+ public: math::Pose3d lastUpdatePose{0, 0, 0, 0, 0, 0};
+
+ /// \brief Current timestamp.
+ public: math::clock::time_point lastUpdateTime;
+};
+
+//////////////////////////////////////////////////
+OdometryPublisher::OdometryPublisher()
+ : dataPtr(std::make_unique())
+{
+ this->dataPtr->linearMean.first.SetWindowSize(10);
+ this->dataPtr->linearMean.second.SetWindowSize(10);
+ this->dataPtr->angularMean.SetWindowSize(10);
+ this->dataPtr->linearMean.first.Clear();
+ this->dataPtr->linearMean.second.Clear();
+ this->dataPtr->angularMean.Clear();
+}
+
+//////////////////////////////////////////////////
+void OdometryPublisher::Configure(const Entity &_entity,
+ const std::shared_ptr &_sdf,
+ EntityComponentManager &_ecm,
+ EventManager &/*_eventMgr*/)
+{
+ this->dataPtr->model = Model(_entity);
+
+ if (!this->dataPtr->model.Valid(_ecm))
+ {
+ ignerr << "DiffDrive plugin should be attached to a model entity. "
+ << "Failed to initialize." << std::endl;
+ return;
+ }
+
+ this->dataPtr->odomFrame = this->dataPtr->model.Name(_ecm) + "/" + "odom";
+ if (!_sdf->HasElement("odom_frame"))
+ {
+ ignwarn << "OdometryPublisher system plugin missing , "
+ << "defaults to \"" << this->dataPtr->odomFrame << "\"" << std::endl;
+ }
+ else
+ {
+ this->dataPtr->odomFrame = _sdf->Get("odom_frame");
+ }
+
+ this->dataPtr->robotBaseFrame = this->dataPtr->model.Name(_ecm)
+ + "/" + "base_footprint";
+ if (!_sdf->HasElement("robot_base_frame"))
+ {
+ ignwarn << "OdometryPublisher system plugin missing , "
+ << "defaults to \"" << this->dataPtr->robotBaseFrame << "\"" << std::endl;
+ }
+ else
+ {
+ this->dataPtr->robotBaseFrame = _sdf->Get("robot_base_frame");
+ }
+
+ double odomFreq = _sdf->Get("odom_publish_frequency", 50).first;
+ if (odomFreq > 0)
+ {
+ std::chrono::duration period{1 / odomFreq};
+ this->dataPtr->odomPubPeriod =
+ std::chrono::duration_cast(period);
+ }
+
+ // Setup odometry
+ std::string odomTopic{"/model/" + this->dataPtr->model.Name(_ecm) +
+ "/odometry"};
+ if (_sdf->HasElement("odom_topic"))
+ odomTopic = _sdf->Get("odom_topic");
+ std::string odomTopicValid {transport::TopicUtils::AsValidTopic(odomTopic)};
+ if (odomTopicValid.empty())
+ {
+ ignerr << "Failed to generate odom topic ["
+ << odomTopic << "]" << std::endl;
+ return;
+ }
+ this->dataPtr->odomPub = this->dataPtr->node.Advertise(
+ odomTopicValid);
+}
+
+//////////////////////////////////////////////////
+void OdometryPublisher::PreUpdate(const ignition::gazebo::UpdateInfo &_info,
+ ignition::gazebo::EntityComponentManager &_ecm)
+{
+ IGN_PROFILE("OdometryPublisher::PreUpdate");
+
+ // \TODO(anyone) Support rewind
+ if (_info.dt < std::chrono::steady_clock::duration::zero())
+ {
+ ignwarn << "Detected jump back in time ["
+ << std::chrono::duration_cast(_info.dt).count()
+ << "s]. System may not work properly." << std::endl;
+ }
+
+ // Create the pose component if it does not exist.
+ auto pos = _ecm.Component(
+ this->dataPtr->model.Entity());
+ if (!pos)
+ {
+ _ecm.CreateComponent(this->dataPtr->model.Entity(),
+ components::Pose());
+ }
+}
+
+//////////////////////////////////////////////////
+void OdometryPublisher::PostUpdate(const UpdateInfo &_info,
+ const EntityComponentManager &_ecm)
+{
+ IGN_PROFILE("OdometryPublisher::PostUpdate");
+ // Nothing left to do if paused.
+ if (_info.paused)
+ return;
+
+ this->dataPtr->UpdateOdometry(_info, _ecm);
+}
+
+//////////////////////////////////////////////////
+void OdometryPublisherPrivate::UpdateOdometry(
+ const ignition::gazebo::UpdateInfo &_info,
+ const ignition::gazebo::EntityComponentManager &_ecm)
+{
+ IGN_PROFILE("DiffDrive::UpdateOdometry");
+ // Record start time.
+ if (!this->initialized)
+ {
+ this->lastUpdateTime = std::chrono::steady_clock::time_point(_info.simTime);
+ this->initialized = true;
+ return;
+ }
+
+ // Construct the odometry message and publish it.
+ msgs::Odometry msg;
+
+ const std::chrono::duration dt =
+ std::chrono::steady_clock::time_point(_info.simTime) - lastUpdateTime;
+ // We cannot estimate the speed if the time interval is zero (or near
+ // zero).
+ if (math::equal(0.0, dt.count()))
+ return;
+
+ // Get and set robotBaseFrame to odom transformation.
+ const math::Pose3d pose = worldPose(this->model.Entity(), _ecm);
+ msg.mutable_pose()->mutable_position()->set_x(pose.Pos().X());
+ msg.mutable_pose()->mutable_position()->set_y(pose.Pos().Y());
+ msgs::Set(msg.mutable_pose()->mutable_orientation(), pose.Rot());
+
+ // Get linear and angular displacements from last updated pose.
+ double linearDisplacementX = pose.Pos().X() - this->lastUpdatePose.Pos().X();
+ double linearDisplacementY = pose.Pos().Y() - this->lastUpdatePose.Pos().Y();
+
+ double currentYaw = pose.Rot().Yaw();
+ const double lastYaw = this->lastUpdatePose.Rot().Yaw();
+ while (currentYaw < lastYaw - IGN_PI) currentYaw += 2 * IGN_PI;
+ while (currentYaw > lastYaw + IGN_PI) currentYaw -= 2 * IGN_PI;
+ const float angularDiff = currentYaw - lastYaw;
+
+ // Get velocities in robotBaseFrame and add to message.
+ double linearVelocityX = (cosf(currentYaw) * linearDisplacementX
+ + sinf(currentYaw) * linearDisplacementY) / dt.count();
+ double linearVelocityY = (cosf(currentYaw) * linearDisplacementY
+ - sinf(currentYaw) * linearDisplacementX) / dt.count();
+ this->linearMean.first.Push(linearVelocityX);
+ this->linearMean.second.Push(linearVelocityY);
+ this->angularMean.Push(angularDiff / dt.count());
+ msg.mutable_twist()->mutable_linear()->set_x(this->linearMean.first.Mean());
+ msg.mutable_twist()->mutable_linear()->set_y(this->linearMean.second.Mean());
+ msg.mutable_twist()->mutable_angular()->set_z(this->angularMean.Mean());
+
+ // Set the time stamp in the header.
+ msg.mutable_header()->mutable_stamp()->CopyFrom(
+ convert(_info.simTime));
+
+ // Set the frame ids.
+ auto frame = msg.mutable_header()->add_data();
+ frame->set_key("frame_id");
+ frame->add_value(odomFrame);
+ auto childFrame = msg.mutable_header()->add_data();
+ childFrame->set_key("child_frame_id");
+ childFrame->add_value(robotBaseFrame);
+
+ this->lastUpdatePose = pose;
+ this->lastUpdateTime = std::chrono::steady_clock::time_point(_info.simTime);
+
+ // Throttle publishing.
+ auto diff = _info.simTime - this->lastOdomPubTime;
+ if (diff > std::chrono::steady_clock::duration::zero() &&
+ diff < this->odomPubPeriod)
+ {
+ return;
+ }
+ this->lastOdomPubTime = _info.simTime;
+ this->odomPub.Publish(msg);
+}
+
+IGNITION_ADD_PLUGIN(OdometryPublisher,
+ ignition::gazebo::System,
+ OdometryPublisher::ISystemConfigure,
+ OdometryPublisher::ISystemPreUpdate,
+ OdometryPublisher::ISystemPostUpdate)
+
+IGNITION_ADD_PLUGIN_ALIAS(OdometryPublisher,
+ "ignition::gazebo::systems::OdometryPublisher")
diff --git a/src/systems/odometry_publisher/OdometryPublisher.hh b/src/systems/odometry_publisher/OdometryPublisher.hh
new file mode 100644
index 0000000000..f60e309d06
--- /dev/null
+++ b/src/systems/odometry_publisher/OdometryPublisher.hh
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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 IGNITION_GAZEBO_SYSTEMS_ODOMETRYPUBLISHER_HH_
+#define IGNITION_GAZEBO_SYSTEMS_ODOMETRYPUBLISHER_HH_
+
+#include
+
+#include
+
+namespace ignition
+{
+namespace gazebo
+{
+// Inline bracket to help doxygen filtering.
+inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE {
+namespace systems
+{
+ // Forward declaration
+ class OdometryPublisherPrivate;
+
+ /// \brief Odometry Publisher which can be attached to any entity in
+ /// order to periodically publish 2D odometry data in the form of
+ /// ignition::msgs::Odometry messages.
+ ///
+ /// # System Parameters
+ ///
+ /// ``: Name of the world-fixed coordinate frame for the
+ // odometry message. This element is optional, and the default value
+ /// is `{name_of_model}/odom`.
+ ///
+ /// ``: Name of the coordinate frame rigidly attached
+ /// to the mobile robot base. This element is optional, and the default
+ /// value is `{name_of_model}/base_footprint`.
+ ///
+ /// ``: Odometry publication frequency. This
+ /// element is optional, and the default value is 50Hz.
+ ///
+ /// ``: Custom topic on which this system will publish odometry
+ /// messages. This element is optional, and the default value is
+ /// `/model/{name_of_model}/odometry`.
+ class OdometryPublisher
+ : public System,
+ public ISystemConfigure,
+ public ISystemPreUpdate,
+ public ISystemPostUpdate
+ {
+ /// \brief Constructor
+ public: OdometryPublisher();
+
+ /// \brief Destructor
+ public: ~OdometryPublisher() override = default;
+
+ // Documentation inherited
+ public: void Configure(const Entity &_entity,
+ const std::shared_ptr &_sdf,
+ EntityComponentManager &_ecm,
+ EventManager &_eventMgr) override;
+
+ // Documentation inherited
+ public: void PreUpdate(
+ const ignition::gazebo::UpdateInfo &_info,
+ ignition::gazebo::EntityComponentManager &_ecm) override;
+
+ // Documentation inherited
+ public: void PostUpdate(
+ const UpdateInfo &_info,
+ const EntityComponentManager &_ecm) override;
+
+ /// \brief Private data pointer
+ private: std::unique_ptr dataPtr;
+ };
+ }
+}
+}
+}
+
+#endif
diff --git a/src/systems/particle_emitter/ParticleEmitter.cc b/src/systems/particle_emitter/ParticleEmitter.cc
index 6c2de2a435..db1aa6b924 100644
--- a/src/systems/particle_emitter/ParticleEmitter.cc
+++ b/src/systems/particle_emitter/ParticleEmitter.cc
@@ -258,6 +258,17 @@ void ParticleEmitter::Configure(const Entity &_entity,
absolutePath);
}
+ // particle scatter ratio
+ const std::string scatterRatioKey = "particle_scatter_ratio";
+ if (_sdf->HasElement(scatterRatioKey))
+ {
+ // todo(anyone) add particle_scatter_ratio field in next release of ign-msgs
+ auto data = this->dataPtr->emitter.mutable_header()->add_data();
+ data->set_key(scatterRatioKey);
+ std::string *value = data->add_value();
+ *value = _sdf->Get(scatterRatioKey);
+ }
+
igndbg << "Loading particle emitter:" << std::endl
<< this->dataPtr->emitter.DebugString() << std::endl;
diff --git a/src/systems/particle_emitter/ParticleEmitter.hh b/src/systems/particle_emitter/ParticleEmitter.hh
index 7a9ab1e278..935fbd0eb2 100644
--- a/src/systems/particle_emitter/ParticleEmitter.hh
+++ b/src/systems/particle_emitter/ParticleEmitter.hh
@@ -33,6 +33,9 @@ namespace systems
/// \brief A system for creating a particle emitter.
///
+ /// This system will be deprecated in Igition Fortress. Please consider
+ /// using the ParticleEmitter2 system.
+ ///
/// System parameters
///
/// ``: Unique name for the particle emitter. The name will be
diff --git a/src/systems/particle_emitter2/CMakeLists.txt b/src/systems/particle_emitter2/CMakeLists.txt
new file mode 100644
index 0000000000..3abcb6619d
--- /dev/null
+++ b/src/systems/particle_emitter2/CMakeLists.txt
@@ -0,0 +1,9 @@
+gz_add_system(particle-emitter2
+ SOURCES
+ ParticleEmitter2.cc
+ PUBLIC_LINK_LIBS
+ ignition-common${IGN_COMMON_VER}::ignition-common${IGN_COMMON_VER}
+ ignition-msgs${IGN_MSGS_VER}::ignition-msgs${IGN_MSGS_VER}
+ ignition-plugin${IGN_PLUGIN_VER}::ignition-plugin${IGN_PLUGIN_VER}
+ ignition-transport${IGN_TRANSPORT_VER}::ignition-transport${IGN_TRANSPORT_VER}
+)
diff --git a/src/systems/particle_emitter2/ParticleEmitter2.cc b/src/systems/particle_emitter2/ParticleEmitter2.cc
new file mode 100644
index 0000000000..0298e71530
--- /dev/null
+++ b/src/systems/particle_emitter2/ParticleEmitter2.cc
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2021 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.
+ *
+ */
+
+#include
+#include
+
+#include