I have been writing a simple physics engine in SFML, and have been using Verlet Integration. I've hit a rather major roadblock when it comes to writing a good collision detector. I've been using this github as a source for most of the collision code. I've had to make some tweaks due to how my code is set up, but for the most part the ideas are the same. However, every time two spheres collide, they don't bounce and roll around as expected, they just kind of stick to each other and don't act at all like actual spheres.
Here's a short video demonstrating my problem.
Here's the section of code concerned with the actual collision checks
void updateCollisions()
{
// Runs through all objects and pushes them apart if they're to close together
for (int i = 0; i < physBalls.size(); i++)
{
PhysicsBall& physball_1 = physBalls[i];
for (int k{ i + 1 }; k < physBalls.size(); k++)
{
PhysicsBall& physball_2 = physBalls[k];
sf::Vector2f collision_axis = physball_1.getPosition() - physball_2.getPosition();
const float dist2 = (collision_axis.x * collision_axis.x) + (collision_axis.y * collision_axis.y);
const float mindist = physball_1.getRadius() + physball_2.getRadius();
if (dist2 < mindist * mindist)
{
const float dist = sqrt(dist2);
const sf::Vector2f n = collision_axis / dist;
const float delta = 0.5f * (dist - mindist);
physball_1.setPosition((physball_1.getPosition() - n * delta * 0.5f));
physball_2.setPosition((physball_2.getPosition() + n * delta * 0.5f));
}
}
}
}
And here's all the code relating to the Physicsball class and the Simulation class:
#pragma once
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>
#include <iostream>
class PhysicsBall
{
private:
sf::CircleShape physball;
float radius;
sf::Vector2f position;
sf::Vector2f prevposition;
sf::Vector2f accel;
//Creates our Physics Ball
void initPhysBall(sf::Vector2f startPos, sf::Vector2f prevPos, float rad)
{
this->radius = rad;
this->position = startPos;
this->prevposition = prevPos;
this->physball.setOrigin(sf::Vector2f(radius, radius));
this->physball.setPosition(startPos);
this->physball.setRadius(this->radius);
this->physball.setFillColor(sf::Color(255, 255, 255, 255));
}
public:
//Constructors and Deconstructors
PhysicsBall(sf::Vector2f startPos, sf::Vector2f prevPos,float rad)
{
initPhysBall(startPos, prevPos, rad);
}
~PhysicsBall()
{
}
//Accessors
sf::Vector2f getPosition()
{
return this->physball.getPosition();
}
float getRadius()
{
return this->physball.getRadius();
}
sf::Vector2f getVelocity()
{
return this->position - this->prevposition;
}
void setPosition(sf::Vector2f pos)
{
this->physball.setPosition(pos);
this->position = pos;
}
//Update Functions
//Moves the ball
void updatePhysBall(float dt)
{
this->physball.setPosition(position);
const sf::Vector2f velocity = this->position - this->prevposition;
this->prevposition = this->position;
this->position = this->position + velocity + this->accel * dt * dt;
}
// Accelerators
void accelerate(sf::Vector2f acc)
{
this->accel += acc;
}
//Render Function
void render(sf::RenderTarget & target)
{
target.draw(this->physball);
}
};
class Simulation
{
private:
sf::RenderWindow* window;
sf::VideoMode videoMode;
sf::Event event;
sf::Clock deltaclock;
float dt;
sf::Vector2i mousePosWindow;
sf::Vector2f mousePosView;
sf::Vector2f mouseStartPos;
sf::Vector2f mousePrevPos;
bool mouseHeldDown;
std::vector<PhysicsBall> physBalls;
sf::Vector2f gravity = sf::Vector2f(0.f, 1000.f);
//Initialize Functions
void initVariables()
{
this->window = nullptr;
}
//Creates Window
void initWindow()
{
this->videoMode.width = 640;
this->videoMode.height = 480;
this->window = new sf::RenderWindow(this->videoMode, "Verlet Integration", sf::Style::Close);
this->window->setFramerateLimit(60);
}
public:
//Vector math functions
float Vec2fDist(sf::Vector2f vec1)
{
return std::sqrt(
(vec1.x * vec1.x) + (vec1.y * vec1.y)
);
}
//Update functions
float calculateDeltaTime()
{
return this->deltaclock.getElapsedTime().asSeconds();
}
//Gets Input
void pollevent()
{
while (this->window->pollEvent(this->event))
{
switch (this->event.type)
{
case sf::Event::Closed:
this->window->close();
break;
case sf::Event::KeyPressed:
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Escape))
{
this->window->close();
break;
}
case sf::Event::MouseButtonPressed:
this->mousePrevPos = mousePosView;
break;
case sf::Event::MouseButtonReleased:
this->mouseStartPos = mousePosView;
spawnPhysBall();
break;
}
}
}
//Creates a physics ball and adds it to a vector
void spawnPhysBall()
{
if (sf::Event::MouseButtonReleased)
{
PhysicsBall physball(mouseStartPos, ((mouseStartPos - mousePrevPos) * 0.01f) + mousePrevPos, 50.f);
physBalls.push_back(physball);
}
}
void updateMousePosistion()
{
//Updates mouse posistion as a Vector2
this->mousePosWindow = sf::Mouse::getPosition(*this->window);
this->mousePosView = this->window->mapPixelToCoords(this->mousePosWindow);
}
//Preforms all the update functions.
void update()
{
this->dt = this->calculateDeltaTime();
this->updateMousePosistion();
this->pollevent();
this->updateCollisions();
for (auto& physball : this->physBalls)
{
updatePhysPos(physball, dt);
updateAcceleration(physball, gravity);
updateApplyConstriants(physball);
}
this->deltaclock.restart();
}
//Updates Position
void updatePhysPos(PhysicsBall &physball, float delta)
{
physball.updatePhysBall(delta);
}
//Updates acceleration.
void updateAcceleration(PhysicsBall &physball, sf::Vector2f acc)
{
physball.accelerate(acc);
}
//Screen collision checks
void updateApplyConstriants(PhysicsBall &physball)
{
const float friction = 0.75f;
if (physball.getPosition().x + physball.getRadius() > videoMode.width)
{
physball.setPosition(
sf::Vector2f((videoMode.width-physball.getRadius()), physball.getPosition().y + (physball.getVelocity().y * friction))
);
}
if (physball.getPosition().y + physball.getRadius() > videoMode.height)
{
physball.setPosition(
sf::Vector2f(physball.getPosition().x + (physball.getVelocity().x * friction), (videoMode.height - physball.getRadius()))
);
}
if (physball.getPosition().x - physball.getRadius() < 0.f)
{
physball.setPosition(
sf::Vector2f(physball.getRadius(), physball.getPosition().y + (physball.getVelocity().y * friction))
);
}
if (physball.getPosition().y - physball.getRadius() < 0.f)
{
physball.setPosition(
sf::Vector2f(physball.getPosition().x + (physball.getVelocity().x * friction), physball.getRadius())
);
}
}
void updateCollisions()
{
// Runs through all objects and pushes them apart if they're to close together
for (int i = 0; i < physBalls.size(); i++)
{
PhysicsBall& physball_1 = physBalls[i];
for (int k{ i + 1 }; k < physBalls.size(); k++)
{
PhysicsBall& physball_2 = physBalls[k];
sf::Vector2f vel = physball_1.getPosition() - physball_2.getPosition();
const float dist2 = (vel.x * vel.x) + (vel.y * vel.y);
const float mindist = physball_1.getRadius() + physball_2.getRadius();
if (dist2 < mindist * mindist)
{
const float dist = sqrt(dist2);
const sf::Vector2f n = vel / dist;
const float delta = 0.5f * (dist - mindist);
physball_1.setPosition(physball_1.getPosition() - n * (delta * 0.5f));
physball_2.setPosition(physball_2.getPosition() + n * (delta * 0.5f));
}
}
}
}
//Render Functions
//Main render function
void render()
{
this->window->clear();
for (auto physball : this->physBalls)
{
physball.render(*this->window);
}
this->window->display();
}
//
//Gets whether or not the window is open
const bool running()
{
return this->window->isOpen();
}
//Constructors and deconstructors
Simulation()
{
initVariables();
initWindow();
}
~Simulation()
{
delete this->window;
}
};
Any help would be greatly appreciated!
I was trying to create a Verlet integration engine in SFML, but the spheres disappear whenever they collide.
During my getPosition() function, I was actually getting the SFML object location, rather then the updated position that frame. After I changed it around, physics are working flawlessly.