collision_system.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
  3. // 2018 Ingo Ruhnke <grumbel@gmail.com>
  4. //
  5. // This program is free software: you can redistribute it and/or modify
  6. // it under the terms of the GNU General Public License as published by
  7. // the Free Software Foundation, either version 3 of the License, or
  8. // (at your option) any later version.
  9. //
  10. // This program is distributed in the hope that it will be useful,
  11. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. // GNU General Public License for more details.
  14. //
  15. // You should have received a copy of the GNU General Public License
  16. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. #include "collision/collision_system.hpp"
  18. #include "collision/collision.hpp"
  19. #include "collision/collision_movement_manager.hpp"
  20. #include "editor/editor.hpp"
  21. #include "math/aatriangle.hpp"
  22. #include "math/rect.hpp"
  23. #include "object/player.hpp"
  24. #include "object/tilemap.hpp"
  25. #include "supertux/constants.hpp"
  26. #include "supertux/sector.hpp"
  27. #include "supertux/tile.hpp"
  28. #include "video/color.hpp"
  29. #include "video/drawing_context.hpp"
  30. namespace {
  31. const float MAX_SPEED = 16.0f;
  32. static const float FORGIVENESS = 256.f; // 16.f * 16.f - half a tile by half a tile.
  33. } // namespace
  34. CollisionSystem::CollisionSystem(Sector& sector) :
  35. m_sector(sector),
  36. m_objects(),
  37. m_ground_movement_manager(new CollisionGroundMovementManager)
  38. {
  39. }
  40. void
  41. CollisionSystem::add(CollisionObject* object)
  42. {
  43. object->set_ground_movement_manager(m_ground_movement_manager);
  44. m_objects.push_back(object);
  45. }
  46. void
  47. CollisionSystem::remove(CollisionObject* object)
  48. {
  49. m_objects.erase(
  50. std::find(m_objects.begin(), m_objects.end(),
  51. object));
  52. // FIXME: This is a patch. A better way of fixing this is coming.
  53. for (auto* collision_object : m_objects) {
  54. collision_object->notify_object_removal(object);
  55. }
  56. for (auto* tilemap : m_sector.get_solid_tilemaps()) {
  57. tilemap->notify_object_removal(object);
  58. }
  59. }
  60. void
  61. CollisionSystem::draw(DrawingContext& context)
  62. {
  63. const Color violet(0.5f, 0.0f, 1.0f, 0.75f);
  64. const Color red(1.0f, 0.0f, 0.0f, 0.75f);
  65. const Color red_bright(1.0f, 0.5f, 0.5f, 0.75f);
  66. const Color cyan(0.0f, 1.0f, 1.0f, 0.75f);
  67. const Color orange(1.0f, 0.5f, 0.0f, 0.75f);
  68. const Color green_bright(0.7f, 1.0f, 0.7f, 0.75f);
  69. for (auto& object : m_objects) {
  70. Color color;
  71. switch (object->get_group()) {
  72. case COLGROUP_MOVING_STATIC:
  73. color = violet;
  74. break;
  75. case COLGROUP_MOVING:
  76. color = red;
  77. break;
  78. case COLGROUP_MOVING_ONLY_STATIC:
  79. color = red_bright;
  80. break;
  81. case COLGROUP_STATIC:
  82. color = cyan;
  83. break;
  84. case COLGROUP_TOUCHABLE:
  85. color = orange;
  86. break;
  87. default:
  88. color = green_bright;
  89. }
  90. const Rectf& rect = object->get_bbox();
  91. context.color().draw_filled_rect(rect, color, LAYER_FOREGROUND1 + 10);
  92. // If unisolid, draw a line on top of the rectangle.
  93. if (object->is_unisolid())
  94. context.color().draw_line(rect.p1(), Vector(rect.get_right(), rect.get_top()),
  95. Color::YELLOW, LAYER_FOREGROUND1 + 11);
  96. }
  97. }
  98. namespace {
  99. collision::Constraints check_collisions(const Vector& obj_movement, const Rectf& moving_obj_rect, const Rectf& other_obj_rect,
  100. CollisionObject* moving_object = nullptr, CollisionObject* other_object = nullptr)
  101. {
  102. collision::Constraints constraints;
  103. // Slightly growing the static object's rectangle to detect a
  104. // collision not only when they overlap, but also when they're
  105. // adjacent or at least extremely close.
  106. const Rectf grown_other_obj_rect = other_obj_rect.grown(EPSILON);
  107. if (!moving_obj_rect.overlaps(grown_other_obj_rect))
  108. return constraints;
  109. const CollisionHit dummy;
  110. if (other_object != nullptr && moving_object != nullptr && !other_object->collides(*moving_object, dummy))
  111. return constraints;
  112. if (moving_object != nullptr && other_object != nullptr && !moving_object->collides(*other_object, dummy))
  113. return constraints;
  114. // Calculate intersection.
  115. const float itop = moving_obj_rect.get_bottom() - grown_other_obj_rect.get_top();
  116. const float ibottom = grown_other_obj_rect.get_bottom() - moving_obj_rect.get_top();
  117. const float ileft = moving_obj_rect.get_right() - grown_other_obj_rect.get_left();
  118. const float iright = grown_other_obj_rect.get_right() - moving_obj_rect.get_left();
  119. bool shiftout = false;
  120. if (!other_object || !other_object->is_unisolid())
  121. {
  122. if (fabsf(obj_movement.y) > fabsf(obj_movement.x)) {
  123. if (ileft < SHIFT_DELTA) {
  124. constraints.constrain_right(grown_other_obj_rect.get_left());
  125. shiftout = true;
  126. } else if (iright < SHIFT_DELTA) {
  127. constraints.constrain_left(grown_other_obj_rect.get_right());
  128. shiftout = true;
  129. }
  130. } else {
  131. // Shiftout bottom/top.
  132. if (itop < SHIFT_DELTA) {
  133. constraints.constrain_bottom(grown_other_obj_rect.get_top());
  134. shiftout = true;
  135. } else if (ibottom < SHIFT_DELTA) {
  136. constraints.constrain_top(grown_other_obj_rect.get_bottom());
  137. shiftout = true;
  138. }
  139. }
  140. }
  141. if (!shiftout)
  142. {
  143. if (other_object && other_object->is_unisolid())
  144. {
  145. // Constrain only on fall on top of the unisolid object.
  146. if (moving_obj_rect.get_bottom() - obj_movement.y <= grown_other_obj_rect.get_top())
  147. {
  148. constraints.constrain_bottom(other_obj_rect.get_top());
  149. constraints.hit.bottom = true;
  150. }
  151. }
  152. else
  153. {
  154. const float vert_penetration = std::min(itop, ibottom);
  155. const float horiz_penetration = std::min(ileft, iright);
  156. if (vert_penetration < horiz_penetration)
  157. {
  158. if (itop < ibottom)
  159. {
  160. constraints.constrain_bottom(grown_other_obj_rect.get_top());
  161. constraints.hit.bottom = true;
  162. }
  163. else
  164. {
  165. constraints.constrain_top(grown_other_obj_rect.get_bottom());
  166. constraints.hit.top = true;
  167. }
  168. }
  169. else
  170. {
  171. if (ileft < iright)
  172. {
  173. constraints.constrain_right(grown_other_obj_rect.get_left());
  174. constraints.hit.right = true;
  175. }
  176. else
  177. {
  178. constraints.constrain_left(grown_other_obj_rect.get_right());
  179. constraints.hit.left = true;
  180. }
  181. }
  182. }
  183. if (other_object && moving_object)
  184. {
  185. CollisionHit hit = constraints.hit;
  186. moving_object->collision(*other_object, hit);
  187. std::swap(hit.left, hit.right);
  188. std::swap(hit.top, hit.bottom);
  189. const HitResponse response = other_object->collision(*moving_object, hit);
  190. if(response==ABORT_MOVE)
  191. return collision::Constraints();
  192. }
  193. }
  194. return constraints;
  195. }
  196. } // namespace
  197. void
  198. CollisionSystem::collision_tilemap(collision::Constraints* constraints,
  199. const Vector& movement, const Rectf& dest,
  200. CollisionObject& object) const
  201. {
  202. // calculate rectangle where the object will move
  203. const float x1 = dest.get_left();
  204. const float x2 = dest.get_right();
  205. const float y1 = dest.get_top();
  206. const float y2 = dest.get_bottom();
  207. for (auto* solids : m_sector.get_solid_tilemaps())
  208. {
  209. // Test with all tiles in this rectangle.
  210. const Rect test_tiles = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2));
  211. bool hits_bottom = false;
  212. for (int x = test_tiles.left; x < test_tiles.right; ++x)
  213. {
  214. for (int y = test_tiles.top; y < test_tiles.bottom; ++y)
  215. {
  216. const Tile& tile = solids->get_tile(x, y);
  217. // Skip non-solid tiles.
  218. if (tile.is_solid())
  219. {
  220. Rectf tile_bbox = solids->get_tile_bbox(x, y);
  221. bool is_relatively_solid = true;
  222. /* If the tile is a unisolid tile, the "is_solid()" function above
  223. * didn't do a thorough check. Calculate the position and (relative)
  224. * movement of the object and determine whether or not the tile is
  225. * solid with regard to those parameters. */
  226. if (tile.is_unisolid ())
  227. {
  228. Vector relative_movement = movement
  229. - solids->get_movement(/* actual = */ true);
  230. if (!tile.is_solid (tile_bbox, object.get_bbox(), relative_movement))
  231. is_relatively_solid = false;
  232. }
  233. if (is_relatively_solid)
  234. {
  235. if (tile.is_slope ()) { // Slope tile.
  236. AATriangle triangle;
  237. int slope_data = tile.get_data();
  238. if (solids->get_flip() & VERTICAL_FLIP)
  239. slope_data = AATriangle::vertical_flip(slope_data);
  240. triangle = AATriangle(tile_bbox, slope_data);
  241. bool triangle_hits_bottom = false;
  242. collision::rectangle_aatriangle(constraints, dest, triangle, triangle_hits_bottom);
  243. hits_bottom |= triangle_hits_bottom;
  244. } else { // Normal rectangular tile.
  245. collision::Constraints new_constraints = check_collisions(movement, dest, tile_bbox, nullptr, nullptr);
  246. hits_bottom |= new_constraints.hit.bottom;
  247. constraints->merge_constraints(new_constraints);
  248. }
  249. }
  250. }
  251. }
  252. }
  253. if (hits_bottom)
  254. solids->hits_object_bottom(object);
  255. }
  256. }
  257. uint32_t
  258. CollisionSystem::collision_tile_attributes(const Rectf& dest, const Vector& mov) const
  259. {
  260. const float x1 = dest.get_left();
  261. const float y1 = dest.get_top();
  262. const float x2 = dest.get_right();
  263. const float y2 = dest.get_bottom();
  264. uint32_t result = 0;
  265. for (auto& solids: m_sector.get_solid_tilemaps())
  266. {
  267. // Test with all tiles in this rectangle.
  268. const Rect test_tiles = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2));
  269. // For ice (only), add a little fudge to recognize tiles Tux is standing on.
  270. const Rect test_tiles_ice = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2 + SHIFT_DELTA));
  271. for (int x = test_tiles.left; x < test_tiles.right; ++x) {
  272. int y;
  273. for (y = test_tiles.top; y < test_tiles.bottom; ++y) {
  274. const Tile& tile = solids->get_tile(x, y);
  275. if ( tile.is_collisionful( solids->get_tile_bbox(x, y), dest, mov) ) {
  276. result |= tile.get_attributes();
  277. }
  278. }
  279. for (; y < test_tiles_ice.bottom; ++y) {
  280. const Tile& tile = solids->get_tile(x, y);
  281. if ( tile.is_collisionful( solids->get_tile_bbox(x, y), dest, mov) ) {
  282. result |= (tile.get_attributes() & Tile::ICE);
  283. }
  284. }
  285. }
  286. }
  287. return result;
  288. }
  289. /** Fills the CollisionHit and Normal vector between two intersecting rectangles. */
  290. void
  291. CollisionSystem::get_hit_normal(const CollisionObject* object1, const CollisionObject* object2,
  292. CollisionHit& hit, Vector& normal) const
  293. {
  294. const Rectf& r1 = object1->m_dest;
  295. const Rectf& r2 = object2->m_dest;
  296. const float itop = r1.get_bottom() - r2.get_top();
  297. const float ibottom = r2.get_bottom() - r1.get_top();
  298. const float ileft = r1.get_right() - r2.get_left();
  299. const float iright = r2.get_right() - r1.get_left();
  300. const float vert_penetration = std::min(itop, ibottom);
  301. const float horiz_penetration = std::min(ileft, iright);
  302. // Apply movement only on top collision with an unisolid object.
  303. if (object1->is_unisolid() &&
  304. r2.get_bottom() - object2->m_movement.y > r1.get_top())
  305. return;
  306. if (object2->is_unisolid() &&
  307. r1.get_bottom() - object1->m_movement.y > r2.get_top())
  308. return;
  309. if (vert_penetration < horiz_penetration) {
  310. if (itop < ibottom) {
  311. hit.bottom = true;
  312. normal.y = vert_penetration;
  313. } else {
  314. hit.top = true;
  315. normal.y = -vert_penetration;
  316. }
  317. } else {
  318. if (ileft < iright) {
  319. hit.right = true;
  320. normal.x = horiz_penetration;
  321. } else {
  322. hit.left = true;
  323. normal.x = -horiz_penetration;
  324. }
  325. }
  326. }
  327. void
  328. CollisionSystem::collision_object(CollisionObject* object1, CollisionObject* object2) const
  329. {
  330. using namespace collision;
  331. const Rectf& r1 = object1->m_dest;
  332. const Rectf& r2 = object2->m_dest;
  333. CollisionHit hit;
  334. if (r1.overlaps(r2)) {
  335. Vector normal(0.0f, 0.0f);
  336. get_hit_normal(object1, object2, hit, normal);
  337. if (!object1->collides(*object2, hit))
  338. return;
  339. std::swap(hit.left, hit.right);
  340. std::swap(hit.top, hit.bottom);
  341. if (!object2->collides(*object1, hit))
  342. return;
  343. std::swap(hit.left, hit.right);
  344. std::swap(hit.top, hit.bottom);
  345. HitResponse response1 = object1->collision(*object2, hit);
  346. std::swap(hit.left, hit.right);
  347. std::swap(hit.top, hit.bottom);
  348. HitResponse response2 = object2->collision(*object1, hit);
  349. if (response1 == CONTINUE && response2 == CONTINUE) {
  350. normal *= (0.5f + EPSILON);
  351. object1->m_dest.move(-normal);
  352. object2->m_dest.move(normal);
  353. } else if (response1 == CONTINUE && response2 == FORCE_MOVE) {
  354. normal *= (1 + EPSILON);
  355. object1->m_dest.move(-normal);
  356. } else if (response1 == FORCE_MOVE && response2 == CONTINUE) {
  357. normal *= (1 + EPSILON);
  358. object2->m_dest.move(normal);
  359. }
  360. }
  361. }
  362. void
  363. CollisionSystem::collision_static(collision::Constraints* constraints,
  364. const Vector& movement, const Rectf& dest,
  365. CollisionObject& object)
  366. {
  367. collision_tilemap(constraints, movement, dest, object);
  368. // Collision with other (static) objects.
  369. for (auto* static_object : m_objects)
  370. {
  371. const float static_size = static_object->get_bbox().get_width() * static_object->get_bbox().get_height();
  372. const float object_size = object.get_bbox().get_width() * object.get_bbox().get_height();
  373. // let's skip this if two colgroup_moving_static's connect and our object is somewhat larger than the static object.
  374. if ((object.get_group() == COLGROUP_MOVING_STATIC && static_object->get_group() == COLGROUP_MOVING_STATIC) &&
  375. (object_size > static_size + FORGIVENESS)) {
  376. return;
  377. }
  378. if ((
  379. static_object->get_group() == COLGROUP_STATIC ||
  380. static_object->get_group() == COLGROUP_MOVING_STATIC
  381. ) &&
  382. static_object->is_valid() &&
  383. static_object != &object)
  384. {
  385. collision::Constraints new_constraints = check_collisions(
  386. movement, dest, static_object->m_dest, &object, static_object);
  387. if (new_constraints.hit.bottom)
  388. static_object->collision_moving_object_bottom(object);
  389. else if (new_constraints.hit.top)
  390. object.collision_moving_object_bottom(*static_object);
  391. constraints->merge_constraints(new_constraints);
  392. }
  393. }
  394. }
  395. void
  396. CollisionSystem::collision_static_constrains(CollisionObject& object)
  397. {
  398. using namespace collision;
  399. const float infinity = (std::numeric_limits<float>::has_infinity ? std::numeric_limits<float>::infinity() : std::numeric_limits<float>::max());
  400. Constraints constraints;
  401. const Vector movement = object.get_movement();
  402. Vector pressure = Vector(0,0);
  403. Rectf& dest = object.m_dest;
  404. for (int i = 0; i < 2; ++i) {
  405. collision_static(&constraints, Vector(0, movement.y), dest, object);
  406. if (!constraints.has_constraints())
  407. break;
  408. // Apply calculated horizontal constraints.
  409. if (constraints.get_position_bottom() < infinity) {
  410. float height = constraints.get_height();
  411. if (height < object.get_bbox().get_height()) {
  412. // We're crushed, but ignore this for now, we'll get this again
  413. // later if we're really crushed or things will solve itself when
  414. // looking at the vertical constraints.
  415. pressure.y += object.get_bbox().get_height() - height;
  416. object.m_pressure.y = pressure.y;
  417. } else {
  418. dest.set_bottom(constraints.get_position_bottom() - EPSILON);
  419. dest.set_top(dest.get_bottom() - object.get_bbox().get_height());
  420. }
  421. } else if (constraints.get_position_top() > -infinity) {
  422. dest.set_top(constraints.get_position_top() + EPSILON);
  423. dest.set_bottom(dest.get_top() + object.get_bbox().get_height());
  424. }
  425. }
  426. if (constraints.has_constraints()) {
  427. if (constraints.hit.top || constraints.hit.bottom) {
  428. constraints.hit.left = false;
  429. constraints.hit.right = false;
  430. object.collision_solid(constraints.hit);
  431. }
  432. }
  433. constraints = Constraints();
  434. for (int i = 0; i < 2; ++i) {
  435. collision_static(&constraints, movement, dest, object);
  436. if (!constraints.has_constraints())
  437. break;
  438. // Apply calculated vertical constraints.
  439. const float width = constraints.get_width();
  440. if (width < infinity) {
  441. if (width + SHIFT_DELTA < object.get_bbox().get_width()) {
  442. // We're crushed, but ignore this for now, we'll get this again
  443. // later if we're really crushed or things will solve itself when
  444. // looking at the horizontal constraints.
  445. pressure.x += object.get_bbox().get_width() - width;
  446. object.m_pressure.x = pressure.x;
  447. } else {
  448. float xmid = constraints.get_x_midpoint ();
  449. dest.set_left(xmid - object.get_bbox().get_width()/2);
  450. dest.set_right(xmid + object.get_bbox().get_width()/2);
  451. }
  452. } else if (constraints.get_position_right() < infinity) {
  453. dest.set_right(constraints.get_position_right() - EPSILON);
  454. dest.set_left(dest.get_right() - object.get_bbox().get_width());
  455. } else if (constraints.get_position_left() > -infinity) {
  456. dest.set_left(constraints.get_position_left() + EPSILON);
  457. dest.set_right(dest.get_left() + object.get_bbox().get_width());
  458. }
  459. }
  460. if (constraints.has_constraints()) {
  461. if ( constraints.hit.left || constraints.hit.right
  462. || constraints.hit.top || constraints.hit.bottom
  463. || constraints.hit.crush )
  464. object.collision_solid(constraints.hit);
  465. }
  466. // An extra pass to make sure we're not crushed vertically.
  467. if (pressure.y > 0) {
  468. constraints = Constraints();
  469. collision_static(&constraints, movement, dest, object);
  470. if (constraints.get_position_bottom() < infinity) {
  471. const float height = constraints.get_height ();
  472. if (height + SHIFT_DELTA < object.get_bbox().get_height()) {
  473. CollisionHit h;
  474. h.top = true;
  475. h.bottom = true;
  476. h.crush = pressure.y > 16;
  477. object.collision_solid(h);
  478. }
  479. }
  480. }
  481. // An extra pass to make sure we're not crushed horizontally.
  482. if (pressure.x > 0) {
  483. constraints = Constraints();
  484. collision_static(&constraints, movement, dest, object);
  485. if (constraints.get_position_right() < infinity) {
  486. float width = constraints.get_width ();
  487. if (width + SHIFT_DELTA < object.get_bbox().get_width()) {
  488. CollisionHit h;
  489. h.top = true;
  490. h.bottom = true;
  491. h.left = true;
  492. h.right = true;
  493. h.crush = pressure.x > 16;
  494. object.collision_solid(h);
  495. }
  496. }
  497. }
  498. }
  499. void
  500. CollisionSystem::update()
  501. {
  502. if (Editor::is_active()) {
  503. return;
  504. // Objects in editor shouldn't collide.
  505. }
  506. using namespace collision;
  507. m_ground_movement_manager->apply_all_ground_movement();
  508. // Calculate destination positions of the objects.
  509. for (const auto& object : m_objects)
  510. {
  511. const Vector& mov = object->get_movement();
  512. // Make sure movement is never faster than MAX_SPEED.
  513. if (glm::length(mov) > MAX_SPEED) {
  514. object->m_movement = glm::normalize(mov) * MAX_SPEED;
  515. }
  516. object->m_dest = object->get_bbox();
  517. object->m_pressure = Vector(0, 0);
  518. object->m_dest.move(object->get_movement());
  519. object->clear_bottom_collision_list();
  520. }
  521. // Part 1: COLGROUP_MOVING vs COLGROUP_STATIC and tilemap.
  522. for (const auto& object : m_objects) {
  523. if ((object->get_group() != COLGROUP_MOVING
  524. && object->get_group() != COLGROUP_MOVING_STATIC
  525. && object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
  526. || !object->is_valid())
  527. continue;
  528. collision_static_constrains(*object);
  529. }
  530. // Part 2: COLGROUP_MOVING vs tile attributes.
  531. for (const auto& object : m_objects) {
  532. if ((object->get_group() != COLGROUP_MOVING
  533. && object->get_group() != COLGROUP_MOVING_STATIC
  534. && object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
  535. || !object->is_valid())
  536. continue;
  537. uint32_t tile_attributes = collision_tile_attributes(object->m_dest, object->get_movement());
  538. if (tile_attributes >= Tile::FIRST_INTERESTING_FLAG) {
  539. object->collision_tile(tile_attributes);
  540. }
  541. }
  542. // Part 2.5: COLGROUP_MOVING vs COLGROUP_TOUCHABLE.
  543. for (const auto& object : m_objects)
  544. {
  545. if ((object->get_group() != COLGROUP_MOVING
  546. && object->get_group() != COLGROUP_MOVING_STATIC)
  547. || !object->is_valid())
  548. continue;
  549. for (auto& object_2 : m_objects) {
  550. if (object_2->get_group() != COLGROUP_TOUCHABLE
  551. || !object_2->is_valid())
  552. continue;
  553. if (object->m_dest.overlaps(object_2->m_dest)) {
  554. Vector normal(0.0f, 0.0f);
  555. CollisionHit hit;
  556. get_hit_normal(object, object_2, hit, normal);
  557. if (!object->collides(*object_2, hit))
  558. continue;
  559. if (!object_2->collides(*object, hit))
  560. continue;
  561. object->collision(*object_2, hit);
  562. object_2->collision(*object, hit);
  563. }
  564. }
  565. }
  566. // Part 3: COLGROUP_MOVING vs COLGROUP_MOVING.
  567. for (auto i = m_objects.begin(); i != m_objects.end(); ++i)
  568. {
  569. auto object = *i;
  570. if (!object->is_valid() ||
  571. (object->get_group() != COLGROUP_MOVING &&
  572. object->get_group() != COLGROUP_MOVING_STATIC))
  573. continue;
  574. for (auto i2 = i+1; i2 != m_objects.end(); ++i2) {
  575. auto object_2 = *i2;
  576. if ((object_2->get_group() != COLGROUP_MOVING
  577. && object_2->get_group() != COLGROUP_MOVING_STATIC)
  578. || !object_2->is_valid())
  579. continue;
  580. collision_object(object, object_2);
  581. }
  582. }
  583. // Apply object movement.
  584. for (auto* object : m_objects) {
  585. object->m_bbox = object->m_dest;
  586. object->m_movement = Vector(0, 0);
  587. }
  588. }
  589. bool
  590. CollisionSystem::is_free_of_tiles(const Rectf& rect, const bool ignoreUnisolid, uint32_t tiletype) const
  591. {
  592. using namespace collision;
  593. for (const auto& solids : m_sector.get_solid_tilemaps()) {
  594. // Test with all tiles in this rectangle.
  595. const Rect test_tiles = solids->get_tiles_overlapping(rect);
  596. for (int x = test_tiles.left; x < test_tiles.right; ++x) {
  597. for (int y = test_tiles.top; y < test_tiles.bottom; ++y) {
  598. const Tile& tile = solids->get_tile(x, y);
  599. if (!(tile.get_attributes() & tiletype))
  600. continue;
  601. if (tile.is_unisolid () && ignoreUnisolid)
  602. continue;
  603. if (tile.is_slope ()) {
  604. AATriangle triangle;
  605. const Rectf tbbox = solids->get_tile_bbox(x, y);
  606. triangle = AATriangle(tbbox, tile.get_data());
  607. Constraints constraints;
  608. if (!collision::rectangle_aatriangle(&constraints, rect, triangle))
  609. continue;
  610. }
  611. // We have a solid tile that overlaps the given rectangle.
  612. return false;
  613. }
  614. }
  615. }
  616. return true;
  617. }
  618. bool
  619. CollisionSystem::is_free_of_statics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignoreUnisolid) const
  620. {
  621. using namespace collision;
  622. if (!is_free_of_tiles(rect, ignoreUnisolid)) return false;
  623. for (const auto& object : m_objects) {
  624. if (object == ignore_object) continue;
  625. if (!object->is_valid()) continue;
  626. if (object->get_group() == COLGROUP_STATIC) {
  627. if (rect.overlaps(object->get_bbox())) return false;
  628. }
  629. }
  630. return true;
  631. }
  632. bool
  633. CollisionSystem::is_free_of_movingstatics(const Rectf& rect, const CollisionObject* ignore_object) const
  634. {
  635. using namespace collision;
  636. if (!is_free_of_tiles(rect)) return false;
  637. for (const auto& object : m_objects) {
  638. if (object == ignore_object) continue;
  639. if (!object->is_valid()) continue;
  640. if ((object->get_group() == COLGROUP_MOVING)
  641. || (object->get_group() == COLGROUP_MOVING_STATIC)
  642. || (object->get_group() == COLGROUP_STATIC)) {
  643. if (rect.overlaps(object->get_bbox())) return false;
  644. }
  645. }
  646. return true;
  647. }
  648. bool
  649. CollisionSystem::is_free_of_specifically_movingstatics(const Rectf& rect, const CollisionObject* ignore_object) const
  650. {
  651. using namespace collision;
  652. for (const auto& object : m_objects) {
  653. if (object == ignore_object) continue;
  654. if (!object->is_valid()) continue;
  655. if ((object->get_group() == COLGROUP_MOVING_STATIC)
  656. && (rect.overlaps(object->get_bbox())))
  657. return false;
  658. }
  659. return true;
  660. }
  661. CollisionSystem::RaycastResult
  662. CollisionSystem::get_first_line_intersection(const Vector& line_start,
  663. const Vector& line_end,
  664. bool ignore_objects,
  665. const CollisionObject* ignore_object) const
  666. {
  667. using namespace collision;
  668. RaycastResult result{};
  669. // Check if no tile is in the way.
  670. const float lsx = std::min(line_start.x, line_end.x);
  671. const float lex = std::max(line_start.x, line_end.x);
  672. const float lsy = std::min(line_start.y, line_end.y);
  673. const float ley = std::max(line_start.y, line_end.y);
  674. for (float test_x = lsx; test_x <= lex; test_x += 16) { // NOLINT.
  675. for (float test_y = lsy; test_y <= ley; test_y += 16) { // NOLINT.
  676. for (const auto& solids : m_sector.get_solid_tilemaps()) {
  677. const auto& test_vector = Vector(test_x, test_y);
  678. if(solids->is_outside_bounds(test_vector))
  679. {
  680. continue;
  681. }
  682. const Tile* tile = &solids->get_tile_at(test_vector);
  683. // FIXME: check collision with slope tiles
  684. if ((tile->get_attributes() & Tile::SOLID))
  685. {
  686. result.is_valid = true;
  687. result.hit = tile;
  688. result.box = solids->get_tile_bbox(static_cast<int>(test_vector.x / 32.f), static_cast<int>(test_vector.y / 32.f));
  689. return result;
  690. }
  691. }
  692. }
  693. }
  694. if (ignore_objects)
  695. {
  696. result.is_valid = false;
  697. return result;
  698. }
  699. // Check if no object is in the way.
  700. for (const auto& object : m_objects) {
  701. if (object == ignore_object) continue;
  702. if (!object->is_valid()) continue;
  703. if ((object->get_group() == COLGROUP_MOVING)
  704. || (object->get_group() == COLGROUP_MOVING_STATIC)
  705. || (object->get_group() == COLGROUP_STATIC))
  706. {
  707. if (intersects_line(object->get_bbox(), line_start, line_end))
  708. {
  709. result.is_valid = true;
  710. result.hit = object;
  711. result.box = object->get_bbox();
  712. return result;
  713. }
  714. }
  715. }
  716. result.is_valid = false;
  717. return result;
  718. }
  719. bool
  720. CollisionSystem::free_line_of_sight(const Vector& line_start, const Vector& line_end, bool ignore_objects, const CollisionObject* ignore_object) const
  721. {
  722. return !get_first_line_intersection(line_start, line_end, ignore_objects, ignore_object).is_valid;
  723. }
  724. std::vector<CollisionObject*>
  725. CollisionSystem::get_nearby_objects (const Vector& center, float max_distance) const
  726. {
  727. std::vector<CollisionObject*> ret;
  728. for (const auto& object : m_objects) {
  729. float distance = object->get_bbox().distance(center);
  730. if (distance <= max_distance)
  731. ret.push_back(object);
  732. }
  733. return ret;
  734. }
  735. /* EOF */