parent
b539b144aa
commit
2beadab671
@ -0,0 +1,281 @@ |
||||
/*
|
||||
One-Size-Fits-All Rectangle Vs Rectangle Collisions |
||||
"Stupid scanners... making me miss at archery..." - javidx9 |
||||
|
||||
License (OLC-3) |
||||
~~~~~~~~~~~~~~~ |
||||
|
||||
Copyright 2018-2020 OneLoneCoder.com |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions |
||||
are met: |
||||
|
||||
1. Redistributions or derivations of source code must retain the above |
||||
copyright notice, this list of conditions and the following disclaimer. |
||||
|
||||
2. Redistributions or derivative works in binary form must reproduce |
||||
the above copyright notice. This list of conditions and the following |
||||
disclaimer must be reproduced in the documentation and/or other |
||||
materials provided with the distribution. |
||||
|
||||
3. Neither the name of the copyright holder nor the names of its |
||||
contributors may be used to endorse or promote products derived |
||||
from this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
|
||||
Relevant Video: https://www.youtube.com/watch?v=8JJ-4JgR7Dg
|
||||
|
||||
Links |
||||
~~~~~ |
||||
YouTube: https://www.youtube.com/javidx9
|
||||
https://www.youtube.com/javidx9extra
|
||||
Discord: https://discord.gg/WhwHUMV
|
||||
Twitter: https://www.twitter.com/javidx9
|
||||
Twitch: https://www.twitch.tv/javidx9
|
||||
GitHub: https://www.github.com/onelonecoder
|
||||
Patreon: https://www.patreon.com/javidx9
|
||||
Homepage: https://www.onelonecoder.com
|
||||
|
||||
Community Blog: https://community.onelonecoder.com
|
||||
|
||||
Author |
||||
~~~~~~ |
||||
David Barr, aka javidx9, ©OneLoneCoder 2018, 2019, 2020 |
||||
*/ |
||||
|
||||
#define OLC_PGE_APPLICATION |
||||
#include "olcPixelGameEngine.h" |
||||
#include <algorithm> |
||||
#include <functional> |
||||
#undef min |
||||
#undef max |
||||
|
||||
namespace olc |
||||
{ |
||||
namespace aabb |
||||
{ |
||||
struct rect |
||||
{ |
||||
olc::vf2d pos; |
||||
olc::vf2d size; |
||||
olc::vf2d vel; |
||||
|
||||
std::array<olc::aabb::rect*, 4> contact; |
||||
}; |
||||
|
||||
bool PointVsRect(const olc::vf2d& p, const olc::aabb::rect* r) |
||||
{ |
||||
return (p.x >= r->pos.x && p.y >= r->pos.y && p.x < r->pos.x + r->size.x && p.y < r->pos.y + r->size.y); |
||||
} |
||||
|
||||
bool RectVsRect(const olc::aabb::rect* r1, const olc::aabb::rect* r2) |
||||
{ |
||||
return (r1->pos.x < r2->pos.x + r2->size.x && r1->pos.x + r1->size.x > r2->pos.x && r1->pos.y < r2->pos.y + r2->size.y && r1->pos.y + r1->size.y > r2->pos.y); |
||||
} |
||||
|
||||
bool RayVsRect(const olc::vf2d& ray_origin, const olc::vf2d& ray_dir, const rect* target, olc::vf2d& contact_point, olc::vf2d& contact_normal, float& t_hit_near) |
||||
{ |
||||
contact_normal = { 0,0 }; |
||||
contact_point = { 0,0 }; |
||||
|
||||
// Cache division
|
||||
olc::vf2d invdir = 1.0f / ray_dir; |
||||
|
||||
// Calculate intersections with rectangle bounding axes
|
||||
olc::vf2d t_near = (target->pos - ray_origin) * invdir; |
||||
olc::vf2d t_far = (target->pos + target->size - ray_origin) * invdir; |
||||
|
||||
if (std::isnan(t_far.y) || std::isnan(t_far.x)) return false; |
||||
if (std::isnan(t_near.y) || std::isnan(t_near.x)) return false; |
||||
|
||||
// Sort distances
|
||||
if (t_near.x > t_far.x) std::swap(t_near.x, t_far.x); |
||||
if (t_near.y > t_far.y) std::swap(t_near.y, t_far.y); |
||||
|
||||
// Early rejection
|
||||
if (t_near.x > t_far.y || t_near.y > t_far.x) return false; |
||||
|
||||
// Closest 'time' will be the first contact
|
||||
t_hit_near = std::max(t_near.x, t_near.y); |
||||
|
||||
// Furthest 'time' is contact on opposite side of target
|
||||
float t_hit_far = std::min(t_far.x, t_far.y); |
||||
|
||||
// Reject if ray direction is pointing away from object
|
||||
if (t_hit_far < 0) |
||||
return false; |
||||
|
||||
// Contact point of collision from parametric line equation
|
||||
contact_point = ray_origin + t_hit_near * ray_dir; |
||||
|
||||
if (t_near.x > t_near.y) |
||||
if (invdir.x < 0) |
||||
contact_normal = { 1, 0 }; |
||||
else |
||||
contact_normal = { -1, 0 }; |
||||
else if (t_near.x < t_near.y) |
||||
if (invdir.y < 0) |
||||
contact_normal = { 0, 1 }; |
||||
else |
||||
contact_normal = { 0, -1 }; |
||||
|
||||
// Note if t_near == t_far, collision is principly in a diagonal
|
||||
// so pointless to resolve. By returning a CN={0,0} even though its
|
||||
// considered a hit, the resolver wont change anything.
|
||||
return true; |
||||
} |
||||
|
||||
bool DynamicRectVsRect(const olc::aabb::rect* r_dynamic, const float fTimeStep, const olc::aabb::rect& r_static, |
||||
olc::vf2d& contact_point, olc::vf2d& contact_normal, float& contact_time) |
||||
{ |
||||
// Check if dynamic rectangle is actually moving - we assume rectangles are NOT in collision to start
|
||||
if (r_dynamic->vel.x == 0 && r_dynamic->vel.y == 0) |
||||
return false; |
||||
|
||||
// Expand target rectangle by source dimensions
|
||||
olc::aabb::rect expanded_target; |
||||
expanded_target.pos = r_static.pos - r_dynamic->size / 2; |
||||
expanded_target.size = r_static.size + r_dynamic->size; |
||||
|
||||
if (RayVsRect(r_dynamic->pos + r_dynamic->size / 2, r_dynamic->vel * fTimeStep, &expanded_target, contact_point, contact_normal, contact_time)) |
||||
return (contact_time >= 0.0f && contact_time < 1.0f); |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
|
||||
|
||||
bool ResolveDynamicRectVsRect(olc::aabb::rect* r_dynamic, const float fTimeStep, olc::aabb::rect* r_static) |
||||
{ |
||||
olc::vf2d contact_point, contact_normal; |
||||
float contact_time = 0.0f; |
||||
if (DynamicRectVsRect(r_dynamic, fTimeStep, *r_static, contact_point, contact_normal, contact_time)) |
||||
{ |
||||
if (contact_normal.y > 0) r_dynamic->contact[0] = r_static; else nullptr; |
||||
if (contact_normal.x < 0) r_dynamic->contact[1] = r_static; else nullptr; |
||||
if (contact_normal.y < 0) r_dynamic->contact[2] = r_static; else nullptr; |
||||
if (contact_normal.x > 0) r_dynamic->contact[3] = r_static; else nullptr; |
||||
|
||||
r_dynamic->vel += contact_normal * olc::vf2d(std::abs(r_dynamic->vel.x), std::abs(r_dynamic->vel.y)) * (1 - contact_time); |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
class RectangleCollisions : public olc::PixelGameEngine |
||||
{ |
||||
public: |
||||
RectangleCollisions() |
||||
{ |
||||
sAppName = "Rectangles!"; |
||||
} |
||||
|
||||
std::vector<olc::aabb::rect> vRects; |
||||
|
||||
public: |
||||
bool OnUserCreate() override |
||||
{ |
||||
vRects.push_back({ {170.0f, 70.0f}, {10.0f, 40.0f} }); |
||||
vRects.push_back({ {150.0f, 50.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {150.0f, 150.0f}, {75.0f, 20.0f} }); |
||||
vRects.push_back({ {170.0f, 50.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {190.0f, 50.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {110.0f, 50.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {50.0f, 130.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {50.0f, 150.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {50.0f, 170.0f}, {20.0f, 20.0f} }); |
||||
vRects.push_back({ {150.0f, 100.0f}, {10.0f, 1.0f} }); |
||||
vRects.push_back({ {200.0f, 100.0f}, {20.0f, 60.0f} }); |
||||
|
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool OnUserUpdate(float fElapsedTime) override |
||||
{ |
||||
Clear(olc::DARK_BLUE); |
||||
|
||||
olc::vf2d vMouse = { float(GetMouseX()), float(GetMouseY()) }; |
||||
olc::vf2d vPoint = { 128.0f, 120.0f }; |
||||
|
||||
if (GetKey(olc::Key::W).bHeld) vRects[0].vel.y = -100.0f; |
||||
if (GetKey(olc::Key::S).bHeld) vRects[0].vel.y = +100.0f; |
||||
if (GetKey(olc::Key::A).bHeld) vRects[0].vel.x = -100.0f; |
||||
if (GetKey(olc::Key::D).bHeld) vRects[0].vel.x = +100.0f; |
||||
|
||||
if (GetMouse(0).bHeld) |
||||
vRects[0].vel += (vMouse - vRects[0].pos).norm() * 100.0f * fElapsedTime; |
||||
|
||||
|
||||
// Draw all rectangles
|
||||
for (const auto& r : vRects) |
||||
DrawRect(r.pos, r.size, olc::WHITE); |
||||
|
||||
|
||||
// Sort collisions in order of distance
|
||||
olc::vf2d cp, cn; |
||||
float t = 0, min_t = INFINITY; |
||||
std::vector<std::pair<int, float>> z; |
||||
|
||||
// Work out collision point, add it to vector along with rect ID
|
||||
for (size_t i = 1; i < vRects.size(); i++) |
||||
{ |
||||
if (olc::aabb::DynamicRectVsRect(&vRects[0], fElapsedTime, vRects[i], cp, cn, t)) |
||||
{ |
||||
z.push_back({ i, t }); |
||||
} |
||||
} |
||||
|
||||
// Do the sort
|
||||
std::sort(z.begin(), z.end(), [](const std::pair<int, float>& a, const std::pair<int, float>& b) |
||||
{ |
||||
return a.second < b.second; |
||||
}); |
||||
|
||||
// Now resolve the collision in correct order
|
||||
for (auto j : z) |
||||
olc::aabb::ResolveDynamicRectVsRect(&vRects[0], fElapsedTime, &vRects[j.first]); |
||||
|
||||
// Embellish the "in contact" rectangles in yellow
|
||||
for (int i = 0; i < 4; i++) |
||||
{ |
||||
if (vRects[0].contact[i]) |
||||
DrawRect(vRects[0].contact[i]->pos, vRects[0].contact[i]->size, olc::YELLOW); |
||||
vRects[0].contact[i] = nullptr; |
||||
} |
||||
|
||||
// UPdate the player rectangles position, with its modified velocity
|
||||
vRects[0].pos += vRects[0].vel * fElapsedTime; |
||||
|
||||
// Draw players velocity vector
|
||||
if (vRects[0].vel.mag2() > 0) |
||||
DrawLine(vRects[0].pos + vRects[0].size / 2, vRects[0].pos + vRects[0].size / 2 + vRects[0].vel.norm() * 20, olc::RED); |
||||
|
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
|
||||
int main() |
||||
{ |
||||
RectangleCollisions demo; |
||||
if (demo.Construct(256, 240, 4, 4, false, false)) |
||||
demo.Start(); |
||||
return 0; |
||||
} |
Loading…
Reference in new issue