ApplePickerComponent.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "ApplePickerComponent.h"
  9. #include "ApplePickingRequests.h"
  10. #include "DemoStatistics/DemoStatisticsNotifications.h"
  11. #include "FruitStorage/FruitStorageBus.h"
  12. #include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentBus.h>
  13. #include <AtomLyIntegration/CommonFeatures/Mesh/MeshComponentConstants.h>
  14. #include <AzCore/Component/ComponentApplicationBus.h>
  15. #include <AzCore/Component/TransformBus.h>
  16. #include <AzCore/EBus/Event.h>
  17. #include <AzCore/Serialization/EditContext.h>
  18. #include <AzCore/Serialization/EditContextConstants.inl>
  19. #include <AzFramework/Physics/PhysicsSystem.h>
  20. #include <AzFramework/Physics/Shape.h>
  21. #include <Integration/SimpleMotionComponentBus.h>
  22. #include <ROS2/Frame/ROS2FrameComponent.h>
  23. #include <ROS2/ROS2Bus.h>
  24. #include <ROS2/Utilities/ROS2Names.h>
  25. using namespace ROS2;
  26. namespace AppleKraken
  27. {
  28. namespace Internal
  29. {
  30. AZStd::string TaskString(const PickAppleTask& task)
  31. {
  32. if (!task.m_appleEntityId.IsValid())
  33. {
  34. return "|Task for an unspecified apple|";
  35. }
  36. return AZStd::string::format("|Task for entity id %s|", task.m_appleEntityId.ToString().c_str());
  37. }
  38. AZStd::string CurrentTaskString(const AZStd::queue<PickAppleTask>& taskQueue)
  39. {
  40. if (taskQueue.empty())
  41. {
  42. return "|No task, pick queue empty!|";
  43. }
  44. const auto& currentTask = taskQueue.front();
  45. return TaskString(currentTask);
  46. }
  47. } // namespace Internal
  48. bool ApplePickerComponent::IsBusy() const
  49. {
  50. if (!m_currentAppleTasks.empty())
  51. { // busy - still has tasks in queue
  52. return true;
  53. }
  54. // There are no apple tasks, but the effector might not be idle or prepared yet
  55. PickingState pickingState;
  56. ApplePickingRequestBus::EventResult(pickingState, m_effectorEntityId, &ApplePickingRequests::GetEffectorState);
  57. if (pickingState.m_effectorState != EffectorState::IDLE && pickingState.m_effectorState != EffectorState::PREPARED)
  58. { // Effector is not ready - still transitioning to a state. This should be a rare occurrence.
  59. AZ_Warning("ApplePicker", false, "Task queue empty but apple picker is busy since the effector is working");
  60. return true;
  61. }
  62. return false;
  63. }
  64. void ApplePickerComponent::ProcessTriggerServiceCall(const TriggerRequestPtr req, TriggerResponsePtr resp)
  65. {
  66. // TODO - also, perhaps add a check whether Kraken is in gathering position, immobile etc.
  67. if (IsBusy())
  68. {
  69. resp->success = false;
  70. resp->message = "Unable to accept request - apple picker is currently busy";
  71. return;
  72. }
  73. resp->success = true;
  74. resp->message = "Command accepted, picking operation started";
  75. StartAutomatedOperation();
  76. return;
  77. }
  78. void ApplePickerComponent::ProcessCancelServiceCall(const TriggerRequestPtr req, TriggerResponsePtr resp)
  79. {
  80. resp->success = true; // The call will be successful regardless of current state
  81. if (IsBusy())
  82. {
  83. resp->message = "Cancelling all pending tasks. Current task will be finished but no more tasks issued";
  84. // No mutexes and checks for this part of demo code: a couple of assumptions here:
  85. // 1. the queue is not empty (depends on IsBusy() implementation as it is now).
  86. // 2. callbacks are in same thread - since the executor spins_some in onTick (again we know implementation).
  87. AZStd::queue<PickAppleTask> currentTaskQueue;
  88. currentTaskQueue.push(m_currentAppleTasks.front());
  89. m_currentAppleTasks = currentTaskQueue;
  90. return;
  91. }
  92. resp->message = "Apple picker is not busy, accepting request and kindly informing you that nothing will be done about it";
  93. return;
  94. }
  95. void ApplePickerComponent::StartAutomatedOperation()
  96. {
  97. if (IsBusy())
  98. {
  99. AZ_Error("ApplePicker", false, "Tasks still in progress for current picking!");
  100. return;
  101. }
  102. // Get effector reach
  103. AZ::Obb effectorRangeGlobalBox;
  104. ApplePickingRequestBus::EventResult(effectorRangeGlobalBox, m_effectorEntityId, &ApplePickingRequests::GetEffectorReachArea);
  105. // Find out apples within the reach
  106. QueryEnvironmentForAllApplesInBox(effectorRangeGlobalBox);
  107. // Tell effector to prepare for picking
  108. ApplePickingRequestBus::Event(m_effectorEntityId, &ApplePickingRequests::PrepareForPicking);
  109. }
  110. void ApplePickerComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
  111. {
  112. // TODO handle timeouts and incoming commands
  113. m_appleGroundTruthDetector->Publish();
  114. }
  115. float ApplePickerComponent::ReportProgress()
  116. {
  117. // TODO (minor) - take into consideration current task progress (effector state)
  118. if (m_initialTasksSize == 0)
  119. {
  120. AZ_Warning("ApplePicker", false, "ReportProgress reporting 1 since no apples were found in the call");
  121. return 1.0f;
  122. }
  123. return 1.0f - (static_cast<float>(m_currentAppleTasks.size()) / static_cast<float>(m_initialTasksSize));
  124. }
  125. void ApplePickerComponent::Activate()
  126. {
  127. if (!m_effectorEntityId.IsValid())
  128. {
  129. AZ_Warning("ApplePicker", false, "Effector entity not set, assuming same entity");
  130. m_effectorEntityId = GetEntityId();
  131. }
  132. ApplePickingNotificationBus::Handler::BusConnect(GetEntityId());
  133. AZ::TickBus::Handler::BusConnect();
  134. DemoStatisticsNotificationBus::Broadcast(&DemoStatisticsNotifications::OnApplePickerSpawned, GetEntityId());
  135. auto ros2Node = ROS2Interface::Get()->GetNode();
  136. auto frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
  137. auto robotNamespace = frame->GetNamespace();
  138. auto triggerTopic = ROS2Names::GetNamespacedName(robotNamespace, m_triggerServiceTopic);
  139. m_triggerService = ros2Node->create_service<std_srvs::srv::Trigger>(
  140. triggerTopic.c_str(),
  141. [this](const TriggerRequestPtr request, TriggerResponsePtr response)
  142. {
  143. this->ProcessTriggerServiceCall(request, response);
  144. });
  145. auto cancelTopic = ROS2Names::GetNamespacedName(robotNamespace, m_cancelServiceTopic);
  146. m_cancelService = ros2Node->create_service<std_srvs::srv::Trigger>(
  147. cancelTopic.c_str(),
  148. [this](const TriggerRequestPtr request, TriggerResponsePtr response)
  149. {
  150. this->ProcessCancelServiceCall(request, response);
  151. });
  152. auto doneTopic = ROS2Names::GetNamespacedName(robotNamespace, m_doneServiceTopic);
  153. m_doneServiceClient = ros2Node->create_client<std_srvs::srv::Empty>(doneTopic.c_str());
  154. auto statusTopic = ROS2Names::GetNamespacedName(robotNamespace, m_progressTopic);
  155. m_progressPublisher = ros2Node->create_publisher<std_msgs::msg::Float32>(statusTopic.c_str(),10);
  156. m_appleGroundTruthDetector = AZStd::make_unique<AppleDetectionGroundTruth>(robotNamespace, frame->GetFrameID());
  157. auto orchestrationStatusTopic = ROS2Names::GetNamespacedName(robotNamespace, m_orchestratorStatusTopic);
  158. m_orchestrationStatusSubscriber = ros2Node->create_subscription<std_msgs::msg::String>(orchestrationStatusTopic.c_str(),10,
  159. [this](const std_msgs::msg::String::ConstSharedPtr msg)
  160. {
  161. AZStd::string label(msg->data.c_str(), msg->data.size());
  162. DemoStatisticsNotificationBus::Broadcast(&DemoStatisticsNotifications::SetApplePickerStatus, GetEntityId(), label);
  163. });
  164. }
  165. void ApplePickerComponent::Deactivate()
  166. {
  167. m_appleGroundTruthDetector.reset();
  168. m_triggerService.reset();
  169. m_cancelService.reset();
  170. AZ::TickBus::Handler::BusDisconnect();
  171. ApplePickingNotificationBus::Handler::BusDisconnect();
  172. }
  173. void ApplePickerComponent::Reflect(AZ::ReflectContext* context)
  174. {
  175. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  176. {
  177. serialize->Class<ApplePickerComponent, AZ::Component>()
  178. ->Version(5)
  179. ->Field("TriggerServiceTopic", &ApplePickerComponent::m_triggerServiceTopic)
  180. ->Field("CancelServiceTopic", &ApplePickerComponent::m_cancelServiceTopic)
  181. ->Field("ProgressTopic", &ApplePickerComponent::m_progressTopic)
  182. ->Field("DoneServiceTopic", &ApplePickerComponent::m_doneServiceTopic)
  183. ->Field("EffectorEntity", &ApplePickerComponent::m_effectorEntityId)
  184. ->Field("FruitStorageEntity", &ApplePickerComponent::m_fruitStorageEntityId)
  185. ->Field("RetrievalPointEntity", &ApplePickerComponent::m_retrievalPointEntityId)
  186. ->Field("AppleEntryAnimationEntity", &ApplePickerComponent::m_entryAnimationEntityId)
  187. ->Field("OrchestratorStatusTopic", &ApplePickerComponent::m_orchestratorStatusTopic);
  188. if (AZ::EditContext* ec = serialize->GetEditContext())
  189. {
  190. ec->Class<ApplePickerComponent>("Apple picking component", "A demo component for apple picking")
  191. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  192. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
  193. ->Attribute(AZ::Edit::Attributes::Category, "AppleKraken")
  194. ->DataElement(
  195. AZ::Edit::UIHandlers::Default,
  196. &ApplePickerComponent::m_triggerServiceTopic,
  197. "Trigger",
  198. "ROS2 service name for gathering trigger")
  199. ->DataElement(
  200. AZ::Edit::UIHandlers::Default,
  201. &ApplePickerComponent::m_cancelServiceTopic,
  202. "Cancel",
  203. "ROS2 service name to cancel ongoing gathering")
  204. ->DataElement(
  205. AZ::Edit::UIHandlers::Default,
  206. &ApplePickerComponent::m_orchestratorStatusTopic,
  207. "OrchestratorStatus",
  208. "ROS2 topic name with robot's status")
  209. ->DataElement(
  210. AZ::Edit::UIHandlers::Default,
  211. &ApplePickerComponent::m_progressTopic,
  212. "Status",
  213. "ROS2 topic that reports progress of gathering")
  214. ->DataElement(
  215. AZ::Edit::UIHandlers::Default,
  216. &ApplePickerComponent::m_doneServiceTopic,
  217. "Done",
  218. "ROS2 service name send on finish gathering")
  219. ->DataElement(
  220. AZ::Edit::UIHandlers::EntityId,
  221. &ApplePickerComponent::m_effectorEntityId,
  222. "Effector",
  223. "Effector (manipulator) entity")
  224. ->DataElement(
  225. AZ::Edit::UIHandlers::EntityId,
  226. &ApplePickerComponent::m_fruitStorageEntityId,
  227. "Fruit Storage",
  228. "Fruit storage entity")
  229. ->DataElement(
  230. AZ::Edit::UIHandlers::EntityId,
  231. &ApplePickerComponent::m_retrievalPointEntityId,
  232. "Retrieval point",
  233. "Entity which holds the point of the apple retrieval chute")
  234. ->DataElement(
  235. AZ::Edit::UIHandlers::EntityId,
  236. &ApplePickerComponent::m_entryAnimationEntityId,
  237. "Animation point",
  238. "Entity which holds the point of apple entry to chute for animation");
  239. }
  240. }
  241. }
  242. void ApplePickerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  243. {
  244. required.push_back(AZ_CRC("ROS2Frame"));
  245. }
  246. void ApplePickerComponent::EffectorReadyForPicking()
  247. {
  248. PickNextApple();
  249. }
  250. void ApplePickerComponent::ApplePicked()
  251. {
  252. if (m_currentAppleTasks.empty())
  253. {
  254. AZ_Error("ApplePicker", false, "ApplePicked called but no current task");
  255. return;
  256. }
  257. AZ_TracePrintf("ApplePicker", "%s. Picked apple\n", Internal::CurrentTaskString(m_currentAppleTasks).c_str());
  258. // Disappear the apple
  259. AZ::Render::MeshComponentRequestBus::Event(
  260. m_currentAppleTasks.front().m_appleEntityId, &AZ::Render::MeshComponentRequestBus::Events::SetVisibility, false);
  261. }
  262. void ApplePickerComponent::AppleRetrieved()
  263. {
  264. if (m_currentAppleTasks.empty())
  265. {
  266. AZ_Error("ApplePicker", false, "AppleRetrieved called but no current task");
  267. return;
  268. }
  269. AZ_TracePrintf(
  270. "ApplePicker", "%s. An apple has been retrieved and stored\n", Internal::CurrentTaskString(m_currentAppleTasks).c_str());
  271. m_currentAppleTasks.pop();
  272. if (!m_fruitStorageEntityId.IsValid())
  273. {
  274. AZ_Warning("ApplePicker", false, "Fruit storage entity not set, assuming same entity");
  275. m_fruitStorageEntityId = GetEntityId();
  276. }
  277. Tags applePickingEventTags = { kPickingAutomatedEventTag };
  278. FruitStorageRequestsBus::Event(m_fruitStorageEntityId, &FruitStorageRequests::AddApple, applePickingEventTags);
  279. DemoStatisticsNotificationBus::Broadcast(&DemoStatisticsNotifications::AddApple, applePickingEventTags);
  280. if (!m_entryAnimationEntityId.IsValid())
  281. {
  282. AZ_Warning("ApplePicker", false, "No animation for apple entry will be played since entry animation entity is invalid");
  283. }
  284. else
  285. {
  286. EMotionFX::Integration::SimpleMotionComponentRequestBus::Event(
  287. m_entryAnimationEntityId, &EMotionFX::Integration::SimpleMotionComponentRequestBus::Events::PlayMotion);
  288. }
  289. PickNextApple();
  290. }
  291. void ApplePickerComponent::PickingFailed(const AZStd::string& reason)
  292. { // TODO - refactor common code (debugs, checks)
  293. if (m_currentAppleTasks.empty())
  294. {
  295. AZ_Error("ApplePicker", false, "PickingFailed called but no current task");
  296. return;
  297. }
  298. AZ_TracePrintf(
  299. "ApplePicker", "%s. Picking failed due to: %s\n", Internal::CurrentTaskString(m_currentAppleTasks).c_str(), reason.c_str());
  300. m_currentAppleTasks.pop();
  301. Tags applePickingEventTags = { kPickingFailedEventTag, kPickingAutomatedEventTag };
  302. DemoStatisticsNotificationBus::Broadcast(&DemoStatisticsNotifications::AddApple, applePickingEventTags);
  303. PickNextApple();
  304. }
  305. void ApplePickerComponent::PickNextApple()
  306. {
  307. auto message = std_msgs::msg::Float32 ();
  308. message.data = this->ReportProgress();
  309. m_progressPublisher->publish(message);
  310. AZ_TracePrintf("ApplePicker", "Pick next apple");
  311. if (!m_currentAppleTasks.empty())
  312. { // Get another apple!
  313. ApplePickingRequestBus::Event(m_effectorEntityId, &ApplePickingRequests::PickApple, m_currentAppleTasks.front());
  314. return;
  315. }
  316. AZ_TracePrintf("ApplePicker", "No more apples!");
  317. auto request = std::make_shared<std_srvs::srv::Empty::Request>();
  318. m_doneServiceClient->async_send_request(request);
  319. ApplePickingRequestBus::Event(m_effectorEntityId, &ApplePickingRequests::FinishPicking);
  320. }
  321. void ApplePickerComponent::QueryEnvironmentForAllApplesInBox(const AZ::Obb& globalBox)
  322. {
  323. if (!m_retrievalPointEntityId.IsValid())
  324. {
  325. AZ_Error("ApplePicker", false, "Retrieval chute entity not set for ApplePickerComponent!");
  326. return;
  327. }
  328. // Scene query for `apple` entity, we want visible entities with exact 'Apple' name
  329. auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get();
  330. // clear
  331. m_currentAppleTasks = AZStd::queue<PickAppleTask>();
  332. // apples need to be unique
  333. AZStd::unordered_set<AZ::EntityId> found_apples;
  334. AZStd::vector<PickAppleTask> appleTasks;
  335. for (auto& physicScene : physicsSystem->GetAllScenes())
  336. {
  337. if (!physicScene)
  338. {
  339. continue;
  340. }
  341. AzPhysics::OverlapRequest request = AzPhysics::OverlapRequestHelpers::CreateBoxOverlapRequest(
  342. 2.0f * globalBox.GetHalfLengths(),
  343. AZ::Transform::CreateFromQuaternionAndTranslation(globalBox.GetRotation(), globalBox.GetPosition()));
  344. // we want maximum overlap buffer set in `physxsystemconfiguration.setreg`
  345. request.m_maxResults = physicsSystem->GetConfiguration()->m_overlapBufferSize;
  346. AzPhysics::SceneQueryHits results = physicScene->QueryScene(&request);
  347. for (const auto& r : results.m_hits)
  348. {
  349. AZStd::string entity_name;
  350. AZ::ComponentApplicationBus::BroadcastResult(entity_name, &AZ::ComponentApplicationRequests::GetEntityName, r.m_entityId);
  351. if (entity_name != "Apple")
  352. {
  353. continue;
  354. }
  355. if (found_apples.contains(r.m_entityId))
  356. {
  357. continue;
  358. }
  359. bool is_visible = false;
  360. AZ::Render::MeshComponentRequestBus::EventResult(
  361. is_visible, r.m_entityId, &AZ::Render::MeshComponentRequests::GetVisibility);
  362. if (!is_visible)
  363. {
  364. continue;
  365. }
  366. AZ::Transform targetTM = AZ::Transform::CreateIdentity();
  367. AZ::TransformBus::EventResult(targetTM, r.m_entityId, &AZ::TransformBus::Events::GetWorldTM);
  368. PickAppleTask t;
  369. t.m_appleEntityId = r.m_entityId;
  370. t.m_appleBoundingBox = r.m_shape->GetAabb(targetTM);
  371. t.m_middle = targetTM.GetTranslation(); /// TODO consider `r.m_position` here
  372. appleTasks.push_back(t);
  373. found_apples.emplace(r.m_entityId);
  374. }
  375. }
  376. AZ::Transform m_retrievalPointTransform;
  377. AZ::TransformBus::EventResult(m_retrievalPointTransform, m_retrievalPointEntityId, &AZ::TransformBus::Events::GetWorldTM);
  378. auto retrievalPoint = m_retrievalPointTransform.GetTranslation();
  379. std::sort(
  380. appleTasks.begin(),
  381. appleTasks.end(),
  382. [retrievalPoint](const PickAppleTask& a, const PickAppleTask& b) -> bool
  383. { // a is closer than b to the retrieval point
  384. return (retrievalPoint - a.m_middle).GetLengthSq() <= (retrievalPoint - b.m_middle).GetLengthSq();
  385. });
  386. m_appleGroundTruthDetector->UpdateGroundTruth(appleTasks);
  387. for (const auto& appleTask : appleTasks)
  388. {
  389. m_currentAppleTasks.emplace(appleTask);
  390. }
  391. m_initialTasksSize = m_currentAppleTasks.size();
  392. AZ_Printf("ApplePickerComponent", "There are %d apples in reach box \n", m_currentAppleTasks.size());
  393. }
  394. } // namespace AppleKraken