collision_system.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886
  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. {
  32. const float MAX_SPEED = 16.0f;
  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. }
  127. else if (iright < SHIFT_DELTA) {
  128. constraints.constrain_left(grown_other_obj_rect.get_right());
  129. shiftout = true;
  130. }
  131. }
  132. else {
  133. // Shiftout bottom/top.
  134. if (itop < SHIFT_DELTA) {
  135. constraints.constrain_bottom(grown_other_obj_rect.get_top());
  136. shiftout = true;
  137. }
  138. else if (ibottom < SHIFT_DELTA) {
  139. constraints.constrain_top(grown_other_obj_rect.get_bottom());
  140. shiftout = true;
  141. }
  142. }
  143. }
  144. if (!shiftout)
  145. {
  146. if (other_object && other_object->is_unisolid() &&
  147. moving_object->get_group() != COLGROUP_MOVING_STATIC)
  148. {
  149. // Constrain only on fall on top of the unisolid object.
  150. if (moving_obj_rect.get_bottom() - obj_movement.y <= grown_other_obj_rect.get_top() - (other_object->get_movement().y - 5.f))
  151. {
  152. constraints.constrain_bottom(other_obj_rect.get_top());
  153. constraints.hit.bottom = true;
  154. }
  155. }
  156. else
  157. {
  158. const float vert_penetration = std::min(itop, ibottom);
  159. const float horiz_penetration = std::min(ileft, iright);
  160. if (vert_penetration < horiz_penetration)
  161. {
  162. if (itop < ibottom)
  163. {
  164. constraints.constrain_bottom(grown_other_obj_rect.get_top());
  165. constraints.hit.bottom = true;
  166. }
  167. else
  168. {
  169. constraints.constrain_top(grown_other_obj_rect.get_bottom());
  170. constraints.hit.top = true;
  171. }
  172. }
  173. else
  174. {
  175. if (ileft < iright)
  176. {
  177. constraints.constrain_right(grown_other_obj_rect.get_left());
  178. constraints.hit.right = true;
  179. }
  180. else
  181. {
  182. constraints.constrain_left(grown_other_obj_rect.get_right());
  183. constraints.hit.left = true;
  184. }
  185. }
  186. }
  187. }
  188. if (other_object && moving_object)
  189. {
  190. CollisionHit hit = constraints.hit;
  191. moving_object->collision(*other_object, hit);
  192. std::swap(hit.left, hit.right);
  193. std::swap(hit.top, hit.bottom);
  194. const HitResponse& response = other_object->collision(*moving_object, hit);
  195. if (response == ABORT_MOVE)
  196. return collision::Constraints();
  197. }
  198. return constraints;
  199. }
  200. } // namespace
  201. void
  202. CollisionSystem::collision_tilemap(collision::Constraints* constraints,
  203. const Vector& movement, const Rectf& dest,
  204. CollisionObject& object) const
  205. {
  206. // calculate rectangle where the object will move
  207. const float x1 = dest.get_left();
  208. const float x2 = dest.get_right();
  209. const float y1 = dest.get_top();
  210. const float y2 = dest.get_bottom();
  211. for (auto* solids : m_sector.get_solid_tilemaps())
  212. {
  213. // Test with all tiles in this rectangle.
  214. const Rect test_tiles = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2));
  215. bool hits_bottom = false;
  216. for (int x = test_tiles.left; x < test_tiles.right; ++x)
  217. {
  218. for (int y = test_tiles.top; y < test_tiles.bottom; ++y)
  219. {
  220. const Tile& tile = solids->get_tile(x, y);
  221. // Skip non-solid tiles.
  222. if (tile.is_solid())
  223. {
  224. Rectf tile_bbox = solids->get_tile_bbox(x, y);
  225. bool is_relatively_solid = true;
  226. /* If the tile is a unisolid tile, the "is_solid()" function above
  227. * didn't do a thorough check. Calculate the position and (relative)
  228. * movement of the object and determine whether or not the tile is
  229. * solid with regard to those parameters. */
  230. if (tile.is_unisolid())
  231. {
  232. Vector relative_movement = movement
  233. - solids->get_movement(/* actual = */ true);
  234. if (!tile.is_solid(tile_bbox, object.get_bbox(), relative_movement))
  235. is_relatively_solid = false;
  236. }
  237. if (is_relatively_solid)
  238. {
  239. if (tile.is_slope()) { // Slope tile.
  240. AATriangle triangle;
  241. int slope_data = tile.get_data();
  242. if (solids->get_flip() & VERTICAL_FLIP)
  243. slope_data = AATriangle::vertical_flip(slope_data);
  244. triangle = AATriangle(tile_bbox, slope_data);
  245. bool triangle_hits_bottom = false;
  246. collision::rectangle_aatriangle(constraints, dest, triangle, triangle_hits_bottom);
  247. hits_bottom |= triangle_hits_bottom;
  248. }
  249. else { // Normal rectangular tile.
  250. collision::Constraints new_constraints = check_collisions(movement, dest, tile_bbox, nullptr, nullptr);
  251. hits_bottom |= new_constraints.hit.bottom;
  252. constraints->merge_constraints(new_constraints);
  253. }
  254. }
  255. }
  256. }
  257. }
  258. if (hits_bottom)
  259. solids->hits_object_bottom(object);
  260. }
  261. }
  262. uint32_t
  263. CollisionSystem::collision_tile_attributes(const Rectf& dest, const Vector& mov) const
  264. {
  265. const float x1 = dest.get_left();
  266. const float y1 = dest.get_top();
  267. const float x2 = dest.get_right();
  268. const float y2 = dest.get_bottom();
  269. uint32_t result = 0;
  270. for (auto& solids : m_sector.get_solid_tilemaps())
  271. {
  272. // Test with all tiles in this rectangle.
  273. const Rect test_tiles = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2));
  274. // For ice (only), add a little fudge to recognize tiles Tux is standing on.
  275. const Rect test_tiles_ice = solids->get_tiles_overlapping(Rectf(x1, y1, x2, y2 + SHIFT_DELTA));
  276. for (int x = test_tiles.left; x < test_tiles.right; ++x) {
  277. int y;
  278. for (y = test_tiles.top; y < test_tiles.bottom; ++y) {
  279. const Tile& tile = solids->get_tile(x, y);
  280. if (tile.is_collisionful(solids->get_tile_bbox(x, y), dest, mov)) {
  281. result |= tile.get_attributes();
  282. }
  283. }
  284. for (; y < test_tiles_ice.bottom; ++y) {
  285. const Tile& tile = solids->get_tile(x, y);
  286. if (tile.is_collisionful(solids->get_tile_bbox(x, y), dest, mov)) {
  287. result |= (tile.get_attributes() & Tile::ICE);
  288. }
  289. }
  290. }
  291. }
  292. return result;
  293. }
  294. /** Fills the CollisionHit and Normal vector between two intersecting rectangles. */
  295. void
  296. CollisionSystem::get_hit_normal(const CollisionObject* object1, const CollisionObject* object2,
  297. CollisionHit& hit, Vector& normal) const
  298. {
  299. const Rectf& r1 = object1->m_dest;
  300. const Rectf& r2 = object2->m_dest;
  301. const float itop = r1.get_bottom() - r2.get_top();
  302. const float ibottom = r2.get_bottom() - r1.get_top();
  303. const float ileft = r1.get_right() - r2.get_left();
  304. const float iright = r2.get_right() - r1.get_left();
  305. const float vert_penetration = std::min(itop, ibottom);
  306. const float horiz_penetration = std::min(ileft, iright);
  307. // Apply movement only on top collision with an unisolid object.
  308. if (object1->is_unisolid() &&
  309. r2.get_bottom() - object2->m_movement.y > r1.get_top())
  310. return;
  311. if (object2->is_unisolid() &&
  312. r1.get_bottom() - object1->m_movement.y > r2.get_top())
  313. return;
  314. if (vert_penetration < horiz_penetration) {
  315. if (itop < ibottom) {
  316. hit.bottom = true;
  317. normal.y = vert_penetration;
  318. }
  319. else {
  320. hit.top = true;
  321. normal.y = -vert_penetration;
  322. }
  323. }
  324. else {
  325. if (ileft < iright) {
  326. hit.right = true;
  327. normal.x = horiz_penetration;
  328. }
  329. else {
  330. hit.left = true;
  331. normal.x = -horiz_penetration;
  332. }
  333. }
  334. }
  335. void
  336. CollisionSystem::collision_object(CollisionObject* object1, CollisionObject* object2) const
  337. {
  338. using namespace collision;
  339. // If both objects are moving statics, that means
  340. // their collision callbacks have already been called.
  341. // We don't need to call them again.
  342. if (object1->get_group() == COLGROUP_MOVING_STATIC &&
  343. object2->get_group() == COLGROUP_MOVING_STATIC)
  344. return;
  345. const Rectf& r1 = object1->m_dest;
  346. const Rectf& r2 = object2->m_dest;
  347. CollisionHit hit;
  348. if (r1.overlaps(r2)) {
  349. Vector normal(0.0f, 0.0f);
  350. get_hit_normal(object1, object2, hit, normal);
  351. if (!object1->collides(*object2, hit))
  352. return;
  353. std::swap(hit.left, hit.right);
  354. std::swap(hit.top, hit.bottom);
  355. if (!object2->collides(*object1, hit))
  356. return;
  357. std::swap(hit.left, hit.right);
  358. std::swap(hit.top, hit.bottom);
  359. HitResponse response1 = object1->collision(*object2, hit);
  360. std::swap(hit.left, hit.right);
  361. std::swap(hit.top, hit.bottom);
  362. HitResponse response2 = object2->collision(*object1, hit);
  363. if (response1 == CONTINUE && response2 == CONTINUE) {
  364. normal *= (0.5f + EPSILON);
  365. object1->m_dest.move(-normal);
  366. object2->m_dest.move(normal);
  367. }
  368. else if (response1 == CONTINUE && response2 == FORCE_MOVE) {
  369. normal *= (1 + EPSILON);
  370. object1->m_dest.move(-normal);
  371. }
  372. else if (response1 == FORCE_MOVE && response2 == CONTINUE) {
  373. normal *= (1 + EPSILON);
  374. object2->m_dest.move(normal);
  375. }
  376. }
  377. }
  378. void
  379. CollisionSystem::collision_static(collision::Constraints* constraints,
  380. const Vector& movement, const Rectf& dest,
  381. CollisionObject& object)
  382. {
  383. collision_tilemap(constraints, movement, dest, object);
  384. // Collision with other (static) objects.
  385. for (auto* static_object : m_objects)
  386. {
  387. if ((
  388. static_object->get_group() == COLGROUP_STATIC ||
  389. static_object->get_group() == COLGROUP_MOVING_STATIC
  390. ) &&
  391. static_object->is_valid() &&
  392. static_object != &object)
  393. {
  394. collision::Constraints new_constraints = check_collisions(
  395. movement, dest, static_object->m_dest, &object, static_object);
  396. if (new_constraints.hit.bottom)
  397. static_object->collision_moving_object_bottom(object);
  398. else if (new_constraints.hit.top)
  399. object.collision_moving_object_bottom(*static_object);
  400. constraints->merge_constraints(new_constraints);
  401. }
  402. }
  403. }
  404. void
  405. CollisionSystem::collision_static_constrains(CollisionObject& object)
  406. {
  407. using namespace collision;
  408. const float infinity = (std::numeric_limits<float>::has_infinity ? std::numeric_limits<float>::infinity() : std::numeric_limits<float>::max());
  409. Constraints constraints;
  410. const Vector movement = object.get_movement();
  411. Vector pressure = Vector(0, 0);
  412. Rectf& dest = object.m_dest;
  413. for (int i = 0; i < 2; ++i) {
  414. collision_static(&constraints, Vector(0, movement.y), dest, object);
  415. if (!constraints.has_constraints())
  416. break;
  417. // Apply calculated horizontal constraints.
  418. if (constraints.get_position_bottom() < infinity) {
  419. float height = constraints.get_height();
  420. if (height < object.get_bbox().get_height()) {
  421. // We're crushed, but ignore this for now, we'll get this again
  422. // later if we're really crushed or things will solve itself when
  423. // looking at the vertical constraints.
  424. pressure.y += object.get_bbox().get_height() - height;
  425. object.m_pressure.y = pressure.y;
  426. }
  427. else {
  428. dest.set_bottom(constraints.get_position_bottom() - EPSILON);
  429. dest.set_top(dest.get_bottom() - object.get_bbox().get_height());
  430. }
  431. }
  432. else if (constraints.get_position_top() > -infinity) {
  433. dest.set_top(constraints.get_position_top() + EPSILON);
  434. dest.set_bottom(dest.get_top() + object.get_bbox().get_height());
  435. }
  436. }
  437. if (constraints.has_constraints()) {
  438. if (constraints.hit.top || constraints.hit.bottom) {
  439. constraints.hit.left = false;
  440. constraints.hit.right = false;
  441. object.collision_solid(constraints.hit);
  442. }
  443. }
  444. constraints = Constraints();
  445. for (int i = 0; i < 2; ++i) {
  446. collision_static(&constraints, movement, dest, object);
  447. if (!constraints.has_constraints())
  448. break;
  449. // Apply calculated vertical constraints.
  450. const float width = constraints.get_width();
  451. if (width < infinity) {
  452. if (width + SHIFT_DELTA < object.get_bbox().get_width()) {
  453. // We're crushed, but ignore this for now, we'll get this again
  454. // later if we're really crushed or things will solve itself when
  455. // looking at the horizontal constraints.
  456. pressure.x += object.get_bbox().get_width() - width;
  457. object.m_pressure.x = pressure.x;
  458. }
  459. else {
  460. float xmid = constraints.get_x_midpoint();
  461. dest.set_left(xmid - object.get_bbox().get_width() / 2);
  462. dest.set_right(xmid + object.get_bbox().get_width() / 2);
  463. }
  464. }
  465. else if (constraints.get_position_right() < infinity) {
  466. dest.set_right(constraints.get_position_right() - EPSILON);
  467. dest.set_left(dest.get_right() - object.get_bbox().get_width());
  468. }
  469. else if (constraints.get_position_left() > -infinity) {
  470. dest.set_left(constraints.get_position_left() + EPSILON);
  471. dest.set_right(dest.get_left() + object.get_bbox().get_width());
  472. }
  473. }
  474. if (constraints.has_constraints()) {
  475. if (constraints.hit.left || constraints.hit.right
  476. || constraints.hit.top || constraints.hit.bottom
  477. || constraints.hit.crush)
  478. object.collision_solid(constraints.hit);
  479. }
  480. // An extra pass to make sure we're not crushed vertically.
  481. if (pressure.y > 0) {
  482. constraints = Constraints();
  483. collision_static(&constraints, movement, dest, object);
  484. if (constraints.get_position_bottom() < infinity) {
  485. const float height = constraints.get_height();
  486. if (height + SHIFT_DELTA < object.get_bbox().get_height()) {
  487. CollisionHit h;
  488. h.top = true;
  489. h.bottom = true;
  490. h.crush = pressure.y > 16;
  491. object.collision_solid(h);
  492. }
  493. }
  494. }
  495. // An extra pass to make sure we're not crushed horizontally.
  496. if (pressure.x > 0) {
  497. constraints = Constraints();
  498. collision_static(&constraints, movement, dest, object);
  499. if (constraints.get_position_right() < infinity) {
  500. float width = constraints.get_width();
  501. if (width + SHIFT_DELTA < object.get_bbox().get_width()) {
  502. CollisionHit h;
  503. h.top = true;
  504. h.bottom = true;
  505. h.left = true;
  506. h.right = true;
  507. h.crush = pressure.x > 16;
  508. object.collision_solid(h);
  509. }
  510. }
  511. }
  512. }
  513. void
  514. CollisionSystem::update()
  515. {
  516. if (Editor::is_active()) {
  517. return;
  518. // Objects in editor shouldn't collide.
  519. }
  520. using namespace collision;
  521. m_ground_movement_manager->apply_all_ground_movement();
  522. // Calculate destination positions of the objects.
  523. for (const auto& object : m_objects)
  524. {
  525. const Vector& mov = object->get_movement();
  526. // Make sure movement is never faster than MAX_SPEED.
  527. if (glm::length(mov) > MAX_SPEED) {
  528. object->m_movement = glm::normalize(mov) * MAX_SPEED;
  529. }
  530. object->m_dest = object->get_bbox();
  531. object->m_pressure = Vector(0, 0);
  532. object->m_dest.move(object->get_movement());
  533. object->clear_bottom_collision_list();
  534. }
  535. // Part 1: COLGROUP_MOVING vs COLGROUP_STATIC and tilemap.
  536. for (const auto& object : m_objects) {
  537. if ((object->get_group() != COLGROUP_MOVING
  538. && object->get_group() != COLGROUP_MOVING_STATIC
  539. && object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
  540. || !object->is_valid())
  541. continue;
  542. collision_static_constrains(*object);
  543. }
  544. // Part 2: COLGROUP_MOVING vs tile attributes.
  545. for (const auto& object : m_objects) {
  546. if ((object->get_group() != COLGROUP_MOVING
  547. && object->get_group() != COLGROUP_MOVING_STATIC
  548. && object->get_group() != COLGROUP_MOVING_ONLY_STATIC)
  549. || !object->is_valid())
  550. continue;
  551. uint32_t tile_attributes = collision_tile_attributes(object->m_dest, object->get_movement());
  552. if (tile_attributes >= Tile::FIRST_INTERESTING_FLAG) {
  553. object->collision_tile(tile_attributes);
  554. }
  555. }
  556. // Part 2.5: COLGROUP_MOVING vs COLGROUP_TOUCHABLE.
  557. for (const auto& object : m_objects)
  558. {
  559. if ((object->get_group() != COLGROUP_MOVING
  560. && object->get_group() != COLGROUP_MOVING_STATIC)
  561. || !object->is_valid())
  562. continue;
  563. for (auto& object_2 : m_objects) {
  564. if (object_2->get_group() != COLGROUP_TOUCHABLE
  565. || !object_2->is_valid())
  566. continue;
  567. if (object->m_dest.overlaps(object_2->m_dest)) {
  568. Vector normal(0.0f, 0.0f);
  569. CollisionHit hit;
  570. get_hit_normal(object, object_2, hit, normal);
  571. if (!object->collides(*object_2, hit))
  572. continue;
  573. if (!object_2->collides(*object, hit))
  574. continue;
  575. object->collision(*object_2, hit);
  576. object_2->collision(*object, hit);
  577. }
  578. }
  579. }
  580. // Part 3: COLGROUP_MOVING vs COLGROUP_MOVING.
  581. for (auto i = m_objects.begin(); i != m_objects.end(); ++i)
  582. {
  583. auto object = *i;
  584. if (!object->is_valid() ||
  585. (object->get_group() != COLGROUP_MOVING &&
  586. object->get_group() != COLGROUP_MOVING_STATIC))
  587. continue;
  588. for (auto i2 = i + 1; i2 != m_objects.end(); ++i2) {
  589. auto object_2 = *i2;
  590. if ((object_2->get_group() != COLGROUP_MOVING
  591. && object_2->get_group() != COLGROUP_MOVING_STATIC)
  592. || !object_2->is_valid())
  593. continue;
  594. collision_object(object, object_2);
  595. }
  596. }
  597. // Apply object movement.
  598. for (auto* object : m_objects) {
  599. object->m_bbox = object->m_dest;
  600. object->m_movement = Vector(0, 0);
  601. }
  602. }
  603. bool
  604. CollisionSystem::is_free_of_tiles(const Rectf& rect, const bool ignoreUnisolid, uint32_t tiletype) const
  605. {
  606. using namespace collision;
  607. for (const auto& solids : m_sector.get_solid_tilemaps()) {
  608. // Test with all tiles in this rectangle.
  609. const Rect test_tiles = solids->get_tiles_overlapping(rect);
  610. for (int x = test_tiles.left; x < test_tiles.right; ++x) {
  611. for (int y = test_tiles.top; y < test_tiles.bottom; ++y) {
  612. const Tile& tile = solids->get_tile(x, y);
  613. if (!(tile.get_attributes() & tiletype))
  614. continue;
  615. if (tile.is_unisolid() && ignoreUnisolid)
  616. continue;
  617. if (tile.is_slope()) {
  618. AATriangle triangle;
  619. const Rectf tbbox = solids->get_tile_bbox(x, y);
  620. triangle = AATriangle(tbbox, tile.get_data());
  621. Constraints constraints;
  622. if (!collision::rectangle_aatriangle(&constraints, rect, triangle))
  623. continue;
  624. }
  625. // We have a solid tile that overlaps the given rectangle.
  626. return false;
  627. }
  628. }
  629. }
  630. return true;
  631. }
  632. bool
  633. CollisionSystem::is_free_of_statics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignoreUnisolid, uint32_t tiletype) const
  634. {
  635. using namespace collision;
  636. if (!is_free_of_tiles(rect, ignoreUnisolid, tiletype)) 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_STATIC) {
  641. if (rect.overlaps(object->get_bbox())) return false;
  642. }
  643. }
  644. return true;
  645. }
  646. bool
  647. CollisionSystem::is_free_of_movingstatics(const Rectf& rect, const CollisionObject* ignore_object, const bool ignore_unisolid) const
  648. {
  649. using namespace collision;
  650. if (!is_free_of_tiles(rect, ignore_unisolid)) return false;
  651. for (const auto& object : m_objects) {
  652. if (object == ignore_object) continue;
  653. if (!object->is_valid()) continue;
  654. if (object->is_unisolid() && ignore_unisolid) continue;
  655. if ((object->get_group() == COLGROUP_MOVING)
  656. || (object->get_group() == COLGROUP_MOVING_STATIC)
  657. || (object->get_group() == COLGROUP_STATIC)) {
  658. if (rect.overlaps(object->get_bbox())) return false;
  659. }
  660. }
  661. return true;
  662. }
  663. bool
  664. CollisionSystem::is_free_of_specifically_movingstatics(const Rectf& rect, const CollisionObject* ignore_object) const
  665. {
  666. using namespace collision;
  667. for (const auto& object : m_objects) {
  668. if (object == ignore_object) continue;
  669. if (!object->is_valid()) continue;
  670. if ((object->get_group() == COLGROUP_MOVING_STATIC)
  671. && (rect.overlaps(object->get_bbox())))
  672. return false;
  673. }
  674. return true;
  675. }
  676. CollisionSystem::RaycastResult
  677. CollisionSystem::get_first_line_intersection(const Vector& line_start,
  678. const Vector& line_end,
  679. RaycastIgnore ignore,
  680. const CollisionObject* ignore_object) const
  681. {
  682. using namespace collision;
  683. RaycastResult tileresult;
  684. if (ignore != IGNORE_TILES)
  685. {
  686. // Check if no tile is in the way.
  687. const float lsx = std::min(line_start.x, line_end.x);
  688. const float lex = std::max(line_start.x, line_end.x);
  689. const float lsy = std::min(line_start.y, line_end.y);
  690. const float ley = std::max(line_start.y, line_end.y);
  691. for (float test_x = lsx; test_x <= lex; test_x += 16) { // NOLINT.
  692. for (float test_y = lsy; test_y <= ley; test_y += 16) { // NOLINT.
  693. for (const auto& solids : m_sector.get_solid_tilemaps()) {
  694. const auto& test_vector = Vector(test_x, test_y);
  695. if (solids->is_outside_bounds(test_vector))
  696. {
  697. continue;
  698. }
  699. const Tile* tile = &solids->get_tile_at(test_vector);
  700. // FIXME: check collision with slope tiles
  701. if ((tile->get_attributes() & Tile::SOLID))
  702. {
  703. tileresult.is_valid = true;
  704. tileresult.hit = tile;
  705. tileresult.box = solids->get_tile_bbox(static_cast<int>(test_vector.x / 32.f), static_cast<int>(test_vector.y / 32.f));
  706. goto finish_tiles;
  707. }
  708. }
  709. }
  710. }
  711. }
  712. finish_tiles:
  713. if (ignore == IGNORE_OBJECTS)
  714. return tileresult;
  715. RaycastResult objresult;
  716. // Check if no object is in the way.
  717. for (const auto& object : m_objects) {
  718. if (object == ignore_object) continue;
  719. if (!object->is_valid()) continue;
  720. if ((object->get_group() == COLGROUP_MOVING)
  721. || (object->get_group() == COLGROUP_MOVING_STATIC)
  722. || (object->get_group() == COLGROUP_STATIC))
  723. {
  724. if (intersects_line(object->get_bbox(), line_start, line_end))
  725. {
  726. objresult.is_valid = true;
  727. objresult.hit = object;
  728. objresult.box = object->get_bbox();
  729. break;
  730. }
  731. }
  732. }
  733. if (ignore == IGNORE_TILES)
  734. return objresult;
  735. if (tileresult.is_valid && objresult.is_valid)
  736. {
  737. float tiledist = glm::distance(tileresult.box.get_middle(), line_start);
  738. float objdist = glm::distance(objresult.box.get_middle(), line_start);
  739. return tiledist < objdist ? tileresult : objresult;
  740. }
  741. else if (tileresult.is_valid)
  742. return tileresult;
  743. else if (objresult.is_valid)
  744. return objresult;
  745. else
  746. {
  747. return RaycastResult();
  748. }
  749. }
  750. bool
  751. CollisionSystem::free_line_of_sight(const Vector& line_start, const Vector& line_end, bool ignore_objects, const CollisionObject* ignore_object) const
  752. {
  753. auto ignore = (ignore_objects ? IGNORE_OBJECTS : IGNORE_NONE);
  754. return !get_first_line_intersection(line_start, line_end, ignore, ignore_object).is_valid;
  755. }
  756. std::vector<CollisionObject*>
  757. CollisionSystem::get_nearby_objects(const Vector& center, float max_distance) const
  758. {
  759. std::vector<CollisionObject*> ret;
  760. for (const auto& object : m_objects) {
  761. float distance = object->get_bbox().distance(center);
  762. if (distance <= max_distance)
  763. ret.push_back(object);
  764. }
  765. return ret;
  766. }
  767. /* EOF */