Skip to content
Snippets Groups Projects
Commit 993afea1 authored by Jonas Johan Solsvik's avatar Jonas Johan Solsvik :video_game:
Browse files

Merge branch 'develop-jonas' into 'master'

Fix collision detection in 02-jumping-boxes

See merge request !5
parents 7e8806cc a791bbc9
No related branches found
No related tags found
1 merge request!5Fix collision detection in 02-jumping-boxes
# Devlog 03.09.2020 - 15:00
## Collision resolving @ gamedev stackexchange
https://gamedev.stackexchange.com/a/71123
```
Instead of using the above method, you resolve collision like so:
Move the player along the X axis.
Check for colliding tiles.
Resolve X collision.
Move the player along the Y axis.
Check for colliding tiles.
Resolve Y collision.
```
## Narrow phase collision detection
Computationally, it is desirable that all shapes are convex in a simulation, since we have a lot of powerful distance and intersection test algorithms that work with convex shapes.
## SAT - Separating axis theorem
The separating axis theorem (SAT) states that two convex shapes are not intersecting if and only if there exists at least one axis where the orthogonal projections of the shapes on this axis do not intersect.
## And more...
- If, for any edge, all vertices of the other polygon are in front of it – then the polygons do not intersect.
- Linear algebra provides an easy formula for this test: given an edge on the first shape with vertices a and b and a vertex v on the other shape, if (v - a) · n is greater than zero, then the vertex is in front of the edge.
- The Expanding Polytope Algorithm (EPA) allows us to obtain that information, starting where GJK left off: with a simplex that contains the origin.
# Devlog 03.09.2020 - 12:00
I found a great article!
@see https://www.toptal.com/game/video-game-physics-part-ii-collision-detection-for-solid-objects
## Newtons three laws of motion
- Inertia: If no force is applied on an object, its velocity (speed and direction of motion) shall not change.
- Force, Mass, and Acceleration: The force acting on an object is equal to the mass of the object multiplied by its acceleration (rate of change of velocity). This is given by the formula F = ma.
- Action and Reaction: “For every action there is an equal and opposite reaction.” In other words, whenever one body exerts a force on another, the second body exerts a force of the same magnitude and opposite direction on the first.
## axis-aligned bounding boxes (AABB)
To test if two AABBs intersect, we only have to find out if their projections intersect on all of the coordinate axes:
# Devlog 03.09.2020 - 09:00
- I am hitting a wall with my hacky off-the-top-of-my-head collision implementation. Time to study som literarture
\ No newline at end of file
......@@ -14,6 +14,7 @@ struct Thing
ThingID id{};
sf::Vector2f velocity{0, 0};
sf::RectangleShape shape{};
bool isColliding = false;
};
struct Controller
......@@ -41,13 +42,9 @@ auto eventsUpdate(
auto physicsUpdate(
ThingVector &things,
const sf::Time deltaTime,
const ThingIDVector &dynamicThings,
const ThingIDVector &staticThings) -> void;
const ThingIDVector &dynamicThings) -> void;
auto physicsTestCollision(
Thing &thing,
Thing &otherThing,
const float dt) -> void;
auto AABBCollisionDetection(sf::RectangleShape A, sf::RectangleShape B) -> bool;
auto animationUpdate(
ThingVector &things,
......@@ -73,23 +70,21 @@ auto main() -> int
ThingVector things;
const ThingIDVector dynamicThings = {0, 1};
const ThingIDVector staticThings = {2};
{
// # Create players
const auto PLAYER_SIZE = sf::Vector2f{50.0f, 50.0f};
const auto PLAYER_ORIGIN = sf::Vector2f{PLAYER_SIZE.x / 2.0f, PLAYER_SIZE.y / 2.0f};
Thing player1{0};
player1.shape.setFillColor(sf::Color::Red);
player1.shape.setSize(PLAYER_SIZE);
player1.shape.setOrigin(PLAYER_ORIGIN);
player1.shape.setOrigin(sf::Vector2f{PLAYER_SIZE.x * .5f, PLAYER_SIZE.y * .5f});
player1.shape.setPosition(sf::Vector2f{-100, -500});
things.push_back(player1);
Thing player2{1};
player2.shape.setFillColor(sf::Color::Blue);
player2.shape.setSize(PLAYER_SIZE);
player2.shape.setOrigin(PLAYER_ORIGIN);
player2.shape.setOrigin(sf::Vector2f{PLAYER_SIZE.x * .5f, PLAYER_SIZE.y * .5f});
player2.shape.setPosition(sf::Vector2f{100, -500});
things.push_back(player2);
......@@ -115,7 +110,7 @@ auto main() -> int
frameClock.restart();
eventsUpdate(window, camera, controllers, gameClock);
physicsUpdate(things, deltaTime, dynamicThings, staticThings);
physicsUpdate(things, deltaTime, dynamicThings);
animationUpdate(things, controllers, deltaTime);
renderUpdate(window, things);
}
......@@ -223,322 +218,155 @@ auto eventsUpdate(
auto physicsUpdate(
ThingVector &things,
const sf::Time deltaTime,
const ThingIDVector &dynamicThings,
const ThingIDVector &staticThings) -> void
const ThingIDVector &dynamicThings) -> void
{
const auto dt = deltaTime.asSeconds();
//
// 1. Rigidbody physics simulation
//
for (const auto dynamicThingID : dynamicThings)
{
auto &dynamicThing = things[dynamicThingID];
// Add gravity to velocity
{
const float DRAG_COEFFISIENT = 1.0 - dt * 2;
const float GRAVITY = 10.0f * 50;
const float SPEED_OF_CAUSALITY_Y = 10.0f * 100;
const float SPEED_OF_CAUSALITY_X = 10.0f * 100;
dynamicThing.velocity.x = std::min(dynamicThing.velocity.x * DRAG_COEFFISIENT, SPEED_OF_CAUSALITY_X);
dynamicThing.velocity.y = std::min(dynamicThing.velocity.y + GRAVITY * dt * DRAG_COEFFISIENT, SPEED_OF_CAUSALITY_Y);
}
for (const auto otherDynamicThingID : dynamicThings)
//
// 2. Move along the x-axis
//
{
const auto oldPosition = dynamicThing.shape.getPosition();
dynamicThing.shape.setPosition(
oldPosition.x + dynamicThing.velocity.x * dt,
oldPosition.y);
// 2.1. Collision-detection x-axis
for (const auto &otherThing : things)
{
if (dynamicThingID == otherDynamicThingID)
if (dynamicThingID == otherThing.id)
{
continue; //... do not test collision with self.
}
physicsTestCollision(dynamicThing, things[otherDynamicThingID], dt);
if (AABBCollisionDetection(dynamicThing.shape, otherThing.shape))
{
// 2.2 Resolve collision x-axis
dynamicThing.shape.setPosition(oldPosition);
dynamicThing.velocity.x = 0;
break;
}
}
for (const auto staticThing : staticThings)
}
//
// 2. Move along the y-axis
//
{
const auto oldPosition = dynamicThing.shape.getPosition();
dynamicThing.shape.setPosition(
oldPosition.x,
oldPosition.y + dynamicThing.velocity.y * dt);
// 2.1. Collision-detection y-axis
for (const auto &otherThing : things)
{
physicsTestCollision(dynamicThing, things[staticThing], dt);
if (dynamicThingID == otherThing.id)
{
continue; //... do not test collision with self.
}
if (AABBCollisionDetection(dynamicThing.shape, otherThing.shape))
{
// 2.2 Resolve collision y-axis
dynamicThing.shape.setPosition(oldPosition);
dynamicThing.velocity.y = 0;
break;
}
}
}
}
std::cout << "----\n";
}
/**
* physicsTestCollision() - Test collision between two rectangles (things)
*
* The algorithm is explained step-by-step by inline block comments below
*
* Assumptions:
* - All collisions happen between rectangles with no rotation.
* - All rectangles are NOT overlapping initially, and this
* function makes sure they will never overlap.
* AABBCollisionDetection() - Checks if two axis-aligned-bounding-boxes (AABB) are interescting
*
* @see https://www.toptal.com/game/video-game-physics-part-ii-collision-detection-for-solid-objects
*/
auto physicsTestCollision(
Thing &collidingThing,
Thing &impactThing,
const float dt) -> void
auto AABBCollisionDetection(sf::RectangleShape A, sf::RectangleShape B) -> bool
{
const auto pos = collidingThing.shape.getPosition();
const auto dtPos = pos + collidingThing.velocity * dt;
const auto halfSize = collidingThing.shape.getSize() * 0.5f;
/**
* Get the four corners of the colliding rectangle
*
* [topLeft] -------------- [topRight] |--------------
* | | -->>> |
* | | |
* | | |
* | collider | -->>> | impacter
* | | |
* | | |
* | | -->>> |
* [botLeft] -------------- [botRight] |--------------
* Offset all positions to make sure all calculations happen
* on positive numbers. This is a rquirement for aabb-algorithm.
*
* | |----------------------------------x
* | |
* | ----- | -----
* | | B | | | B |
* | ----- | -----
* | |
* -------------|--------------x Offset ->> |
* ----- | | -----
* | A | | | | A |
* ----- | | -----
* | |
* | |
* y y
*/
const auto topLeft = sf::Vector2f{pos.x - halfSize.x, pos.y - halfSize.y};
const auto topRight = sf::Vector2f{pos.x + halfSize.x, pos.y - halfSize.y};
const auto bottomLeft = sf::Vector2f{pos.x - halfSize.x, pos.y + halfSize.y};
const auto bottomRight = sf::Vector2f{pos.x + halfSize.x, pos.y + halfSize.y};
const auto WORLD_OFFSET = sf::Vector2f{1000000.0f, 1000000.0f}; // Offset +1 million in both directions
/**
* Extrapolate where the colliding rectangle will be after dt
* Get the aMin, aMax, bMin, bMax corners.
*
* -----------|
* |-----------------------------------------------x
* |
* | [dtTopLeft] --------------- [dtTopRight]
* | | | -->>>
* collider |--- | |
* | \ | |
* | \-->> | collider | -->>>
* | | |
* |
* | [bMin]-------------------
* | | |
* -----------| | | -->>>
* [dtBotLeft] ---------------- [dtBotRight]
*/
const auto dtTopLeft = sf::Vector2f{dtPos.x - halfSize.x, dtPos.y - halfSize.y};
const auto dtTopRight = sf::Vector2f{dtPos.x + halfSize.x, dtPos.y - halfSize.y};
const auto dtBottomLeft = sf::Vector2f{dtPos.x - halfSize.x, dtPos.y + halfSize.y};
const auto dtBottomRight = sf::Vector2f{dtPos.x + halfSize.x, dtPos.y + halfSize.y};
/**
* Get the four corners of the impact rectangle
*
* -----| [impTopLeft] -------------- [impTopRight]
* | -->>> | |
* | | |
* | | |
* | -->>> | impacter |
* | | |
* | | |
* | -->>> | |
* -----| [impBotLeft] --------------- [impBotRight]
*/
const auto impPos = impactThing.shape.getPosition();
const auto impHalfSize = impactThing.shape.getSize() * 0.5f;
const auto impTopLeft = sf::Vector2f{impPos.x - impHalfSize.x, impPos.y - impHalfSize.y};
const auto impTopRight = sf::Vector2f{impPos.x + impHalfSize.x, impPos.y - impHalfSize.y};
const auto impBottomLeft = sf::Vector2f{impPos.x - impHalfSize.x, impPos.y + impHalfSize.y};
const auto impBottomRight = sf::Vector2f{impPos.x + impHalfSize.x, impPos.y + impHalfSize.y};
/**
* Now we are ready to test if any of the dt points are within the impacter
*
*
* |
* | [impTopLeft]---------------------------[impTopRight]
* | [aMin]----------------- |
* | | | | |
* | | | | |
* | | |---------|---------[bMax]
* | | |
* | | |
* | | |
* | | |
* ---[outsideCorner] ----------|-------->> [insideCorner?] |
* | |
* | |
* | |
* [impBotLeft]---------------------------- [impBotRight]
*
* If we find a point inside, we store two points in `outsideCorner` and `insideCorner`
* | -----------------[aMax]
* |
* |
* y
*/
sf::Vector2f outsideCorner{};
sf::Vector2f insideCorner{};
if (dtBottomRight.x > impTopLeft.x &&
dtBottomRight.x < impTopRight.x &&
dtBottomRight.y > impTopLeft.y &&
dtBottomRight.y < impBottomLeft.y)
{
outsideCorner = bottomRight;
insideCorner = dtBottomRight;
}
else if (dtBottomLeft.x > impTopLeft.x &&
dtBottomLeft.x < impTopRight.x &&
dtBottomLeft.y > impTopLeft.y &&
dtBottomLeft.y < impBottomLeft.y)
{
outsideCorner = bottomLeft;
insideCorner = dtBottomLeft;
}
else if (dtTopRight.x > impTopLeft.x &&
dtTopRight.x < impTopRight.x &&
dtTopRight.y > impTopLeft.y &&
dtTopRight.y < impBottomLeft.y)
{
outsideCorner = topRight;
insideCorner = dtTopRight;
}
else if (dtTopLeft.x > impTopLeft.x &&
dtTopLeft.x < impTopRight.x &&
dtTopLeft.y > impTopLeft.y &&
dtTopLeft.y < impBottomLeft.y)
{
outsideCorner = topLeft;
insideCorner = dtTopLeft;
}
else
{
return; // ...return early if no `insideCorner` found
}
const auto a_pos = A.getPosition();
const auto b_pos = B.getPosition();
const auto a_size = A.getSize();
const auto b_size = B.getSize();
const auto a_min = a_pos - (a_size * 0.5f) + WORLD_OFFSET;
const auto a_max = a_pos - (a_size * 0.5f) + WORLD_OFFSET + a_size;
const auto b_min = b_pos - (b_size * 0.5f) + WORLD_OFFSET;
const auto b_max = b_pos - (b_size * 0.5f) + WORLD_OFFSET + b_size;
/**
* Find intersection between insideCorner and all impactRectangle sides
*
* \ | /
* \|/
* ---------------[topIntersect]----------------
* | /|\ |
* \ | / | \ | /
* \|/ | \|/
* [leftIntersect]-------[insideCorner] ------[rightIntersect]
* /|\ | /|\
* / | \ | / | \
* | \|/ |
* ----------------[botIntersect]---------------|
* /|\
* / | \
*
* @see https://www.tutorialspoint.com/program-for-point-of-intersection-of-two-lines-in-cplusplus
*
* Compute all diffs between min and max, on both x-axis and y-axis.
*/
const auto intersection = [](
const sf::Vector2f A,
const sf::Vector2f B,
const sf::Vector2f C,
const sf::Vector2f D) -> sf::Vector2f {
// Line AB represented as a1x + b1y = c1
float a = B.y - A.y;
float b = A.x - B.x;
float c = a * (A.x) + b * (A.y);
// Line CD represented as a2x + b2y = c2
float a1 = D.y - C.y;
float b1 = C.x - D.x;
float c1 = a1 * (C.x) + b1 * (C.y);
// Special case with parallell lines
double det = a * b1 - a1 * b;
if (det == 0)
{
return sf::Vector2f{FLOAT_MAX, FLOAT_MAX};
}
// Intersection x,y
float x = (b1 * c - b * c1) / det;
float y = (a * c1 - a1 * c) / det;
return sf::Vector2f{x, y};
};
const auto leftIntersection = intersection(outsideCorner,
insideCorner,
impBottomLeft,
impTopLeft);
const auto bottomIntersection = intersection(outsideCorner,
insideCorner,
impBottomLeft,
impBottomRight);
const auto rightIntersection = intersection(outsideCorner,
insideCorner,
impBottomRight,
impTopRight);
const auto topIntersection = intersection(outsideCorner,
insideCorner,
impTopLeft,
impTopRight);
const auto diff_1 = a_min.x - b_max.x;
const auto diff_2 = a_min.y - b_max.y;
const auto diff_3 = b_min.x - a_max.x;
const auto diff_4 = b_min.y - a_max.y;
/**
* Calculate distance between all intersection points and the outsideCorner
*
* [outsideCorner]<-------------- distance ----------------->[topIntersect]
* |------------------------->[leftIntersect]
* |-------------------->[botIntersect]
* |-------------------------------->[rightIntersect]
*
* @returns distance as a floating point scalar value
* If all diffs are less than 0, we have a collision!
*/
const auto distance = [](const sf::Vector2f A, const sf::Vector2f B) -> float {
return std::sqrt(std::pow(B.x - A.x, 2) +
std::pow(B.y - A.y, 2) * 1.0);
};
const auto isColliding = diff_1 < 0 && diff_2 < 0 && diff_3 < 0 && diff_4 < 0;
const auto leftD = distance(leftIntersection, outsideCorner);
const auto bottomD = distance(bottomIntersection, outsideCorner);
const auto rightD = distance(rightIntersection, outsideCorner);
const auto topD = distance(topIntersection, outsideCorner);
/**
* The colliding side is the side with the shortest distance to the
* outsideCorner.
*
* If colliding from LEFT or RIGHT, set x-velocity to 0
*
* |------------------|
* | |
* | |
* ---->>> [LEFT] [RIGHT] <<<-------
* | |
* | |
* |------------------|
*
*/
if (leftD < bottomD &&
leftD < rightD &&
leftD < topD)
{
std::cout << "----COLLIDING LEFT: " << collidingThing.id << " -> " << impactThing.id << "\n";
collidingThing.velocity.x = 0;
}
else if (rightD < bottomD &&
rightD < topD)
{
std::cout << "----COLLIDING RIGHT: " << collidingThing.id << " -> " << impactThing.id << "\n";
collidingThing.velocity.x = 0;
}
/*
* else if colliding from TOP or BOTTOM, set y-velocity to 0
*
* |
* |
* *
* |-------[TOP]--------|
* | |
* | |
* | |
* | |
* | |
* |-----[BOTTOM]-------|
* *
* |
* |
*/
else if (topD < bottomD)
{
std::cout << "----COLLIDING TOP: " << collidingThing.id << " -> " << impactThing.id << "\n";
collidingThing.velocity.y = 0;
}
else // if (bottomD < topD)
{
std::cout << "----COLLIDING BOTTOM: " << collidingThing.id << " -> " << impactThing.id << "\n";
collidingThing.velocity.y = 0;
}
return isColliding;
}
const float BOUNCINESS = 400.f;
......@@ -575,17 +403,6 @@ auto animationUpdate(
controller.right = false; // Event consumed
}
}
const auto dt = deltaTime.asSeconds();
for (auto &thing : things)
{
const auto position = thing.shape.getPosition();
const auto velocity = thing.velocity;
thing.shape.setPosition(
position.x + velocity.x * dt,
position.y + velocity.y * dt);
}
}
auto renderUpdate(
......
......@@ -12,6 +12,7 @@ set(
02-jumping-boxes
)
foreach(target ${targets})
add_executable(${target} "${SRCDIR}/${target}/main.cpp")
......
......@@ -17,3 +17,4 @@ all:
make -C build;
./build/02-jumping-boxes;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment