Getting the very basic grasps of things
Hello! This is my first tutorial. It sprouted from being inspired by TheAustech's dynamic 2d lighting tutorial here (good read).
The purpose of this tutorial is to take your knowledge of 2d Vectors and apply it to something, well, "meaningful". We will create a simple simulation, that may not be 100% correct in the physics sense, but it gives you some sort of base to start off with that I hope you find helpful
NOTE: I am almost sure some of the physics here is completely wrong, however, it gives a good introduction in my opinion.
Required
Knowledge of C++ (at least up to inheritance, or classes)
A C++ IDE/Compiler/Whatever you use of choice
SDL library (this code should be easily transferrable to any API)
SDL_GFX library (OPTIONAL, I only use it for easy circles :3)
Understanding of vectors (not std::vector, math vectors, the ones with direction and magnitude ;P)
Knowledge of Trigonometry
Getting Started
Before you continue on with the tutorial, please go ahead and create a brand new empty project, link the SDL libraries (or whatever you wish to use), and compile a simple "Hello World" just to make sure things are working.
The Coding Plan (I hope I get criticism for this!)
We will have a simulation class, that will handle our events (in this case, closing the window :P), update the balls, and render the output to our screen. It's a sort of application class that I like to do in each of my projects.
The simulation class will contain a vector (std::vector here) of a ball class, which we will create as well.
Each ball class will handle collision on its own.
Enough talk! Programming time
We will start with main.cpp
NOTE: I explain everything through comments
main.cpp
Code: Select all
//main.cpp - entry point
/*
We need math.h for rand
ctime for time()
I believe D:
*/
#include <math.h>
#include <ctime>
//Main simulation class
#include "simulation.h"
int main(int argc, char* args[])
{
srand(time(NULL)); // Make sure rand() returns truly random numbers
simulation bouncyBalls(1024, 768, 50); // Create the simulation
return bouncyBalls.loop(); // Loop the simulation, exit when the loop is done
}
Code: Select all
//Define guards
#ifndef SIMULATION_H
#define SIMULATION_H
#include "SDL.h" // Main SDL
#include "ball.h" // ball class, cannot use forward declaration here (look below)
#include "vec2.h" // 2d vector class, cannot use forward declaration here (look below)
#include <vector> // We store the balls in a vector
using namespace std; // Dont yell at me for this, I use it for cleaner code
class simulation
{
public:
simulation(unsigned short w, unsigned short h, unsigned short num); //w = width of window, h = height of window, num = number of balls
~simulation(){}; // Simple destructor
int loop(); // Main loop, will call other functions
void handleEvents(); // Handle keyboard, mouse input
void update(); // Updates the balls (calls the balls' update function
void render(); // Render the balls to the screen
private:
SDL_Surface * screen; // Main screen used for rendering
SDL_Event events; // Event queue
bool quit; // Quit flag, program exits when true
float delta; // This along with thisTime and lastTime are all used for delta timing.
float thisTime; // Holds the current time
float lastTime; // Holds the previous time
vector<ball> balls; // Vector containing all of the balls we want
vec2 gravity; // An abstract force used in the simulation :D
};
#endif
Code: Select all
#include "SDL.h"
#include "SDL_gfxPrimitives.h"
#include "simulation.h"
#include "ball.h"
simulation::simulation(unsigned short w, unsigned short h, unsigned short num)
{
// We only need video and timing
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER);
// Set the caption!
SDL_WM_SetCaption("Bouncy Ball Simulation Tutorial", NULL); // No icon here :P
// Set the screen up
screen = SDL_SetVideoMode(w, h, 32, SDL_SWSURFACE | SDL_RESIZABLE); //Yay resizing
// Make sure the program doesn't quit as soon as it starts...
quit = false;
// Initialize delta, thisTime, lastTime so we dont get funky errors
delta = 0.0f;
thisTime = 0.0f;
lastTime = 0.0f;
// Create balls!
for (unsigned short i = 0; i < num; i++)
{
ball myBall;
balls.push_back(myBall);
}
gravity.x = 0.0f;
gravity.y = 90.8f;
// Ready to go!
}
// Remember, the destructor is taken care of in the prototype
int simulation::loop()
{
while (!quit) // While quit is false
{
// Figure out the delta time
thisTime = (float)SDL_GetTicks();
delta = ( thisTime - lastTime ) / 1000.0f;
lastTime = thisTime;
handleEvents(); // Start calling each needed function, each is self explanatory
update();
render();
SDL_Flip(screen); // Display our wonderful creation!
SDL_Delay(10); // We dont want to use too much CPU power now do we?
// Gee isn't that all nice and clean :0
}
return 0; //Remember, this returns back to int main(..). So returning 0 here, is exiting the program
}
//Here we handle the very few events we cover in this tutorial
void simulation::handleEvents()
{
while (SDL_PollEvent(&events)) // While we need to take care of events
{
if (events.type == SDL_QUIT) // The user closed the window? BLASPHEMY!
{
quit = true; // Quit the program :(
}
if (events.type == SDL_VIDEORESIZE) // The user wants more space!
{
SDL_FreeSurface(screen); // Remove the current surface from memory (prevent memory leak)
screen = SDL_SetVideoMode( events.resize.w, events.resize.h, 32, SDL_SWSURFACE | SDL_RESIZABLE ); // Resize the screen
if(screen == NULL) // If your computer exploded...
{
quit = true; // Quit the program :(
}
}
}
// These are all of the events we will handle in this tutorial, but you can easily expand this to, say, dropping a ball with lmb :)
}
void simulation::update() // Here is where the "physics" happens, sorta
{
// This function goes through the list of balls, and calls their update function, passing the delta value
for (unsigned short i = 0; i < balls.size(); i++)
{
balls[i].addForce(gravity); // Add gravity to the balls acceleration
balls[i].update(delta); // Let the ball update itself
balls[i].bounceScreen(); // See if it collides with the screen, if so, handle it
for (unsigned short j = 0; j < balls.size(); j++) // THIS IS HORRIBLE FOR EFFECIENCY, feel free to slap me if you wish
{
if (j != i) //This basically checks if the current ball is colliding with any other ball.
balls[i].collideOther(&balls[j]);
}
}
}
void simulation::render() // Straightfoward :P Just render circles where the balls are
{
SDL_FillRect(screen, NULL, NULL); //Clear the screen with black
for (unsigned short i = 0; i < balls.size(); i++) // For every ball we have...
{
filledCircleRGBA(screen, (int)balls[i].position.x, (int)balls[i].position.y, balls[i].radius, 255, 255 ,255, 255);// I'm using white here, you can use whatever :P (this is unique to SDL_GFX, this command isnt in SDL alone)
}
}
Code: Select all
// Define guards
#ifndef VEC2_H
#define VEC2_H
// This is a vector class that I developed for my personal use, I think its fairly standard and self explanatory, so I won't explain it :)
class vec2
{
public:
vec2();
vec2(float tx, float ty);
~vec2();
void add(const vec2 & v, float dt = 0.0f);
void subtract(const vec2 & v);
void scale(float mul);
void normalize();
float getLength() const;
float getX() const;
float getY() const;
void setX(float v);
void setY(float v);
float x, y;
};
#endif
Code: Select all
// Implementation of the vec2 class. I believe this is fairly standard and doesn't need explaining.
// If you need help, then you dont have a good enough grasp of vectors
#include "vec2.h"
#include <math.h>
vec2::vec2()
{
x = y = 0.0f;
}
vec2::vec2(float tx, float ty)
{
x = x;
y = y;
}
vec2::~vec2()
{}
void vec2::add(const vec2 & v, float dt)
{
if (dt == 0.0f)
{
x += v.getX();
y += v.getY();
}
else
{
x += v.getX() * dt;
y += v.getY() * dt;
}
}
void vec2::subtract(const vec2 & v)
{
x -= v.getX();
y -= v.getY();
}
void vec2::scale( float mul)
{
x *= mul;
y *= mul;
}
void vec2::normalize()
{
float ln = getLength();
x /= ln;
y /= ln;
}
float vec2::getLength() const
{
return sqrt( (x)*(x) + (y)*(y) );
}
float vec2::getX() const
{
return x;
}
float vec2::getY() const
{
return y;
}
void vec2::setX(float v)
{
x = v;
}
void vec2::setY(float v)
{
y = v;
}
Code: Select all
// Define guards, yet again
#ifndef BALL_H
#define BALL_H
//We only need to use the vector class here
#include "vec2.h"
class ball
{
public:
ball();
~ball(){}; // Nothing that needs to be destroyed really
void update(const float dt); // Updates the balls position, velocity, etc
void bounceScreen(); // Checks whether the ball hit the edges of the screen, pong style :D
void collideOther(ball * other); // Checks to see if the ball is colliding with another ball, if so, handle it
void addForce(vec2 f); // Adds any abstract force to the balls "physics"
float mass; // The ball's mass
unsigned short radius; // Radius of the circle that will represent it
vec2 position; // Vectors for position, velocity, and acceleration
vec2 velocity;
vec2 acceleration;
};
#endif
Code: Select all
#include "SDL.h" // SDL Main
#include <math.h> // rand()
#include "ball.h" // ball class
#include "vec2.h" // vec2 class
#define FRICTION 0.1f // Here is the friction constant we will use, I suppose you can play with this.
ball::ball()
{
//Random positioning
position.setX((float)(rand()%SDL_GetVideoSurface()->w));
position.setY((float)(rand()%SDL_GetVideoSurface()->h));
//Random initial velocity
velocity.setX((float)(rand()%100) - 100.0f);
velocity.setY((float)(rand()%100) - 100.0f);
//No acceleration at start
acceleration.setX(0.0f);
acceleration.setY(0.0f);
mass = (float)(rand()%10+2); // 0 through 10 :)
radius = (unsigned short)mass; //Just to reflect the mass differences
}
//Destructor defined in prototype
void ball::addForce(vec2 f)
{
f.scale(1.0f/mass); // f = ma, so f/m = a. f/m could be written as f * 1/m (which is the case here)
acceleration.add(f);
}
void ball::update(const float delta) // Update velocity, position, and everything "physics" here
{
velocity.add(acceleration, delta); // Self Explanatory
acceleration.scale(0.0f);
position.x += velocity.x * delta; // Move the ball
position.y += velocity.y * delta;
}
void ball::bounceScreen()
{
vec2 fric; // Friction vector to make things easier
fric.x = FRICTION * mass;
fric.y = FRICTION * mass;
bool hitX = false; // Flags that tell the function whether the ball collided
bool hitY = false;
// These 4 functions should be self explanatory
// If you need help, look up Pong's collision
if (position.getX() < radius)
{
position.setX(radius);
velocity.setX(velocity.getX() * -1.0f);
hitX = true;
}
if (position.getY() < radius)
{
position.setY(radius);
velocity.setY(velocity.getY() * -1.0f);
hitY = true;
}
if (position.getX() > SDL_GetVideoSurface()->w - radius)
{
position.setX((float)SDL_GetVideoSurface()->w - radius);
velocity.setX(velocity.getX() * -1.0f);
hitX = true;
}
if (position.getY() > SDL_GetVideoSurface()->h - radius)
{
position.setY((float)SDL_GetVideoSurface()->h - radius);
velocity.setY(velocity.getY() * -1.0f);
hitY = true;
}
if (hitX || hitY) //If there was a collision
{
// Apply friction :D
if (velocity.x < 0.0f)
velocity.x += fric.x;
else
velocity.x -= fric.x;
if (velocity.y < 0.0f)
velocity.y += fric.y;
else
velocity.y -= fric.y;
}
}
void ball::collideOther(ball * other) // This is probably the heart of this simulation
{
vec2 dist; // The vector that holds the distance between the two balls' centers
dist.x = position.x - other->position.x;
dist.y = position.y - other->position.y;
// If the balls' radiuses squared is greater than the length of the distance vector squared, then they are colliding.
// If you take out the squared part, this is saying that if the distance between the balls is less than the balls'
// radii summed up, then they are colliding. This is easy to visualize. Look at Figure 1
if ( (dist.x * dist.x + dist.y * dist.y) < ((radius + other->radius) * (radius + other->radius)) ) //Pythagerom Theorem, minus the sqrt part because thats costly on time :)
{
dist.normalize(); // Hey! The balls are colliding
dist.x *= radius + other->radius;
dist.y *= radius + other->radius;
/*
lets use some numbers here.
This ball's, A, position is (100,100)
The ball it collides with, B, is at (110, 105)
A's radius is 10
B's radius is 6
The distance between A and B is
X: -10
Y: -5
When normalized,
X: -0.9009009...
Y: -0.450450...
So now, we multiply by the radii (16)
X: -14.4
Y: -7.2
This gives us the offsets that we need to add to the other's ball position to put this ball in the correct position
Lets add them
A's X: 110 - 14.4 = 95.6
A's Y: 105 - 7.2 = 97.8
Lets see what the distance between the two circles is now ;)
A (95.6, 97.8)
B (110, 105)
X: 95.6 - 110 = -14.4
Y: 97.8 - 105 = -7.2
-14.4^2 + -7.2^2 = 259.2
sqrt(259.2) = 16.0996...
What was the sum of the radii? 16, so this makes the circles just BARELY touch ;)
*/
position.x = other->position.x + dist.x; // The previous comment explains this
position.y = other->position.y + dist.y;
vec2 velocityB; // We need to preserve the current balls velocity!
velocityB.x = velocity.x;
velocityB.y = velocity.y;
// This calculates the new velocity vectors
velocity.x = (velocity.x * (mass - other->mass) + 2 * (other->mass * other->velocity.x)) / (mass + other->mass);
velocity.y = (velocity.y * (mass - other->mass) + 2 * (other->mass * other->velocity.y)) / (mass + other->mass);
other->velocity.x = (other->velocity.x * (other->mass - mass) + 2 * (mass * velocityB.x)) / (mass + other->mass);
other->velocity.y = (other->velocity.y * (other->mass - mass) + 2 * (mass * velocityB.y)) / (mass + other->mass);
//Add friction!
if (other->velocity.x < 0.0f)
other->velocity.x += FRICTION * other->mass;
else
other->velocity.x -= FRICTION * other->mass;
if (other->velocity.y < 0.0f)
other->velocity.y += FRICTION * other->mass;
else
other->velocity.y -= FRICTION * other->mass;
}
}
Figure 1
Final note:
Please, please, please tell me what you think, what I can improve on, what part of my physics is wrong, if I need to explain something more, etc.!
I have a feeling I need to explain the collision handling more :/
Download
This is a tutorial, where you're supposed to learn. I provided source code above, and providing a download makes it possible for you not to get anything out of this and take the easy way out.