|
|
/*
|
|
|
Convex Polygon Collision Detection
|
|
|
"Don't you dare try concave ones..." - javidx9
|
|
|
|
|
|
License (OLC-3)
|
|
|
~~~~~~~~~~~~~~~
|
|
|
|
|
|
Copyright 2018-2019 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.
|
|
|
|
|
|
Instructions:
|
|
|
~~~~~~~~~~~~~
|
|
|
Use arrow keys to control pentagon
|
|
|
Use WASD to control triangle
|
|
|
F1..F4 selects algorithm
|
|
|
|
|
|
Relevant Video: https://youtu.be/7Ik2vowGcU0
|
|
|
|
|
|
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
|
|
|
|
|
|
Author
|
|
|
~~~~~~
|
|
|
David Barr, aka javidx9, <EFBFBD>OneLoneCoder 2019
|
|
|
*/
|
|
|
|
|
|
#define OLC_PGE_APPLICATION
|
|
|
#include "olcPixelGameEngine.h"
|
|
|
|
|
|
#include <vector>
|
|
|
#include <algorithm>
|
|
|
|
|
|
// Override base class with your custom functionality
|
|
|
class PolygonCollisions : public olc::PixelGameEngine
|
|
|
{
|
|
|
public:
|
|
|
PolygonCollisions()
|
|
|
{
|
|
|
sAppName = "Polygon Collisions";
|
|
|
}
|
|
|
|
|
|
struct vec2d
|
|
|
{
|
|
|
float x;
|
|
|
float y;
|
|
|
};
|
|
|
|
|
|
struct polygon
|
|
|
{
|
|
|
std::vector<vec2d> p; // Transformed Points
|
|
|
vec2d pos; // Position of shape
|
|
|
float angle; // Direction of shape
|
|
|
std::vector<vec2d> o; // "Model" of shape
|
|
|
bool overlap = false; // Flag to indicate if overlap has occurred
|
|
|
};
|
|
|
|
|
|
std::vector<polygon> vecShapes;
|
|
|
|
|
|
int nMode = 0;
|
|
|
|
|
|
public:
|
|
|
bool OnUserCreate() override
|
|
|
{
|
|
|
// Create Pentagon
|
|
|
polygon s1;
|
|
|
float fTheta = 3.14159f * 2.0f / 5.0f;
|
|
|
s1.pos = { 100, 100 };
|
|
|
s1.angle = 0.0f;
|
|
|
for (int i = 0; i < 5; i++)
|
|
|
{
|
|
|
s1.o.push_back({ 30.0f * cosf(fTheta * i), 30.0f * sinf(fTheta * i) });
|
|
|
s1.p.push_back({ 30.0f * cosf(fTheta * i), 30.0f * sinf(fTheta * i) });
|
|
|
}
|
|
|
|
|
|
// Create Triangle
|
|
|
polygon s2;
|
|
|
fTheta = 3.14159f * 2.0f / 3.0f;
|
|
|
s2.pos = { 200, 150 };
|
|
|
s2.angle = 0.0f;
|
|
|
for (int i = 0; i < 3; i++)
|
|
|
{
|
|
|
s2.o.push_back({ 20.0f * cosf(fTheta * i), 20.0f * sinf(fTheta * i) });
|
|
|
s2.p.push_back({ 20.0f * cosf(fTheta * i), 20.0f * sinf(fTheta * i) });
|
|
|
}
|
|
|
|
|
|
// Create Quad
|
|
|
polygon s3;
|
|
|
s3.pos = { 50, 200 };
|
|
|
s3.angle = 0.0f;
|
|
|
s3.o.push_back({ -30, -30 });
|
|
|
s3.o.push_back({ -30, +30 });
|
|
|
s3.o.push_back({ +30, +30 });
|
|
|
s3.o.push_back({ +30, -30 });
|
|
|
s3.p.resize(4);
|
|
|
|
|
|
|
|
|
vecShapes.push_back(s1);
|
|
|
vecShapes.push_back(s2);
|
|
|
vecShapes.push_back(s3);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool ShapeOverlap_SAT(polygon &r1, polygon &r2)
|
|
|
{
|
|
|
polygon *poly1 = &r1;
|
|
|
polygon *poly2 = &r2;
|
|
|
|
|
|
for (int shape = 0; shape < 2; shape++)
|
|
|
{
|
|
|
if (shape == 1)
|
|
|
{
|
|
|
poly1 = &r2;
|
|
|
poly2 = &r1;
|
|
|
}
|
|
|
|
|
|
for (int a = 0; a < poly1->p.size(); a++)
|
|
|
{
|
|
|
int b = (a + 1) % poly1->p.size();
|
|
|
vec2d axisProj = { -(poly1->p[b].y - poly1->p[a].y), poly1->p[b].x - poly1->p[a].x };
|
|
|
float d = sqrtf(axisProj.x * axisProj.x + axisProj.y * axisProj.y);
|
|
|
axisProj = { axisProj.x / d, axisProj.y / d };
|
|
|
|
|
|
// Work out min and max 1D points for r1
|
|
|
float min_r1 = INFINITY, max_r1 = -INFINITY;
|
|
|
for (int p = 0; p < poly1->p.size(); p++)
|
|
|
{
|
|
|
float q = (poly1->p[p].x * axisProj.x + poly1->p[p].y * axisProj.y);
|
|
|
min_r1 = std::min(min_r1, q);
|
|
|
max_r1 = std::max(max_r1, q);
|
|
|
}
|
|
|
|
|
|
// Work out min and max 1D points for r2
|
|
|
float min_r2 = INFINITY, max_r2 = -INFINITY;
|
|
|
for (int p = 0; p < poly2->p.size(); p++)
|
|
|
{
|
|
|
float q = (poly2->p[p].x * axisProj.x + poly2->p[p].y * axisProj.y);
|
|
|
min_r2 = std::min(min_r2, q);
|
|
|
max_r2 = std::max(max_r2, q);
|
|
|
}
|
|
|
|
|
|
if (!(max_r2 >= min_r1 && max_r1 >= min_r2))
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
bool ShapeOverlap_SAT_STATIC(polygon &r1, polygon &r2)
|
|
|
{
|
|
|
polygon *poly1 = &r1;
|
|
|
polygon *poly2 = &r2;
|
|
|
|
|
|
float overlap = INFINITY;
|
|
|
|
|
|
for (int shape = 0; shape < 2; shape++)
|
|
|
{
|
|
|
if (shape == 1)
|
|
|
{
|
|
|
poly1 = &r2;
|
|
|
poly2 = &r1;
|
|
|
}
|
|
|
|
|
|
for (int a = 0; a < poly1->p.size(); a++)
|
|
|
{
|
|
|
int b = (a + 1) % poly1->p.size();
|
|
|
vec2d axisProj = { -(poly1->p[b].y - poly1->p[a].y), poly1->p[b].x - poly1->p[a].x };
|
|
|
|
|
|
// Optional normalisation of projection axis enhances stability slightly
|
|
|
//float d = sqrtf(axisProj.x * axisProj.x + axisProj.y * axisProj.y);
|
|
|
//axisProj = { axisProj.x / d, axisProj.y / d };
|
|
|
|
|
|
// Work out min and max 1D points for r1
|
|
|
float min_r1 = INFINITY, max_r1 = -INFINITY;
|
|
|
for (int p = 0; p < poly1->p.size(); p++)
|
|
|
{
|
|
|
float q = (poly1->p[p].x * axisProj.x + poly1->p[p].y * axisProj.y);
|
|
|
min_r1 = std::min(min_r1, q);
|
|
|
max_r1 = std::max(max_r1, q);
|
|
|
}
|
|
|
|
|
|
// Work out min and max 1D points for r2
|
|
|
float min_r2 = INFINITY, max_r2 = -INFINITY;
|
|
|
for (int p = 0; p < poly2->p.size(); p++)
|
|
|
{
|
|
|
float q = (poly2->p[p].x * axisProj.x + poly2->p[p].y * axisProj.y);
|
|
|
min_r2 = std::min(min_r2, q);
|
|
|
max_r2 = std::max(max_r2, q);
|
|
|
}
|
|
|
|
|
|
// Calculate actual overlap along projected axis, and store the minimum
|
|
|
overlap = std::min(std::min(max_r1, max_r2) - std::max(min_r1, min_r2), overlap);
|
|
|
|
|
|
if (!(max_r2 >= min_r1 && max_r1 >= min_r2))
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// If we got here, the objects have collided, we will displace r1
|
|
|
// by overlap along the vector between the two object centers
|
|
|
vec2d d = { r2.pos.x - r1.pos.x, r2.pos.y - r1.pos.y };
|
|
|
float s = sqrtf(d.x*d.x + d.y*d.y);
|
|
|
r1.pos.x -= overlap * d.x / s;
|
|
|
r1.pos.y -= overlap * d.y / s;
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// Use edge/diagonal intersections.
|
|
|
bool ShapeOverlap_DIAGS(polygon &r1, polygon &r2)
|
|
|
{
|
|
|
polygon *poly1 = &r1;
|
|
|
polygon *poly2 = &r2;
|
|
|
|
|
|
for (int shape = 0; shape < 2; shape++)
|
|
|
{
|
|
|
if (shape == 1)
|
|
|
{
|
|
|
poly1 = &r2;
|
|
|
poly2 = &r1;
|
|
|
}
|
|
|
|
|
|
// Check diagonals of polygon...
|
|
|
for (int p = 0; p < poly1->p.size(); p++)
|
|
|
{
|
|
|
vec2d line_r1s = poly1->pos;
|
|
|
vec2d line_r1e = poly1->p[p];
|
|
|
|
|
|
// ...against edges of the other
|
|
|
for (int q = 0; q < poly2->p.size(); q++)
|
|
|
{
|
|
|
vec2d line_r2s = poly2->p[q];
|
|
|
vec2d line_r2e = poly2->p[(q + 1) % poly2->p.size()];
|
|
|
|
|
|
// Standard "off the shelf" line segment intersection
|
|
|
float h = (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r1e.y) - (line_r1s.x - line_r1e.x) * (line_r2e.y - line_r2s.y);
|
|
|
float t1 = ((line_r2s.y - line_r2e.y) * (line_r1s.x - line_r2s.x) + (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r2s.y)) / h;
|
|
|
float t2 = ((line_r1s.y - line_r1e.y) * (line_r1s.x - line_r2s.x) + (line_r1e.x - line_r1s.x) * (line_r1s.y - line_r2s.y)) / h;
|
|
|
|
|
|
if (t1 >= 0.0f && t1 < 1.0f && t2 >= 0.0f && t2 < 1.0f)
|
|
|
{
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// Use edge/diagonal intersections.
|
|
|
bool ShapeOverlap_DIAGS_STATIC(polygon &r1, polygon &r2)
|
|
|
{
|
|
|
polygon *poly1 = &r1;
|
|
|
polygon *poly2 = &r2;
|
|
|
|
|
|
for (int shape = 0; shape < 2; shape++)
|
|
|
{
|
|
|
if (shape == 1)
|
|
|
{
|
|
|
poly1 = &r2;
|
|
|
poly2 = &r1;
|
|
|
}
|
|
|
|
|
|
// Check diagonals of this polygon...
|
|
|
for (int p = 0; p < poly1->p.size(); p++)
|
|
|
{
|
|
|
vec2d line_r1s = poly1->pos;
|
|
|
vec2d line_r1e = poly1->p[p];
|
|
|
|
|
|
vec2d displacement = { 0,0 };
|
|
|
|
|
|
// ...against edges of this polygon
|
|
|
for (int q = 0; q < poly2->p.size(); q++)
|
|
|
{
|
|
|
vec2d line_r2s = poly2->p[q];
|
|
|
vec2d line_r2e = poly2->p[(q + 1) % poly2->p.size()];
|
|
|
|
|
|
// Standard "off the shelf" line segment intersection
|
|
|
float h = (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r1e.y) - (line_r1s.x - line_r1e.x) * (line_r2e.y - line_r2s.y);
|
|
|
float t1 = ((line_r2s.y - line_r2e.y) * (line_r1s.x - line_r2s.x) + (line_r2e.x - line_r2s.x) * (line_r1s.y - line_r2s.y)) / h;
|
|
|
float t2 = ((line_r1s.y - line_r1e.y) * (line_r1s.x - line_r2s.x) + (line_r1e.x - line_r1s.x) * (line_r1s.y - line_r2s.y)) / h;
|
|
|
|
|
|
if (t1 >= 0.0f && t1 < 1.0f && t2 >= 0.0f && t2 < 1.0f)
|
|
|
{
|
|
|
displacement.x += (1.0f - t1) * (line_r1e.x - line_r1s.x);
|
|
|
displacement.y += (1.0f - t1) * (line_r1e.y - line_r1s.y);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
r1.pos.x += displacement.x * (shape == 0 ? -1 : +1);
|
|
|
r1.pos.y += displacement.y * (shape == 0 ? -1 : +1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Cant overlap if static collision is resolved
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool OnUserUpdate(float fElapsedTime) override
|
|
|
{
|
|
|
if (GetKey(olc::Key::F1).bReleased) nMode = 0;
|
|
|
if (GetKey(olc::Key::F2).bReleased) nMode = 1;
|
|
|
if (GetKey(olc::Key::F3).bReleased) nMode = 2;
|
|
|
if (GetKey(olc::Key::F4).bReleased) nMode = 3;
|
|
|
|
|
|
// Shape 1
|
|
|
if (GetKey(olc::Key::LEFT).bHeld) vecShapes[0].angle -= 2.0f * fElapsedTime;
|
|
|
if (GetKey(olc::Key::RIGHT).bHeld) vecShapes[0].angle += 2.0f * fElapsedTime;
|
|
|
|
|
|
if (GetKey(olc::Key::UP).bHeld)
|
|
|
{
|
|
|
vecShapes[0].pos.x += cosf(vecShapes[0].angle) * 60.0f * fElapsedTime;
|
|
|
vecShapes[0].pos.y += sinf(vecShapes[0].angle) * 60.0f * fElapsedTime;
|
|
|
}
|
|
|
|
|
|
if (GetKey(olc::Key::DOWN).bHeld)
|
|
|
{
|
|
|
vecShapes[0].pos.x -= cosf(vecShapes[0].angle) * 60.0f * fElapsedTime;
|
|
|
vecShapes[0].pos.y -= sinf(vecShapes[0].angle) * 60.0f * fElapsedTime;
|
|
|
}
|
|
|
|
|
|
// Shape 2
|
|
|
if (GetKey(olc::Key::A).bHeld) vecShapes[1].angle -= 2.0f * fElapsedTime;
|
|
|
if (GetKey(olc::Key::D).bHeld) vecShapes[1].angle += 2.0f * fElapsedTime;
|
|
|
|
|
|
if (GetKey(olc::Key::W).bHeld)
|
|
|
{
|
|
|
vecShapes[1].pos.x += cosf(vecShapes[1].angle) * 60.0f * fElapsedTime;
|
|
|
vecShapes[1].pos.y += sinf(vecShapes[1].angle) * 60.0f * fElapsedTime;
|
|
|
}
|
|
|
|
|
|
if (GetKey(olc::Key::S).bHeld)
|
|
|
{
|
|
|
vecShapes[1].pos.x -= cosf(vecShapes[1].angle) * 60.0f * fElapsedTime;
|
|
|
vecShapes[1].pos.y -= sinf(vecShapes[1].angle) * 60.0f * fElapsedTime;
|
|
|
}
|
|
|
|
|
|
// Update Shapes and reset flags
|
|
|
for (auto &r : vecShapes)
|
|
|
{
|
|
|
for (int i = 0; i < r.o.size(); i++)
|
|
|
r.p[i] =
|
|
|
{ // 2D Rotation Transform + 2D Translation
|
|
|
(r.o[i].x * cosf(r.angle)) - (r.o[i].y * sinf(r.angle)) + r.pos.x,
|
|
|
(r.o[i].x * sinf(r.angle)) + (r.o[i].y * cosf(r.angle)) + r.pos.y,
|
|
|
};
|
|
|
|
|
|
r.overlap = false;
|
|
|
}
|
|
|
|
|
|
// Check for overlap
|
|
|
for (int m = 0; m < vecShapes.size(); m++)
|
|
|
for (int n = m + 1; n < vecShapes.size(); n++)
|
|
|
{
|
|
|
switch (nMode)
|
|
|
{
|
|
|
case 0: vecShapes[m].overlap |= ShapeOverlap_SAT(vecShapes[m], vecShapes[n]); break;
|
|
|
case 1: vecShapes[m].overlap |= ShapeOverlap_SAT_STATIC(vecShapes[m], vecShapes[n]); break;
|
|
|
case 2: vecShapes[m].overlap |= ShapeOverlap_DIAGS(vecShapes[m], vecShapes[n]); break;
|
|
|
case 3: vecShapes[m].overlap |= ShapeOverlap_DIAGS_STATIC(vecShapes[m], vecShapes[n]); break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// === Render Display ===
|
|
|
Clear(olc::BLUE);
|
|
|
|
|
|
// Draw Shapes
|
|
|
for (auto &r : vecShapes)
|
|
|
{
|
|
|
// Draw Boundary
|
|
|
for (int i = 0; i < r.p.size(); i++)
|
|
|
DrawLine(r.p[i].x, r.p[i].y, r.p[(i + 1) % r.p.size()].x, r.p[(i + 1) % r.p.size()].y, (r.overlap ? olc::RED : olc::WHITE));
|
|
|
|
|
|
// Draw Direction
|
|
|
DrawLine(r.p[0].x, r.p[0].y, r.pos.x, r.pos.y, (r.overlap ? olc::RED : olc::WHITE));
|
|
|
}
|
|
|
|
|
|
// Draw HUD
|
|
|
DrawString(8, 10, "F1: SAT", (nMode == 0 ? olc::RED : olc::YELLOW));
|
|
|
DrawString(8, 20, "F2: SAT/STATIC", (nMode == 1 ? olc::RED : olc::YELLOW));
|
|
|
DrawString(8, 30, "F3: DIAG", (nMode == 2 ? olc::RED : olc::YELLOW));
|
|
|
DrawString(8, 40, "F4: DIAG/STATIC", (nMode == 3 ? olc::RED : olc::YELLOW));
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
int main()
|
|
|
{
|
|
|
PolygonCollisions demo;
|
|
|
if (demo.Construct(256, 240, 4, 4))
|
|
|
demo.Start();
|
|
|
return 0;
|
|
|
} |