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