Search code examples
sfmlverlet-integration

Verlet Integration Collision Solver Not working C++ SFML


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.


Solution

  • 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.