From 5c7fae3a718370160827441e320fca4048ac8276 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Thu, 21 May 2020 21:44:14 -0700 Subject: [PATCH 1/2] More actor components and follow plugin Signed-off-by: Louise Poubel --- examples/worlds/follow_actor.sdf | 364 ++++++++++++++++++ include/ignition/gazebo/components/Actor.hh | 42 ++ .../component_inspector/ComponentInspector.cc | 14 + src/rendering/RenderUtil.cc | 25 +- src/systems/CMakeLists.txt | 1 + src/systems/follow_actor/CMakeLists.txt | 5 + src/systems/follow_actor/FollowActor.cc | 277 +++++++++++++ src/systems/follow_actor/FollowActor.hh | 84 ++++ test/integration/CMakeLists.txt | 1 + test/integration/follow_actor_system.cc | 189 +++++++++ test/worlds/follow_actor.sdf | 57 +++ 11 files changed, 1057 insertions(+), 2 deletions(-) create mode 100644 examples/worlds/follow_actor.sdf create mode 100644 src/systems/follow_actor/CMakeLists.txt create mode 100644 src/systems/follow_actor/FollowActor.cc create mode 100644 src/systems/follow_actor/FollowActor.hh create mode 100644 test/integration/follow_actor_system.cc create mode 100644 test/worlds/follow_actor.sdf diff --git a/examples/worlds/follow_actor.sdf b/examples/worlds/follow_actor.sdf new file mode 100644 index 0000000000..50423034c8 --- /dev/null +++ b/examples/worlds/follow_actor.sdf @@ -0,0 +1,364 @@ + + + + + + + + + + + + + + + + 3D View + false + docked + + + ogre2 + scene + 0.4 0.4 0.4 + 0.8 0.8 0.8 + -6 0 6 0 0.5 0 + + + + + + World control + false + false + 72 + 121 + 1 + + floating + + + + + + + true + true + true + /world/actors/control + /world/actors/stats + + + + + + + World stats + false + false + 110 + 290 + 1 + + floating + + + + + + + true + true + true + true + /world/actors/stats + + + + + + + 0 + 0 + 263 + 50 + floating + false + #03a9f4 + + + + + + + false + docked + + + + + + + + + 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 0.0 1 + + + + + + + 0.0 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 + + + + + + + 1 -2 0.5 0 0 0 + + + + 1 + 0 + 0 + 1 + 0 + 1 + + 1.0 + + + + + 1 1 1 + + + + + + + + 1 1 1 + + + + 1 0 0 1 + 1 0 0 1 + 1 0 0 1 + + + + + + + 1 0 0.5 0 0 0 + + + + 1 + 0 + 0 + 1 + 0 + 1 + + 1.0 + + + + + 1 1 1 + + + + + + + + 1 1 1 + + + + 0 1 0 1 + 0 1 0 1 + 0 1 0 1 + + + + + + + 1 2 0.5 0 0 0 + + + + 1 + 0 + 0 + 1 + 0 + 1 + + 1.0 + + + + + 1 1 1 + + + + + + + + 1 1 1 + + + + 0 0 1 1 + 0 0 1 1 + 0 0 1 1 + + + + + + + 1 4 0.5 0 0 0 + + + + 1 + 0 + 0 + 1 + 0 + 1 + + 1.0 + + + + + 1 1 1 + + + + + + + + 1 1 1 + + + + 1 1 0 1 + 1 1 0 1 + 1 1 0 1 + + + + + + + walker_slow_red + + 0 -2 1.0 0 0 0 + https://fuel.ignitionrobotics.org/1.0/chapulina/models/Walking actor + + red_box + 1.0 + 8.0 + 1 + 4.58837 + + + + + walker_fast_green + 0 0 1.0 0 0 0 + https://fuel.ignitionrobotics.org/1.0/chapulina/models/Walking actor + + green_box + 1.0 + 8.0 + 4 + 4.58837 + + + + + runner_slow_blue + 0 2 1.0 0 0 0 + https://fuel.ignitionrobotics.org/1.0/Mingfei/models/actor + + blue_box + 1.0 + 8.0 + 1 + run + 1.5 + + + + + runner_fast_yellow + 0 4 1.0 0 0 0 + https://fuel.ignitionrobotics.org/1.0/Mingfei/models/actor + + yellow_box + 1.0 + 8.0 + 4 + run + 1.5 + + + + diff --git a/include/ignition/gazebo/components/Actor.hh b/include/ignition/gazebo/components/Actor.hh index aeca817612..c4db743488 100644 --- a/include/ignition/gazebo/components/Actor.hh +++ b/include/ignition/gazebo/components/Actor.hh @@ -17,6 +17,9 @@ #ifndef IGNITION_GAZEBO_COMPONENTS_ACTOR_HH_ #define IGNITION_GAZEBO_COMPONENTS_ACTOR_HH_ +#include +#include + #include #include @@ -37,6 +40,34 @@ namespace serializers { using ActorSerializer = serializers::ComponentToMsgSerializer; + + class AnimationTimeSerializer + { + /// \brief Serialization for `std::chrono::steady_clock::duration`. + /// \param[in] _out Output stream. + /// \param[in] _time Time to stream + /// \return The stream. + public: static std::ostream &Serialize(std::ostream &_out, + const std::chrono::steady_clock::duration &_time) + { + _out << std::chrono::duration_cast( + _time).count(); + return _out; + } + + /// \brief Deserialization for `std::chrono::steady_clock::duration`. + /// \param[in] _in Input stream. + /// \param[out] _time Time to populate + /// \return The stream. + public: static std::istream &Deserialize(std::istream &_in, + std::chrono::steady_clock::duration &_time) + { + int64_t time; + _in >> time; + _time = std::chrono::duration(time); + return _in; + } + }; } namespace components @@ -47,6 +78,17 @@ namespace components using Actor = Component; IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.Actor", Actor) + + /// \brief Time in seconds within animation being currently played. + using AnimationTime = Component; + IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.AnimationTime", + AnimationTime) + + /// \brief Name of animation being currently played. + using AnimationName = Component; + IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.AnimationName", + AnimationName) } } } diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index d28a74a15f..1857dca09e 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -411,6 +411,13 @@ void ComponentInspector::Update(const UpdateInfo &, setUnit(item, "rad/s"); } } + else if (typeId == components::AnimationName::typeId) + { + auto comp = _ecm.Component( + this->dataPtr->entity); + if (comp) + setData(item, comp->Data()); + } else if (typeId == components::CastShadows::typeId) { auto comp = _ecm.Component( @@ -506,6 +513,13 @@ void ComponentInspector::Update(const UpdateInfo &, if (comp) setData(item, comp->Data()); } + else if (typeId == components::TrajectoryPose::typeId) + { + auto comp = _ecm.Component( + this->dataPtr->entity); + if (comp) + setData(item, comp->Data()); + } else if (typeId == components::WindMode::typeId) { auto comp = _ecm.Component(this->dataPtr->entity); diff --git a/src/rendering/RenderUtil.cc b/src/rendering/RenderUtil.cc index eadb608ef8..379ece6b57 100644 --- a/src/rendering/RenderUtil.cc +++ b/src/rendering/RenderUtil.cc @@ -1029,13 +1029,34 @@ void RenderUtilPrivate::UpdateRenderingEntities( // Trajectory origin this->entityPoses[_entity] = _pose->Data(); - if (this->actorManualSkeletonUpdate) + auto animTimeComp = _ecm.Component(_entity); + auto animNameComp = _ecm.Component(_entity); + + // Animation time set through ECM so ign-rendering can calculate bone + // transforms + if (animTimeComp && animNameComp) + { + auto skel = this->sceneManager.ActorSkeletonById(_entity); + if (nullptr != skel) + { + AnimationUpdateData animData; + animData.loop = true; + animData.followTrajectory = true; + animData.animationName = animNameComp->Data(); + animData.time = animTimeComp->Data(); + animData.rootTransform = skel->RootNode()->Transform(); + animData.valid = true; + this->actorAnimationData[_entity] = animData; + } + } + // Bone poses calculated by ign-common + else if (this->actorManualSkeletonUpdate) { - // Bone poses calculated by ign-common this->actorTransforms[_entity] = this->sceneManager.ActorSkeletonTransformsAt( _entity, this->simTime); } + // Trajectory info from SDF so ign-rendering can calculate bone poses else { this->actorAnimationData[_entity] = diff --git a/src/systems/CMakeLists.txt b/src/systems/CMakeLists.txt index 5f7a9cc111..413e0ec7cd 100644 --- a/src/systems/CMakeLists.txt +++ b/src/systems/CMakeLists.txt @@ -78,6 +78,7 @@ add_subdirectory(breadcrumbs) add_subdirectory(contact) add_subdirectory(detachable_joint) add_subdirectory(diff_drive) +add_subdirectory(follow_actor) add_subdirectory(imu) add_subdirectory(joint_controller) add_subdirectory(joint_state_publisher) diff --git a/src/systems/follow_actor/CMakeLists.txt b/src/systems/follow_actor/CMakeLists.txt new file mode 100644 index 0000000000..864371e1d6 --- /dev/null +++ b/src/systems/follow_actor/CMakeLists.txt @@ -0,0 +1,5 @@ +gz_add_system(follow-actor + SOURCES + FollowActor.cc +) + diff --git a/src/systems/follow_actor/FollowActor.cc b/src/systems/follow_actor/FollowActor.cc new file mode 100644 index 0000000000..ea68d4219d --- /dev/null +++ b/src/systems/follow_actor/FollowActor.cc @@ -0,0 +1,277 @@ +/* + * 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. + * + */ + +#include + +#include + +#include +#include +#include +#include +#include + +#include "FollowActor.hh" + +using namespace ignition; +using namespace gazebo; +using namespace systems; + +/// \brief Private FollowActor data class. +class ignition::gazebo::systems::FollowActorPrivate +{ + /// \brief Entity for the actor. + public: Entity actorEntity{kNullEntity}; + + /// \brief Velocity of the actor + public: double velocity{0.8}; + + /// \brief Current target to follow + public: Entity targetEntity{kNullEntity}; + + /// \brief Minimum distance in meters to keep away from target. + public: double minDistance{1.2}; + + /// \brief Maximum distance in meters to keep away from target. + public: double maxDistance{4}; + + /// \brief Velocity of the animation dislocation on the X axis, in m/s. + /// Used to coordinate translational motion with the actor's feet. + /// TODO(louise) Automatically calculate it from the root node's first and + /// last keyframes + public: double animationXVel{2.0}; + + /// \brief Time of the last update. + public: std::chrono::steady_clock::duration lastUpdate{0}; +}; + +////////////////////////////////////////////////// +FollowActor::FollowActor() : + System(), dataPtr(std::make_unique()) +{ +} + +////////////////////////////////////////////////// +FollowActor::~FollowActor() = default; + +////////////////////////////////////////////////// +void FollowActor::Configure(const Entity &_entity, + const std::shared_ptr &_sdf, + EntityComponentManager &_ecm, + EventManager &/*_eventMgr*/) +{ + this->dataPtr->actorEntity = _entity; + + auto actorComp = _ecm.Component(_entity); + if (!actorComp) + { + ignerr << "Entity [" << _entity << "] is not an actor." << std::endl; + return; + } + + if (!_sdf->HasElement("target")) + { + ignerr << "Missing , can't follow." << std::endl; + return; + } + + auto targetName = _sdf->Get("target"); + this->dataPtr->targetEntity = _ecm.EntityByComponents(components::Name( + targetName)); + if (kNullEntity == this->dataPtr->targetEntity) + { + ignerr << "Failed to find target entity [" << targetName << "]" + << std::endl; + return; + } + + if (_sdf->HasElement("velocity")) + this->dataPtr->velocity = _sdf->Get("velocity"); + + if (_sdf->HasElement("min_distance")) + this->dataPtr->minDistance = _sdf->Get("min_distance"); + + if (_sdf->HasElement("max_distance")) + this->dataPtr->maxDistance = _sdf->Get("max_distance"); + + if (_sdf->HasElement("animation_x_vel")) + this->dataPtr->animationXVel = _sdf->Get("animation_x_vel"); + + std::string animationName; + + // If animation not provided, use first one from SDF + if (!_sdf->HasElement("animation")) + { + if (actorComp->Data().AnimationCount() < 1) + { + ignerr << "Actor SDF doesn't have any animations." << std::endl; + return; + } + + animationName = actorComp->Data().AnimationByIndex(0)->Name(); + } + else + { + animationName = _sdf->Get("animation"); + } + + if (animationName.empty()) + { + ignerr << "Can't find actor's animation name." << std::endl; + return; + } + + auto animationNameComp = _ecm.Component(_entity); + if (nullptr == animationNameComp) + { + _ecm.CreateComponent(_entity, components::AnimationName(animationName)); + } + else + { + *animationNameComp = components::AnimationName(animationName); + } + _ecm.SetChanged(_entity, + components::AnimationName::typeId, ComponentState::OneTimeChange); + + // Set custom animation time from this plugin + auto animTimeComp = _ecm.Component(_entity); + if (nullptr == animTimeComp) + { + _ecm.CreateComponent(_entity, components::AnimationTime()); + } + + math::Pose3d initialPose; + auto poseComp = _ecm.Component(_entity); + if (nullptr == poseComp) + { + _ecm.CreateComponent(_entity, components::Pose( + math::Pose3d::Zero)); + } + else + { + initialPose = poseComp->Data(); + + // We'll be setting the actor's X/Y pose with respect to the world. So we + // zero the current values. + auto newPose = initialPose; + newPose.Pos().X(0); + newPose.Pos().Y(0); + *poseComp = components::Pose(newPose); + } + + // Having a trajectory pose prevents the actor from moving with the + // SDF script + auto trajPoseComp = _ecm.Component(_entity); + if (nullptr == trajPoseComp) + { + // Leave Z to the pose component, control only 2D with Trajectory + initialPose.Pos().Z(0); + _ecm.CreateComponent(_entity, components::TrajectoryPose(initialPose)); + } +} + +////////////////////////////////////////////////// +void FollowActor::PreUpdate(const UpdateInfo &_info, + EntityComponentManager &_ecm) +{ + IGN_PROFILE("FollowActor::PreUpdate"); + + if (_info.paused) + return; + + // TODO(louise) Throttle this system + + // Time delta + std::chrono::duration dtDuration = _info.simTime - + this->dataPtr->lastUpdate; + double dt = dtDuration.count(); + + this->dataPtr->lastUpdate = _info.simTime; + + // Is there a follow target? + if (this->dataPtr->targetEntity == kNullEntity) + return; + + // Current world pose + auto trajPoseComp = _ecm.Component( + this->dataPtr->actorEntity); + auto actorPose = trajPoseComp->Data(); + auto initialPose = actorPose; + + // Current target + auto targetPose = _ecm.Component( + this->dataPtr->targetEntity)->Data(); + + // Direction to target + auto dir = targetPose.Pos() - actorPose.Pos(); + dir.Z(0); + + // Stop if too close to target + if (dir.Length() <= this->dataPtr->minDistance) + { + return; + } + + // Stop following if too far from target + if (dir.Length() > this->dataPtr->maxDistance) + { + ignmsg << "Target [" << this->dataPtr->targetEntity + << "] too far, actor [" << this->dataPtr->actorEntity + <<"] stopped following" << std::endl; + this->dataPtr->targetEntity = kNullEntity; + + return; + } + + dir.Normalize(); + + // Towards target + math::Angle yaw = atan2(dir.Y(), dir.X()); + yaw.Normalize(); + + actorPose.Pos() += dir * this->dataPtr->velocity * dt; + actorPose.Pos().Z(0); + actorPose.Rot() = math::Quaterniond(0, 0, yaw.Radian()); + + // Distance traveled is used to coordinate motion with the walking + // animation + double distanceTraveled = (actorPose.Pos() - initialPose.Pos()).Length(); + + // Update actor root pose + *trajPoseComp = components::TrajectoryPose(actorPose); + _ecm.SetChanged(this->dataPtr->actorEntity, + components::TrajectoryPose::typeId, ComponentState::OneTimeChange); + + // Update actor bone trajectories based on animation time + auto animTimeComp = _ecm.Component( + this->dataPtr->actorEntity); + + auto animTime = animTimeComp->Data() + + std::chrono::duration_cast( + std::chrono::duration(distanceTraveled * + this->dataPtr->animationXVel)); + *animTimeComp = components::AnimationTime(animTime); + _ecm.SetChanged(this->dataPtr->actorEntity, + components::AnimationTime::typeId, ComponentState::OneTimeChange); +} + +IGNITION_ADD_PLUGIN(FollowActor, System, + FollowActor::ISystemConfigure, + FollowActor::ISystemPreUpdate +) + +IGNITION_ADD_PLUGIN_ALIAS(FollowActor, "ignition::gazebo::systems::FollowActor") diff --git a/src/systems/follow_actor/FollowActor.hh b/src/systems/follow_actor/FollowActor.hh new file mode 100644 index 0000000000..36a2e9a049 --- /dev/null +++ b/src/systems/follow_actor/FollowActor.hh @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2020 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_FOLLOWACTOR_HH_ +#define IGNITION_GAZEBO_SYSTEMS_FOLLOWACTOR_HH_ + +#include +#include +#include +#include + +namespace ignition +{ +namespace gazebo +{ +// Inline bracket to help doxygen filtering. +inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { +namespace systems +{ + // Forward declarations. + class FollowActorPrivate; + + /// \class FollowActor FollowActor.hh ignition/gazebo/systems/FollowActor.hh + /// \brief Make an actor follow a target entity in the world. + /// + /// ## SDF parameters + /// + /// : Name of entity to follow. + /// + /// : Distance in meters to keep from target's origin. + /// + /// : Distance in meters from target's origin when to stop + /// following. + /// + /// : Actor's velocity in m/s + /// + /// : Actor's animation to play. If empty, the first animation in + /// the model will be used. + /// + /// : Velocity of the animation on the X axis. Used to + /// coordinate translational motion with the actor's + /// animation. + class IGNITION_GAZEBO_VISIBLE FollowActor: + public System, + public ISystemConfigure, + public ISystemPreUpdate + { + /// \brief Constructor + public: explicit FollowActor(); + + /// \brief Destructor + public: ~FollowActor() override; + + // Documentation inherited + public: void Configure(const Entity &_entity, + const std::shared_ptr &_sdf, + EntityComponentManager &_ecm, + EventManager &_eventMgr) final; + + /// Documentation inherited + public: void PreUpdate(const UpdateInfo &_info, + EntityComponentManager &_ecm) final; + + /// \brief Private data pointer. + private: std::unique_ptr dataPtr; + }; + } +} +} +} +#endif diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index c07d6e1383..8c93e19f43 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -14,6 +14,7 @@ set(tests entity_erase.cc events.cc examples_build.cc + follow_actor_system.cc imu_system.cc joint_controller_system.cc lift_drag_system.cc diff --git a/test/integration/follow_actor_system.cc b/test/integration/follow_actor_system.cc new file mode 100644 index 0000000000..562bcd905f --- /dev/null +++ b/test/integration/follow_actor_system.cc @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2020 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 + +#include "ignition/gazebo/components/Actor.hh" +#include "ignition/gazebo/components/Name.hh" +#include "ignition/gazebo/components/Pose.hh" +#include "ignition/gazebo/Server.hh" +#include "ignition/gazebo/SystemLoader.hh" +#include "ignition/gazebo/test_config.hh" + +#include "plugins/MockSystem.hh" + +#define tol 10e-4 + +using namespace ignition; +using namespace gazebo; +using namespace std::chrono_literals; + +/// \brief Test FollowActor system +class FollowActorTest : public ::testing::TestWithParam +{ + // Documentation inherited + protected: void SetUp() override + { + common::Console::SetVerbosity(4); + setenv("IGN_GAZEBO_SYSTEM_PLUGIN_PATH", + (std::string(PROJECT_BINARY_PATH) + "/lib").c_str(), 1); + } +}; + +class Relay +{ + public: Relay() + { + auto plugin = loader.LoadPlugin("libMockSystem.so", + "ignition::gazebo::MockSystem", + nullptr); + EXPECT_TRUE(plugin.has_value()); + + this->systemPtr = plugin.value(); + + this->mockSystem = static_cast( + systemPtr->QueryInterface()); + EXPECT_NE(nullptr, this->mockSystem); + } + + public: Relay &OnPreUpdate(MockSystem::CallbackType _cb) + { + this->mockSystem->preUpdateCallback = std::move(_cb); + return *this; + } + + public: Relay &OnUpdate(MockSystem::CallbackType _cb) + { + this->mockSystem->updateCallback = std::move(_cb); + return *this; + } + + public: Relay &OnPostUpdate(MockSystem::CallbackTypeConst _cb) + { + this->mockSystem->postUpdateCallback = std::move(_cb); + return *this; + } + + public: SystemPluginPtr systemPtr; + + private: SystemLoader loader; + private: MockSystem *mockSystem; +}; + + +///////////////////////////////////////////////// +TEST_P(FollowActorTest, PublishCmd) +{ + // Start server + ServerConfig serverConfig; + serverConfig.SetSdfFile(std::string(PROJECT_SOURCE_PATH) + + "/test/worlds/follow_actor.sdf"); + + Server server(serverConfig); + EXPECT_FALSE(server.Running()); + EXPECT_FALSE(*server.Running(0)); + + unsigned int preUpdateCount{0}; + unsigned int postUpdateCount{0}; + unsigned int boxMoveCount{0}; + + // Create a system that records the actor poses + Relay testSystem; + testSystem.OnPreUpdate( + [&](const gazebo::UpdateInfo &_info, + gazebo::EntityComponentManager &_ecm) + { + auto entity = _ecm.EntityByComponents( + components::Name("box")); + EXPECT_NE(kNullEntity, entity); + + auto poseComp = _ecm.Component(entity); + ASSERT_NE(nullptr, poseComp); + + // Move box every 500 ms + if (_info.iterations % 500 == 0) + { + auto pose = poseComp->Data(); + pose.Pos().X() += 0.5; + poseComp->SetData(pose, + [](const math::Pose3d &, const math::Pose3d &){return true;}); + boxMoveCount++; + } + + preUpdateCount++; + EXPECT_EQ(_info.iterations, preUpdateCount); + }); + + testSystem.OnPostUpdate( + [&](const gazebo::UpdateInfo &_info, + const gazebo::EntityComponentManager &_ecm) + { + auto actorEntity = _ecm.EntityByComponents( + components::Name("walker")); + EXPECT_NE(kNullEntity, actorEntity); + + auto targetEntity = _ecm.EntityByComponents( + components::Name("box")); + EXPECT_NE(kNullEntity, targetEntity); + + auto animNameComp = _ecm.Component(actorEntity); + ASSERT_NE(nullptr, animNameComp); + EXPECT_EQ("walking", animNameComp->Data()); + + // Animation time is always increasing + auto animTimeComp = _ecm.Component(actorEntity); + ASSERT_NE(nullptr, animTimeComp); + + // Actor pose is fixed to zero X/Y and initial Z + auto actorPoseComp = _ecm.Component(actorEntity); + ASSERT_NE(nullptr, actorPoseComp); + EXPECT_EQ(math::Pose3d(0, 0, 1, 0, 0, 0), actorPoseComp->Data()); + + // Target is moving + auto targetPoseComp = _ecm.Component(targetEntity); + ASSERT_NE(nullptr, targetPoseComp); + EXPECT_DOUBLE_EQ(1.0 + static_cast(_info.iterations / 500) * 0.5, + targetPoseComp->Data().Pos().X()); + + // Actor trajectory pose X increases to follow target + auto actorTrajPoseComp = _ecm.Component( + actorEntity); + ASSERT_NE(nullptr, actorTrajPoseComp); + + // Actor is behind box, respecting min_distance and never staying too far + auto diff = targetPoseComp->Data().Pos() - + actorTrajPoseComp->Data().Pos(); + EXPECT_GE(diff.X(), 1.0) << _info.iterations; + EXPECT_LE(diff.X(), 1.8) << _info.iterations; + + postUpdateCount++; + EXPECT_EQ(_info.iterations, postUpdateCount); + }); + server.AddSystem(testSystem.systemPtr); + + unsigned int iterations{400}; + server.Run(true /* blocking */, iterations, false /* paused */); + EXPECT_EQ(iterations, preUpdateCount); + EXPECT_EQ(iterations, postUpdateCount); + EXPECT_EQ(iterations / 500, boxMoveCount); +} + +// Run multiple times +INSTANTIATE_TEST_CASE_P(ServerRepeat, FollowActorTest, + ::testing::Range(1, 2)); diff --git a/test/worlds/follow_actor.sdf b/test/worlds/follow_actor.sdf new file mode 100644 index 0000000000..1bc7015a70 --- /dev/null +++ b/test/worlds/follow_actor.sdf @@ -0,0 +1,57 @@ + + + + + true + 1 -2 0.5 0 0 0 + + + + 1 + 0 + 0 + 1 + 0 + 1 + + 1.0 + + + + + 1 1 1 + + + + + + + + 1 1 1 + + + + 1 0 0 1 + 1 0 0 1 + 1 0 0 1 + + + + + + + walker + + 0 -2 1.0 0 0 0 + https://fuel.ignitionrobotics.org/1.0/chapulina/models/Walking actor + + box + 1.0 + 8.0 + 1 + 4.58837 + + + + From 71466eead2ffdc77d7a26515965b520724907568 Mon Sep 17 00:00:00 2001 From: Louise Poubel Date: Thu, 4 Jun 2020 18:38:33 -0700 Subject: [PATCH 2/2] PR feedback Signed-off-by: Louise Poubel --- src/systems/follow_actor/FollowActor.cc | 27 +++++++++++++++++++------ src/systems/follow_actor/FollowActor.hh | 3 ++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/systems/follow_actor/FollowActor.cc b/src/systems/follow_actor/FollowActor.cc index ea68d4219d..32d51ef60a 100644 --- a/src/systems/follow_actor/FollowActor.cc +++ b/src/systems/follow_actor/FollowActor.cc @@ -1,5 +1,5 @@ /* - * Copyright (C) 2019 Open Source Robotics Foundation + * Copyright (C) 2020 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. @@ -57,6 +57,9 @@ class ignition::gazebo::systems::FollowActorPrivate /// \brief Time of the last update. public: std::chrono::steady_clock::duration lastUpdate{0}; + + /// \brief True if currently following + public: bool following{true}; }; ////////////////////////////////////////////////// @@ -144,6 +147,7 @@ void FollowActor::Configure(const Entity &_entity, { *animationNameComp = components::AnimationName(animationName); } + // Mark as a one-time-change so that the change is propagated to the GUI _ecm.SetChanged(_entity, components::AnimationName::typeId, ComponentState::OneTimeChange); @@ -229,13 +233,22 @@ void FollowActor::PreUpdate(const UpdateInfo &_info, // Stop following if too far from target if (dir.Length() > this->dataPtr->maxDistance) { - ignmsg << "Target [" << this->dataPtr->targetEntity - << "] too far, actor [" << this->dataPtr->actorEntity - <<"] stopped following" << std::endl; - this->dataPtr->targetEntity = kNullEntity; - + if (this->dataPtr->following) + { + ignmsg << "Target [" << this->dataPtr->targetEntity + << "] too far, actor [" << this->dataPtr->actorEntity + <<"] stopped following" << std::endl; + this->dataPtr->following = false; + } return; } + if (!this->dataPtr->following) + { + ignmsg << "Target [" << this->dataPtr->targetEntity + << "] within range, actor [" << this->dataPtr->actorEntity + <<"] started following" << std::endl; + this->dataPtr->following = true; + } dir.Normalize(); @@ -253,6 +266,7 @@ void FollowActor::PreUpdate(const UpdateInfo &_info, // Update actor root pose *trajPoseComp = components::TrajectoryPose(actorPose); + // Mark as a one-time-change so that the change is propagated to the GUI _ecm.SetChanged(this->dataPtr->actorEntity, components::TrajectoryPose::typeId, ComponentState::OneTimeChange); @@ -265,6 +279,7 @@ void FollowActor::PreUpdate(const UpdateInfo &_info, std::chrono::duration(distanceTraveled * this->dataPtr->animationXVel)); *animTimeComp = components::AnimationTime(animTime); + // Mark as a one-time-change so that the change is propagated to the GUI _ecm.SetChanged(this->dataPtr->actorEntity, components::AnimationTime::typeId, ComponentState::OneTimeChange); } diff --git a/src/systems/follow_actor/FollowActor.hh b/src/systems/follow_actor/FollowActor.hh index 36a2e9a049..85743aa161 100644 --- a/src/systems/follow_actor/FollowActor.hh +++ b/src/systems/follow_actor/FollowActor.hh @@ -43,7 +43,8 @@ namespace systems /// : Distance in meters to keep from target's origin. /// /// : Distance in meters from target's origin when to stop - /// following. + /// following. When the actor is back within range it starts + /// following again. /// /// : Actor's velocity in m/s ///