Verified Commit a791bbc9 authored by Jonas Johan Solsvik's avatar Jonas Johan Solsvik 🎮
Browse files

MErge 03 and 02

parent f0faa5c4
This diff is collapsed.
# 03-aabb-collisions
- In this experiment, will try to implement a well-known collision-detection algorithm.
#include <cstdio>
#include <cmath>
#include <iostream>
#include <SFML/Audio.hpp>
#include <SFML/Graphics.hpp>
#define FLOAT_MAX 3.40282347e+38F
using ThingID = uint;
struct Thing
{
ThingID id{};
sf::Vector2f velocity{0, 0};
sf::RectangleShape shape{};
bool isColliding = false;
};
struct Controller
{
ThingID thingID{};
bool up{};
bool down{};
bool left{};
bool right{};
uint debounceUpMs{};
uint debounceDownMs{};
uint debounceLeftMs{};
uint debounceRightMs{};
};
using ThingVector = std::vector<Thing>;
using ThingIDVector = std::vector<ThingID>;
auto eventsUpdate(
sf::RenderWindow &window,
sf::View &camera,
std::vector<Controller> &controllers,
const sf::Clock gameClock) -> void;
auto physicsUpdate(
ThingVector &things,
const sf::Time deltaTime,
const ThingIDVector &dynamicThings) -> void;
auto AABBCollisionDetection(sf::RectangleShape A, sf::RectangleShape B) -> bool;
auto animationUpdate(
ThingVector &things,
std::vector<Controller> &controllers,
const sf::Time deltaTime) -> void;
auto renderUpdate(
sf::RenderWindow &window,
const ThingVector &things) -> void;
auto main() -> int
{
// # Create the main window and camera
const int WIDTH = 600;
const int HEIGHT = 600;
const int FRAMERATE_LIMIT = 90;
sf::RenderWindow window(sf::VideoMode(WIDTH, HEIGHT), "Double trouble");
window.setFramerateLimit(FRAMERATE_LIMIT);
sf::View camera(sf::FloatRect(0, 0, WIDTH, HEIGHT));
camera.setCenter(0, -200);
camera.setSize(WIDTH, HEIGHT);
window.setView(camera);
ThingVector things;
const ThingIDVector dynamicThings = {0, 1};
{
// # Create players
const auto PLAYER_SIZE = sf::Vector2f{50.0f, 50.0f};
Thing player1{0};
player1.shape.setFillColor(sf::Color::Red);
player1.shape.setSize(PLAYER_SIZE);
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(sf::Vector2f{PLAYER_SIZE.x * .5f, PLAYER_SIZE.y * .5f});
player2.shape.setPosition(sf::Vector2f{100, -500});
things.push_back(player2);
// # Create ground
Thing ground{2};
ground.shape.setSize(sf::Vector2f{4000, 20});
ground.shape.setOrigin(2000, 10);
ground.shape.setPosition(0, 0);
ground.shape.setFillColor(sf::Color::White);
things.push_back(ground);
}
std::vector<Controller> controllers{
Controller{thingID : 0},
Controller{thingID : 1}}; // One controller for each player
sf::Clock gameClock{}; // Never restart
sf::Clock frameClock{}; // Restart every frame
while (window.isOpen())
{
const auto deltaTime = frameClock.getElapsedTime();
frameClock.restart();
eventsUpdate(window, camera, controllers, gameClock);
physicsUpdate(things, deltaTime, dynamicThings);
animationUpdate(things, controllers, deltaTime);
renderUpdate(window, things);
}
return 0;
}
auto eventsUpdate(
sf::RenderWindow &window,
sf::View &camera,
std::vector<Controller> &controllers,
const sf::Clock gameClock) -> void
{
auto &controller1 = controllers[0];
auto &controller2 = controllers[1];
// # Process events
sf::Event event;
while (window.pollEvent(event))
{
// ## Close window on exit
if (event.type == sf::Event::Closed ||
event.type == sf::Event::KeyPressed &&
event.key.code == sf::Keyboard::Escape)
{
window.close();
}
else if (event.type == sf::Event::KeyPressed)
{
const auto elapsedMs = gameClock.getElapsedTime().asMilliseconds();
const uint KEY_DEBOUNCE = 400;
/**
* Controller 1 - W + A + S + D
*/
if (
event.key.code == sf::Keyboard::W &&
controller1.debounceUpMs < elapsedMs)
{
controller1.up = true;
controller1.debounceUpMs = elapsedMs + KEY_DEBOUNCE;
}
else if (
event.key.code == sf::Keyboard::A &&
controller1.debounceLeftMs < elapsedMs)
{
controller1.left = true;
controller1.debounceLeftMs = elapsedMs + KEY_DEBOUNCE;
}
else if (
event.key.code == sf::Keyboard::S &&
controller1.debounceDownMs < elapsedMs)
{
controller1.down = true;
controller1.debounceDownMs = elapsedMs + KEY_DEBOUNCE;
}
else if (
event.key.code == sf::Keyboard::D &&
controller1.debounceRightMs < elapsedMs)
{
controller1.right = true;
controller1.debounceRightMs = elapsedMs + KEY_DEBOUNCE;
}
/**
* Controller 2 - Up + Left + Down + Right
*/
else if (
event.key.code == sf::Keyboard::Up &&
controller2.debounceUpMs < elapsedMs)
{
controller2.up = true;
controller2.debounceUpMs = elapsedMs + KEY_DEBOUNCE;
}
else if (
event.key.code == sf::Keyboard::Left &&
controller2.debounceLeftMs < elapsedMs)
{
controller2.left = true;
controller2.debounceLeftMs = elapsedMs + KEY_DEBOUNCE;
}
else if (
event.key.code == sf::Keyboard::Down &&
controller2.debounceDownMs < elapsedMs)
{
controller2.down = true;
controller2.debounceDownMs = elapsedMs + KEY_DEBOUNCE;
}
else if (
event.key.code == sf::Keyboard::Right &&
controller2.debounceRightMs < elapsedMs)
{
controller2.right = true;
controller2.debounceRightMs = elapsedMs + KEY_DEBOUNCE;
}
}
else if (event.type == sf::Event::Resized)
{
sf::FloatRect visibleArea(0, 0, event.size.width, event.size.height);
camera.setSize(event.size.width, event.size.height);
window.setView(camera);
}
}
}
auto physicsUpdate(
ThingVector &things,
const sf::Time deltaTime,
const ThingIDVector &dynamicThings) -> void
{
const auto dt = deltaTime.asSeconds();
std::cout << "----------------\n";
//
// 1. Rigidbody physics simulation
//
for (const auto dynamicThingID : dynamicThings)
{
auto &dynamicThing = things[dynamicThingID];
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);
//
// 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 == otherThing.id)
{
continue; //... do not test collision with self.
}
if (AABBCollisionDetection(dynamicThing.shape, otherThing.shape))
{
// 2.2 Resolve collision x-axis
dynamicThing.shape.setPosition(oldPosition);
dynamicThing.velocity.x = 0;
break;
}
}
}
//
// 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)
{
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";
}
/**
* 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 AABBCollisionDetection(sf::RectangleShape A, sf::RectangleShape B) -> bool
{
/**
* 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 WORLD_OFFSET = sf::Vector2f{1000000.0f, 1000000.0f}; // Offset +1 million in both directions
/**
* Get the aMin, aMax, bMin, bMax corners.
*
* |-----------------------------------------------x
* |
* |
* | [bMin]-------------------
* | | |
* | | |
* | | |
* | | |
* | | |
* | [aMin]----------------- |
* | | | | |
* | | | | |
* | | |---------|---------[bMax]
* | | |
* | | |
* | | |
* | | |
* | -----------------[aMax]
* |
* |
* y
*/
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;
/**
* Compute all diffs between min and max, on both x-axis and y-axis.
*/
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;
/**
* If all diffs are less than 0, we have a collision!
*/
const auto isColliding = diff_1 < 0 && diff_2 < 0 && diff_3 < 0 && diff_4 < 0;
return isColliding;
}
const float BOUNCINESS = 400.f;
const sf::Vector2f JUMP_UP{0, -BOUNCINESS};
const sf::Vector2f JUMP_DOWN{0, BOUNCINESS};
const sf::Vector2f JUMP_LEFT{-BOUNCINESS, 0};
const sf::Vector2f JUMP_RIGHT{BOUNCINESS, 0};
auto animationUpdate(
ThingVector &things,
std::vector<Controller> &controllers,
const sf::Time deltaTime) -> void
{
for (auto &controller : controllers)
{
if (controller.up)
{
things[controller.thingID].velocity += JUMP_UP;
controller.up = false; // Event consumed
}
if (controller.left)
{
things[controller.thingID].velocity += JUMP_LEFT;
controller.left = false; // Event consumed
}
if (controller.down)
{
things[controller.thingID].velocity += JUMP_DOWN;
controller.down = false; // Event consumed
}
if (controller.right)
{
things[controller.thingID].velocity += JUMP_RIGHT;
controller.right = false; // Event consumed
}
}
}
auto renderUpdate(
sf::RenderWindow &window,
const ThingVector &things) -> void
{
window.clear();
for (auto &thing : things)
{
window.draw(thing.shape);
}
window.display();
}
......@@ -10,7 +10,6 @@ set(
targets
01-red-circle
02-jumping-boxes
03-aabb-collision
)
......
.PHONY: 01 02 03 all
.PHONY: 01 02 all
all:
mkdir -p build;
......@@ -17,9 +17,4 @@ all:
make -C build;
./build/02-jumping-boxes;
03:
mkdir -p build;
cmake --target 03-aabb-collision -S . -B build;
make -C build;
./build/03-aabb-collision;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment