C++ Ball Physics Tutorial 3 - Dragging and bouncing balls

In the last part we created a framework for our project and generated ten balls with random positions on the screen. In this part we will make it possible to drag those balls and give them velocity, and add some collision logic. Let's begin with finishing the balls update function, then make the dragging work, and lastly add the collision logic. Open up Ball.cpp and locate the update function, fill it with this code:

void Ball::update(float deltatime) {

	setAcceleration(-getVelocity().x * 0.9f * deltatime, -getVelocity().y * 0.9f * deltatime);
	setVelocity(getVelocity().x + (getAcceleration().x), getVelocity().y + (getAcceleration().y));

	circleShape.move(getVelocity());

	if (getPosition().x < getRadius()) {
		setPosition(getRadius(), getPosition().y);
		setVelocity(-getVelocity().x, getVelocity().y);
	}
	else if (getPosition().x > 1000 - getRadius()) {
		setPosition(1000 - getRadius(), getPosition().y);
		setVelocity(-getVelocity().x, getVelocity().y);
	}

	if (getPosition().y < getRadius()) {
		setPosition(getPosition().x, getRadius());
		setVelocity(getVelocity().x, -getVelocity().y);
	}
	else if (getPosition().y > 600 - getRadius()) {
		setPosition(getPosition().x, 600 - getRadius());
		setVelocity(getVelocity().x, -getVelocity().y);
	}
}

In the first line, setAcceleration, we are slowing down the ball so it won't keep the same velocity forever. This is done by multiplying the negative direction of the velocity in x and y with 0.9, and multiplying it with deltatime. 0.9 was chosen arbitrary, feel free to change that. When the acceleration is calculated, we add that to the current velocity in the next line. Then we simply use the velocity to move the ball with CircleShapes move function.

The following if statements makes sure that the balls are inside the window. If the position of the ball is less than the balls radius, it means that the ball is trying to escape the window, so we invert its velocity in the appropriate direction and place the ball just inside the window.

We loop through every ball and call its update function in the World.cpps update function. Notice that I also call updateBallCollision() here, we're going to fill it in soon.

void World::update(float deltatime) {
	updateBallCollision();

	for (Ball& ball : balls) {
		ball.update(deltatime);
	}
}

 

Collision.h

Let's create a new class that handles collisions to make life easier, call it Collision.

#ifndef COLLISION_H
#define COLLISION_H

#include <SFML/Graphics.hpp>
#include "Ball.h"

class Collision {
public:
	Collision();
	~Collision();

	bool ballOverlap(Ball ball, Ball ball2);
	bool ballPointOverlap(sf::Vector2f point, Ball ball);
	float distanceSquared(sf::Vector2f a, sf::Vector2f b);

};

#endif

Collision.h includes Ball.h, and has three functions. ballOverlap will return true if two balls overlap. ballPointOverlap will return true if a point is inside a ball. This function will be used to check if we click on a ball. distanceSquared will return the squared distance between two points.

 

Collision.cpp

#include "Collision.h"

Collision::Collision() {}

Collision::~Collision() {}

bool Collision::ballOverlap(Ball ball1, Ball ball2) {
	float distance = distanceSquared(ball1.getPosition(), ball2.getPosition());
	float radiusSum = ball1.getRadius() + ball2.getRadius();

	return distance <= radiusSum * radiusSum;
}

bool Collision::ballPointOverlap(sf::Vector2f point, Ball ball) {
	float distance = distanceSquared(ball.getPosition(), point);
	return distance < ball.getRadius() * ball.getRadius();
}

float Collision::distanceSquared(sf::Vector2f a, sf::Vector2f b) {
	float distX = a.x - b.x;
	float distY = a.y - b.y;
	return distX * distX + distY * distY;
}

distanceSquared takes two vectors as arguments and performs pythagorean theorem on them to get the squared distance, which is returned.

This function is used in both ballOverlap and ballPointOverlapballOverlap returns true if the distance is less or equal to the sum of the balls radius. Since distance is squared, we square the sum of the radius. ballPointOverlap use the same logic but with a single point and a ball instead of two balls.

Include and declare Collision in World.h so we can use it.

#ifndef WORLD_H
#define WORLD_H
#include "Ball.h"
#include "Collision.h" // Add this


...

private:
	std::vector<Ball> balls;
	Ball* draggedBall;
	Collision collision; // And this
};

#endif

 

Dragging a ball

All set! Back to World.cpp, locate the function dragBall and fill it with this code:

bool World::dragBall(sf::Vector2f point) {
	for (Ball& ball : balls) {
		if (collision.ballPointOverlap(sf::Vector2f(point.x, point.y), ball)) {
			draggedBall = &ball;
			return true;
		}
	}
	return false;
}

This function will be called every time we left click on the window, and takes the mouse location as an argument. It loops through every ball on the screen and calls the ballPointOverlap function from Collision. If the mouse location is inside of a ball, we point our pointer draggedBall to that ball and return true.

When we release the left mouse button we want the ball to get some velocity based on the distance of the new location of the mouse and the balls location. This is done in setDraggedVelocity:

void World::setDraggedVelocity(float x, float y) {

	if (draggedBall) {
		draggedBall->setVelocity((draggedBall->getPosition().x - x) / 100,
			(draggedBall->getPosition().y - y) / 100);

		draggedBall = nullptr;
	}
}

Arguments x and y is the mouse location. First, we check if draggedBall is pointing at a ball. If it is, we subtract the balls position in both directions with the mouse position, and divide it with 100. The number 100 was chosen arbitrary and should be changed to your preferences, it may give the ball a very small velocity or a very high velocity.

We are going to call these functions from Main.cpp, so go ahead and open that file. This is the final Main.cpp:

#include <iostream>
#include <SFML/Graphics.hpp>
#include "Ball.h"
#include "WorldRenderer.h"
#include <vector>
#include <time.h>
#include "World.h"

int main() {
	sf::RenderWindow window(sf::VideoMode(1000, 600), "Ball Physics Simulator");

	srand((unsigned)(time(NULL)));

	World world;
	WorldRenderer worldRenderer(world);

	sf::Vertex line[] = { sf::Vertex(sf::Vector2f(-1, -1)), sf::Vertex(sf::Vector2f(-1, -1)) };

	bool dragging = false;

	float deltatime = 0.f;
	sf::Clock clock;

	while (window.isOpen()) {
		sf::Event event;

		deltatime = clock.restart().asSeconds();

		while (window.pollEvent(event)) {
			switch (event.type) {
			case sf::Event::Closed:
				window.close();
				break;

			case sf::Event::MouseButtonPressed:
				if (event.mouseButton.button == sf::Mouse::Left) {

					sf::Vector2i point = sf::Mouse::getPosition(window);

					if (world.dragBall(sf::Vector2f((float)point.x, (float)point.y))) {
						dragging = true;
					}
				}
				break;

			case sf::Event::MouseButtonReleased:
				if (event.mouseButton.button == sf::Mouse::Left) {

					world.setDraggedVelocity(line[1].position.x, line[1].position.y);

					dragging = false;
				}
				break;
			}
		}

		if (dragging) {
			sf::Vector2i point = sf::Mouse::getPosition(window);

			line[0] = sf::Vertex(sf::Vector2f(world.getDraggedBall()->getPosition()), sf::Color::Blue);
			line[1] = sf::Vertex(sf::Vector2f((float)point.x, (float)point.y), sf::Color::Blue);
		}

		world.update(deltatime);

		window.clear(sf::Color::White);
		worldRenderer.render(window);

		if (dragging) {
			window.draw(line, 2, sf::Lines);
		}
		window.display();

	}

	return 0;
}

To draw a line from a ball and the mouse position, we use an array of sf::Vertex. and initialize it with two points outside of the screen:

sf::Vertex line[] = { sf::Vertex(sf::Vector2f(-1, -1)), sf::Vertex(sf::Vector2f(-1, -1)) };

line[0] will be the point of the balls position, and line[1] will be the point of the mouse position. We keep track of the dragged balls with a boolean dragging.

bool dragging = false;

Deltatime and sf::Clock makes our program run smoother.

Inside the game loop, we poll events to check if the left mouse button is pressed or released. If it is pressed, the position is stored in a vector. This is a vector of type int, so we need to convert the point values to floats. We call the function world.dragBall and use this point in the parameter. If it returns true, a ball was hit and we can set dragging to true:

			case sf::Event::MouseButtonPressed:
				if (event.mouseButton.button == sf::Mouse::Left) {

					sf::Vector2i point = sf::Mouse::getPosition(window);

					if (world.dragBall(sf::Vector2f((float)point.x, (float)point.y))) {
						dragging = true;
					}
				}
				break;

 

When the mouse is released, we all we have to do is to call world.setDraggedVelocity, as we talked about before, and set dragging to false. We feed the function  with the current position of the mouse, which is stored in line[1].position.x and line[1].position.y:

case sf::Event::MouseButtonReleased:
				if (event.mouseButton.button == sf::Mouse::Left) {

					world.setDraggedVelocity(line[1].position.x, line[1].position.y);
					dragging = false;
				}
				break;
			}

 

Outside of the polling, if dragging is true, line is updated with the dragged balls position and the mouse position. Notice that the last parameter of sf::Vertex is a color:

		if (dragging) {
			sf::Vector2i point = sf::Mouse::getPosition(window);

			line[0] = sf::Vertex(sf::Vector2f(world.getDraggedBall()->getPosition()), sf::Color::Blue);
			line[1] = sf::Vertex(sf::Vector2f((float)point.x, (float)point.y), sf::Color::Blue);
		}

 

Next, we update the world and render it:

		world.update(deltatime);

		window.clear(sf::Color::White);
		worldRenderer.render(window);

 

And if dragging is true, we want to see the line on the screen too:

		if (dragging) {
			window.draw(line, 2, sf::Lines);
		}
		window.display();

 

That's it for this part! Run the program and try it out, the balls should bounce on the ends of the window when they are given some velocity. In the next part the fun begins, where the balls will bounce when they collide.

Enjoyed this article? Give the teacher an apple.

cookie

0

Author

authors profile photo

Articles with similar tags

Comments