picking_service.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. #include "picking_service.h"
  2. #include "../../render/gl/camera.h"
  3. #include "../core/component.h"
  4. #include "../core/world.h"
  5. #include <algorithm>
  6. #include <limits>
  7. #include <qglobal.h>
  8. #include <qpoint.h>
  9. #include <qvectornd.h>
  10. #include <vector>
  11. namespace Game::Systems {
  12. auto PickingService::world_to_screen(const Render::GL::Camera &cam, int view_w,
  13. int view_h, const QVector3D &world,
  14. QPointF &out) -> bool {
  15. return cam.world_to_screen(world, qreal(view_w), qreal(view_h), out);
  16. }
  17. auto PickingService::screen_to_ground(const Render::GL::Camera &cam, int view_w,
  18. int view_h, const QPointF &screen_pt,
  19. QVector3D &out_world) -> bool {
  20. if (view_w <= 0 || view_h <= 0) {
  21. return false;
  22. }
  23. return cam.screen_to_ground(screen_pt.x(), screen_pt.y(), qreal(view_w),
  24. qreal(view_h), out_world);
  25. }
  26. auto PickingService::project_bounds(const Render::GL::Camera &cam,
  27. const QVector3D &center, float hx, float hz,
  28. int view_w, int view_h,
  29. QRectF &out) -> bool {
  30. QVector3D const corners[4] = {
  31. QVector3D(center.x() - hx, center.y(), center.z() - hz),
  32. QVector3D(center.x() + hx, center.y(), center.z() - hz),
  33. QVector3D(center.x() + hx, center.y(), center.z() + hz),
  34. QVector3D(center.x() - hx, center.y(), center.z() + hz)};
  35. QPointF screen_pts[4];
  36. for (int i = 0; i < 4; ++i) {
  37. if (!world_to_screen(cam, view_w, view_h, corners[i], screen_pts[i])) {
  38. return false;
  39. }
  40. }
  41. qreal min_x = screen_pts[0].x();
  42. qreal max_x = screen_pts[0].x();
  43. qreal min_y = screen_pts[0].y();
  44. qreal max_y = screen_pts[0].y();
  45. for (int i = 1; i < 4; ++i) {
  46. min_x = std::min(min_x, screen_pts[i].x());
  47. max_x = std::max(max_x, screen_pts[i].x());
  48. min_y = std::min(min_y, screen_pts[i].y());
  49. max_y = std::max(max_y, screen_pts[i].y());
  50. }
  51. out = QRectF(QPointF(min_x, min_y), QPointF(max_x, max_y));
  52. return true;
  53. }
  54. auto PickingService::update_hover(float sx, float sy,
  55. Engine::Core::World &world,
  56. const Render::GL::Camera &camera, int view_w,
  57. int view_h) -> Engine::Core::EntityID {
  58. if (sx < 0 || sy < 0 || sx >= view_w || sy >= view_h) {
  59. m_prev_hover_id = 0;
  60. return 0;
  61. }
  62. auto prev_hover = m_prev_hover_id;
  63. Engine::Core::EntityID const picked =
  64. pick_single(sx, sy, world, camera, view_w, view_h, 0, false);
  65. if (picked != 0 && picked != prev_hover) {
  66. m_hover_grace_ticks = 6;
  67. }
  68. Engine::Core::EntityID current_hover = picked;
  69. if (current_hover == 0 && prev_hover != 0 && m_hover_grace_ticks > 0) {
  70. current_hover = prev_hover;
  71. }
  72. if (m_hover_grace_ticks > 0) {
  73. --m_hover_grace_ticks;
  74. }
  75. m_prev_hover_id = current_hover;
  76. return current_hover;
  77. }
  78. auto PickingService::pick_single(
  79. float sx, float sy, Engine::Core::World &world,
  80. const Render::GL::Camera &camera, int view_w, int view_h, int owner_filter,
  81. bool prefer_buildings_first) -> Engine::Core::EntityID {
  82. const float base_unit_pick_radius = 30.0F;
  83. const float base_building_pick_radius = 30.0F;
  84. float best_unit_dist2 = std::numeric_limits<float>::max();
  85. float best_building_dist2 = std::numeric_limits<float>::max();
  86. Engine::Core::EntityID best_unit_id = 0;
  87. Engine::Core::EntityID best_building_id = 0;
  88. auto ents = world.get_entities_with<Engine::Core::TransformComponent>();
  89. for (auto *e : ents) {
  90. if (!e->has_component<Engine::Core::UnitComponent>()) {
  91. continue;
  92. }
  93. auto *t = e->get_component<Engine::Core::TransformComponent>();
  94. auto *u = e->get_component<Engine::Core::UnitComponent>();
  95. if (owner_filter != 0 && u->owner_id != owner_filter) {
  96. continue;
  97. }
  98. QPointF sp;
  99. if (!camera.world_to_screen(
  100. QVector3D(t->position.x, t->position.y, t->position.z), view_w,
  101. view_h, sp)) {
  102. continue;
  103. }
  104. auto const dx = float(sx - sp.x());
  105. auto const dy = float(sy - sp.y());
  106. float const d2 = dx * dx + dy * dy;
  107. if (e->has_component<Engine::Core::BuildingComponent>()) {
  108. bool hit = false;
  109. float pick_dist2 = d2;
  110. const float margin_x_z = 1.6F;
  111. const float margin_y = 1.2F;
  112. float const hx = std::max(0.6F, t->scale.x * margin_x_z);
  113. float const hz = std::max(0.6F, t->scale.z * margin_x_z);
  114. float const hy = std::max(0.5F, t->scale.y * margin_y);
  115. QPointF pts[8];
  116. int ok_count = 0;
  117. auto project = [&](const QVector3D &w, QPointF &out) {
  118. return camera.world_to_screen(w, view_w, view_h, out);
  119. };
  120. ok_count += static_cast<int>(
  121. project(QVector3D(t->position.x - hx, t->position.y + 0.0F,
  122. t->position.z - hz),
  123. pts[0]));
  124. ok_count += static_cast<int>(
  125. project(QVector3D(t->position.x + hx, t->position.y + 0.0F,
  126. t->position.z - hz),
  127. pts[1]));
  128. ok_count += static_cast<int>(
  129. project(QVector3D(t->position.x + hx, t->position.y + 0.0F,
  130. t->position.z + hz),
  131. pts[2]));
  132. ok_count += static_cast<int>(
  133. project(QVector3D(t->position.x - hx, t->position.y + 0.0F,
  134. t->position.z + hz),
  135. pts[3]));
  136. ok_count += static_cast<int>(project(
  137. QVector3D(t->position.x - hx, t->position.y + hy, t->position.z - hz),
  138. pts[4]));
  139. ok_count += static_cast<int>(project(
  140. QVector3D(t->position.x + hx, t->position.y + hy, t->position.z - hz),
  141. pts[5]));
  142. ok_count += static_cast<int>(project(
  143. QVector3D(t->position.x + hx, t->position.y + hy, t->position.z + hz),
  144. pts[6]));
  145. ok_count += static_cast<int>(project(
  146. QVector3D(t->position.x - hx, t->position.y + hy, t->position.z + hz),
  147. pts[7]));
  148. if (ok_count == 8) {
  149. qreal min_x = pts[0].x();
  150. qreal max_x = pts[0].x();
  151. qreal min_y = pts[0].y();
  152. qreal max_y = pts[0].y();
  153. for (int i = 1; i < 8; ++i) {
  154. min_x = std::min(min_x, pts[i].x());
  155. max_x = std::max(max_x, pts[i].x());
  156. min_y = std::min(min_y, pts[i].y());
  157. max_y = std::max(max_y, pts[i].y());
  158. }
  159. if (sx >= min_x && sx <= max_x && sy >= min_y && sy <= max_y) {
  160. hit = true;
  161. pick_dist2 = d2;
  162. }
  163. }
  164. if (!hit) {
  165. float const scale_x_z =
  166. std::max(std::max(t->scale.x, t->scale.z), 1.0F);
  167. float const rp = base_building_pick_radius * scale_x_z;
  168. float const r2 = rp * rp;
  169. if (d2 <= r2) {
  170. hit = true;
  171. }
  172. }
  173. if (hit && pick_dist2 < best_building_dist2) {
  174. best_building_dist2 = pick_dist2;
  175. best_building_id = e->get_id();
  176. }
  177. } else {
  178. float const r2 = base_unit_pick_radius * base_unit_pick_radius;
  179. if (d2 <= r2 && d2 < best_unit_dist2) {
  180. best_unit_dist2 = d2;
  181. best_unit_id = e->get_id();
  182. }
  183. }
  184. }
  185. if (prefer_buildings_first) {
  186. if ((best_building_id != 0U) &&
  187. ((best_unit_id == 0U) || best_building_dist2 <= best_unit_dist2)) {
  188. return best_building_id;
  189. }
  190. if (best_unit_id != 0U) {
  191. return best_unit_id;
  192. }
  193. } else {
  194. if (best_unit_id != 0U) {
  195. return best_unit_id;
  196. }
  197. if (best_building_id != 0U) {
  198. return best_building_id;
  199. }
  200. }
  201. return 0;
  202. }
  203. auto PickingService::pick_unit_first(
  204. float sx, float sy, Engine::Core::World &world,
  205. const Render::GL::Camera &camera, int view_w, int view_h,
  206. int owner_filter) -> Engine::Core::EntityID {
  207. auto id =
  208. pick_single(sx, sy, world, camera, view_w, view_h, owner_filter, false);
  209. if (id != 0) {
  210. return id;
  211. }
  212. return pick_single(sx, sy, world, camera, view_w, view_h, owner_filter, true);
  213. }
  214. auto PickingService::pick_in_rect(
  215. float x1, float y1, float x2, float y2, Engine::Core::World &world,
  216. const Render::GL::Camera &camera, int view_w, int view_h,
  217. int owner_filter) -> std::vector<Engine::Core::EntityID> {
  218. float const min_x = std::min(x1, x2);
  219. float const max_x = std::max(x1, x2);
  220. float const min_y = std::min(y1, y2);
  221. float const max_y = std::max(y1, y2);
  222. std::vector<Engine::Core::EntityID> picked;
  223. auto ents = world.get_entities_with<Engine::Core::TransformComponent>();
  224. for (auto *e : ents) {
  225. if (!e->has_component<Engine::Core::UnitComponent>()) {
  226. continue;
  227. }
  228. if (e->has_component<Engine::Core::BuildingComponent>()) {
  229. continue;
  230. }
  231. auto *u = e->get_component<Engine::Core::UnitComponent>();
  232. if ((u == nullptr) || u->owner_id != owner_filter) {
  233. continue;
  234. }
  235. auto *t = e->get_component<Engine::Core::TransformComponent>();
  236. QPointF sp;
  237. if (!camera.world_to_screen(
  238. QVector3D(t->position.x, t->position.y, t->position.z), view_w,
  239. view_h, sp)) {
  240. continue;
  241. }
  242. if (sp.x() >= min_x && sp.x() <= max_x && sp.y() >= min_y &&
  243. sp.y() <= max_y) {
  244. picked.push_back(e->get_id());
  245. }
  246. }
  247. return picked;
  248. }
  249. } // namespace Game::Systems