ApplePickerComponent.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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 - (m_currentAppleTasks.size() / 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();
  133. AZ::TickBus::Handler::BusConnect();
  134. auto ros2Node = ROS2Interface::Get()->GetNode();
  135. auto frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
  136. auto robotNamespace = frame->GetNamespace();
  137. auto triggerTopic = ROS2Names::GetNamespacedName(robotNamespace, m_triggerServiceTopic);
  138. m_triggerService = ros2Node->create_service<std_srvs::srv::Trigger>(
  139. triggerTopic.c_str(),
  140. [this](const TriggerRequestPtr request, TriggerResponsePtr response)
  141. {
  142. this->ProcessTriggerServiceCall(request, response);
  143. });
  144. auto cancelTopic = ROS2Names::GetNamespacedName(robotNamespace, m_cancelServiceTopic);
  145. m_cancelService = ros2Node->create_service<std_srvs::srv::Trigger>(
  146. cancelTopic.c_str(),
  147. [this](const TriggerRequestPtr request, TriggerResponsePtr response)
  148. {
  149. this->ProcessCancelServiceCall(request, response);
  150. });
  151. m_appleGroundTruthDetector = AZStd::make_unique<AppleDetectionGroundTruth>(robotNamespace, frame->GetFrameID());
  152. }
  153. void ApplePickerComponent::Deactivate()
  154. {
  155. m_appleGroundTruthDetector.reset();
  156. m_triggerService.reset();
  157. m_cancelService.reset();
  158. AZ::TickBus::Handler::BusDisconnect();
  159. ApplePickingNotificationBus::Handler::BusDisconnect();
  160. }
  161. void ApplePickerComponent::Reflect(AZ::ReflectContext* context)
  162. {
  163. if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
  164. {
  165. serialize->Class<ApplePickerComponent, AZ::Component>()
  166. ->Version(4)
  167. ->Field("TriggerServiceTopic", &ApplePickerComponent::m_triggerServiceTopic)
  168. ->Field("CancelServiceTopic", &ApplePickerComponent::m_cancelServiceTopic)
  169. ->Field("EffectorEntity", &ApplePickerComponent::m_effectorEntityId)
  170. ->Field("FruitStorageEntity", &ApplePickerComponent::m_fruitStorageEntityId)
  171. ->Field("RetrievalPointEntity", &ApplePickerComponent::m_retrievalPointEntityId)
  172. ->Field("AppleEntryAnimationEntity", &ApplePickerComponent::m_entryAnimationEntityId);
  173. if (AZ::EditContext* ec = serialize->GetEditContext())
  174. {
  175. ec->Class<ApplePickerComponent>("Apple picking component", "A demo component for apple picking")
  176. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  177. ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game"))
  178. ->Attribute(AZ::Edit::Attributes::Category, "AppleKraken")
  179. ->DataElement(
  180. AZ::Edit::UIHandlers::Default,
  181. &ApplePickerComponent::m_triggerServiceTopic,
  182. "Trigger",
  183. "ROS2 service name for gathering trigger")
  184. ->DataElement(
  185. AZ::Edit::UIHandlers::Default,
  186. &ApplePickerComponent::m_cancelServiceTopic,
  187. "Cancel",
  188. "ROS2 service name to cancel ongoing gathering")
  189. ->DataElement(
  190. AZ::Edit::UIHandlers::EntityId,
  191. &ApplePickerComponent::m_effectorEntityId,
  192. "Effector",
  193. "Effector (manipulator) entity")
  194. ->DataElement(
  195. AZ::Edit::UIHandlers::EntityId,
  196. &ApplePickerComponent::m_fruitStorageEntityId,
  197. "Fruit Storage",
  198. "Fruit storage entity")
  199. ->DataElement(
  200. AZ::Edit::UIHandlers::EntityId,
  201. &ApplePickerComponent::m_retrievalPointEntityId,
  202. "Retrieval point",
  203. "Entity which holds the point of the apple retrieval chute")
  204. ->DataElement(
  205. AZ::Edit::UIHandlers::EntityId,
  206. &ApplePickerComponent::m_entryAnimationEntityId,
  207. "Animation point",
  208. "Entity which holds the point of apple entry to chute for animation");
  209. }
  210. }
  211. }
  212. void ApplePickerComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
  213. {
  214. required.push_back(AZ_CRC("ROS2Frame"));
  215. }
  216. void ApplePickerComponent::EffectorReadyForPicking()
  217. {
  218. PickNextApple();
  219. }
  220. void ApplePickerComponent::ApplePicked()
  221. {
  222. if (m_currentAppleTasks.empty())
  223. {
  224. AZ_Error("ApplePicker", false, "ApplePicked called but no current task");
  225. return;
  226. }
  227. AZ_TracePrintf("ApplePicker", "%s. Picked apple\n", Internal::CurrentTaskString(m_currentAppleTasks).c_str());
  228. // Disappear the apple
  229. AZ::Render::MeshComponentRequestBus::Event(
  230. m_currentAppleTasks.front().m_appleEntityId, &AZ::Render::MeshComponentRequestBus::Events::SetVisibility, false);
  231. }
  232. void ApplePickerComponent::AppleRetrieved()
  233. {
  234. if (m_currentAppleTasks.empty())
  235. {
  236. AZ_Error("ApplePicker", false, "AppleRetrieved called but no current task");
  237. return;
  238. }
  239. AZ_TracePrintf(
  240. "ApplePicker", "%s. An apple has been retrieved and stored\n", Internal::CurrentTaskString(m_currentAppleTasks).c_str());
  241. m_currentAppleTasks.pop();
  242. if (!m_fruitStorageEntityId.IsValid())
  243. {
  244. AZ_Warning("ApplePicker", false, "Fruit storage entity not set, assuming same entity");
  245. m_fruitStorageEntityId = GetEntityId();
  246. }
  247. Tags applePickingEventTags = { kPickingAutomatedEventTag };
  248. FruitStorageRequestsBus::Event(m_fruitStorageEntityId, &FruitStorageRequests::AddApple, applePickingEventTags);
  249. DemoStatisticsNotificationBus::Broadcast(&DemoStatisticsNotifications::AddApple, applePickingEventTags);
  250. if (!m_entryAnimationEntityId.IsValid())
  251. {
  252. AZ_Warning("ApplePicker", false, "No animation for apple entry will be played since entry animation entity is invalid");
  253. }
  254. else
  255. {
  256. EMotionFX::Integration::SimpleMotionComponentRequestBus::Event(
  257. m_entryAnimationEntityId, &EMotionFX::Integration::SimpleMotionComponentRequestBus::Events::PlayMotion);
  258. }
  259. PickNextApple();
  260. }
  261. void ApplePickerComponent::PickingFailed(const AZStd::string& reason)
  262. { // TODO - refactor common code (debugs, checks)
  263. if (m_currentAppleTasks.empty())
  264. {
  265. AZ_Error("ApplePicker", false, "PickingFailed called but no current task");
  266. return;
  267. }
  268. AZ_TracePrintf(
  269. "ApplePicker", "%s. Picking failed due to: %s\n", Internal::CurrentTaskString(m_currentAppleTasks).c_str(), reason.c_str());
  270. m_currentAppleTasks.pop();
  271. Tags applePickingEventTags = { kPickingFailedEventTag, kPickingAutomatedEventTag };
  272. DemoStatisticsNotificationBus::Broadcast(&DemoStatisticsNotifications::AddApple, applePickingEventTags);
  273. PickNextApple();
  274. }
  275. void ApplePickerComponent::PickNextApple()
  276. {
  277. AZ_TracePrintf("ApplePicker", "Pick next apple");
  278. if (!m_currentAppleTasks.empty())
  279. { // Get another apple!
  280. ApplePickingRequestBus::Event(m_effectorEntityId, &ApplePickingRequests::PickApple, m_currentAppleTasks.front());
  281. return;
  282. }
  283. AZ_TracePrintf("ApplePicker", "No more apples!");
  284. ApplePickingRequestBus::Event(m_effectorEntityId, &ApplePickingRequests::FinishPicking);
  285. }
  286. void ApplePickerComponent::QueryEnvironmentForAllApplesInBox(const AZ::Obb& globalBox)
  287. {
  288. if (!m_retrievalPointEntityId.IsValid())
  289. {
  290. AZ_Error("ApplePicker", false, "Retrieval chute entity not set for ApplePickerComponent!");
  291. return;
  292. }
  293. // Scene query for `apple` entity, we want visible entities with exact 'Apple' name
  294. auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get();
  295. // clear
  296. m_currentAppleTasks = AZStd::queue<PickAppleTask>();
  297. // apples need to be unique
  298. AZStd::unordered_set<AZ::EntityId> found_apples;
  299. AZStd::vector<PickAppleTask> appleTasks;
  300. for (auto& physicScene : physicsSystem->GetAllScenes())
  301. {
  302. if (!physicScene)
  303. {
  304. continue;
  305. }
  306. AzPhysics::OverlapRequest request = AzPhysics::OverlapRequestHelpers::CreateBoxOverlapRequest(
  307. 2.0f * globalBox.GetHalfLengths(),
  308. AZ::Transform::CreateFromQuaternionAndTranslation(globalBox.GetRotation(), globalBox.GetPosition()));
  309. // we want maximum overlap buffer set in `physxsystemconfiguration.setreg`
  310. request.m_maxResults = physicsSystem->GetConfiguration()->m_overlapBufferSize;
  311. AzPhysics::SceneQueryHits results = physicScene->QueryScene(&request);
  312. for (const auto& r : results.m_hits)
  313. {
  314. AZStd::string entity_name;
  315. AZ::ComponentApplicationBus::BroadcastResult(entity_name, &AZ::ComponentApplicationRequests::GetEntityName, r.m_entityId);
  316. if (entity_name != "Apple")
  317. {
  318. continue;
  319. }
  320. if (found_apples.contains(r.m_entityId))
  321. {
  322. continue;
  323. }
  324. bool is_visible = false;
  325. AZ::Render::MeshComponentRequestBus::EventResult(
  326. is_visible, r.m_entityId, &AZ::Render::MeshComponentRequests::GetVisibility);
  327. if (!is_visible)
  328. {
  329. continue;
  330. }
  331. AZ::Transform targetTM = AZ::Transform::CreateIdentity();
  332. AZ::TransformBus::EventResult(targetTM, r.m_entityId, &AZ::TransformBus::Events::GetWorldTM);
  333. PickAppleTask t;
  334. t.m_appleEntityId = r.m_entityId;
  335. t.m_appleBoundingBox = r.m_shape->GetAabb(targetTM);
  336. t.m_middle = targetTM.GetTranslation(); /// TODO consider `r.m_position` here
  337. appleTasks.push_back(t);
  338. found_apples.emplace(r.m_entityId);
  339. }
  340. }
  341. AZ::Transform m_retrievalPointTransform;
  342. AZ::TransformBus::EventResult(m_retrievalPointTransform, m_retrievalPointEntityId, &AZ::TransformBus::Events::GetWorldTM);
  343. auto retrievalPoint = m_retrievalPointTransform.GetTranslation();
  344. std::sort(
  345. appleTasks.begin(),
  346. appleTasks.end(),
  347. [retrievalPoint](const PickAppleTask& a, const PickAppleTask& b) -> bool
  348. { // a is closer than b to the retrieval point
  349. return (retrievalPoint - a.m_middle).GetLengthSq() <= (retrievalPoint - b.m_middle).GetLengthSq();
  350. });
  351. m_appleGroundTruthDetector->UpdateGroundTruth(appleTasks);
  352. for (const auto& appleTask : appleTasks)
  353. {
  354. m_currentAppleTasks.emplace(appleTask);
  355. }
  356. m_initialTasksSize = m_currentAppleTasks.size();
  357. AZ_Printf("ApplePickerComponent", "There are %d apples in reach box \n", m_currentAppleTasks.size());
  358. }
  359. } // namespace AppleKraken