Preface
This tute should be fast-paced. It's really not that taxing, but there's a little bit to get through - so please pay close attention to the details! If you do, you'll be well on your way to producing things like this yourself (!!!):
Cloth:
attachment:cloth.jpg
Tornado:
attachment:tornado.jpg
[wiki:Multimedia_Computing_and_Processing/Tutorials/Notes Tutorial notes] have been added to address issues raised during past tutes. Please read them regularly, as this will be updated often.
Numbers are intentionally left out of this tutorial exercise to make you experiment during the development of your applications. This is a very important skill: to be able to logically figure out what numbers are suitable, which should (hopefully!) transform into powerful intuition. Of course, please ask your friend tutors for help if you run into problems/questions.
Simple Animation
Use the sample code from the [wiki:Multimedia_Computing_and_Processing/Tutorials/OpenGL_Basics homework] to prepare a window in which you display a cube and a sphere next to one another near the origin.BR You may use [http://openglut.sourceforge.net/group__geometry.html glutSolidSphere and glutSolidCube] to draw the shapes, and [http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/gl/translate.html glTranslate] to position them.BR Don't forget to [http://msdn2.microsoft.com/en-us/library/ms537150.aspx glPushMatrix and glPopMatrix] around the translation and drawing of individual shapes!BR
For example:
// Assume starting at origin glPushMatrix(); glTranslate<>; // Don't forget to add 'f' or 'd' // We're where we want to be glutSolidSphere(); glPopMatrix(); // Back at the origin again, ready to push/translate/draw next shape/pop once more.
Also remember to position your perspective camera with [http://www.opengl.org/documentation/specs/man_pages/hardcopy/GL/html/glu/lookat.html gluLookAt] above and behind the origin looking directly at it.BR
Make your shapes move!
Instead of having static numbers in glTranslate, you're going to stick a variable in there and make the shapes move along a line, and repeat.BR
For example:
float fX; // Code to change fX, slowly shifting it between 0.0f and 10.0f glTranslatef(fX, 0.0f, 0.0f);
Use the [http://msdn2.microsoft.com/en-us/library/ms724408.aspx GetTickCount] Windows function, or [http://man.he.net/man2/gettimeofday gettimeofday] for Linux, to get the current system time in either milliseconds or microseconds respectively.BR You can create a step counter that is the system time divided by a certain factor to slow it down sufficiently.BR
DWORD dwTime; // DWORD is an unsigned int. Set 'dwTime' to the current time. float fX = (float)dwTime / 100.0f; // Takes 1/10th of a second to add 1 to fX
unsigned long dwTime; // Set 'dwTime' to the current time. float fX = (float)dwTime / 100.0f; // Takes 1/10th of a second to add 1 to fX
You can then eg: control one variable in glTranslate (say the X value) by putting this value in there. It will make your object move down the X axis.BR Of course, you must note the time when you start the program and subtract it from the time you get when you retreive the system time. This will make sure your step counter starts from 0!
// GLOBAL variable DWORD dwStartTime; // Program initialisation dwStartTime = GetTickCount(); // Main loop DWORD dwCurrentTime = GetTickCount() - dwStartTime; // Use 'dwCurrentTime' which will start from (around) 0 DWORD dwLoopedTime = dwCurrentTime % 1000; // Keep it in a loop so objects don't fly off to infinity float fX = (float)dwLoopedTime / 100.0f;
// *NIX version
// GLOBAL variable
struct timeval dwStartTime;
unsigned long get_time()
{
struct timeval tt;
gettimeofday(&tt, NULL)
return ( (tt.tv_sec - dwStartTime.tv_sec) * 1000000 + (tt.tv_usec + dwStartTime.tv_usec) );
}
// Program initialisation
gettimeofday(&dwStartTime, NULL);
// Main loop
DWORD dwCurrentTime = get_time();
// Use 'dwCurrentTime' which will start from (around) 0
DWORD dwLoopedTime = dwCurrentTime % 1000; // Keep it in a loop so objects don't fly off to infinity
float fX = (float)dwLoopedTime / 100.0f;
Simple Collision
You're going to build up a planetary simulator using [http://csep10.phys.utk.edu/astr161/lect/history/newtongrav.html Newton's Universal Law of Gravitation]:
latex($$ G * ((m_1 * m_2) / r^2) $$) where latex($$ G = 6.6725985 * 10^{-11} $$), m are the masses of the two bodies in kilograms and r is the distance between them in meters.
Create a struct to describe the following attributes of each planet: mass, radius, colour, position, velocity and per-step accumulated forces. You should be able to figure out which of those values are vectors and scalars (hint: only two are scalars).BR
You can use your code from above. The first step is to create the initial conditions (ie: initialise your struct with sensible values, eg: no velocity, different colours, position them slightly apart, unit mass and radius, no initial accumulated force). Part of the fun is experimenting with these values and seeing what happens!BR
Simulation Step Code
Remember to set your accumulated force for each planet to zero before you run the simulation loop!
Making Your Planets Move
Create two nested for loops that iterate through each planet. Make sure you skip (continue) in the case where the indices are the same as the same planet won't attract itself!BR Inside the inner for loop, get a handle on the pair of planets for easy access (ie: get pointers to them).BR Calculate the distance between them using the Euclidean distance between their current positions.BR You can now calculate the attractive gravitation force between the two bodies in Newtons. The trick is converting this scalar value into a vector to add it to the accumulated force vector for each pair of planets.BR
float fForce = /* Calculated force using Newton's Universal Law of Gravitation - just plug in the values */;
float fVector[3] = {planet[iInner].fPosition[0] - planet[iOuter].fPosition[0], planet[iInner].fPosition[1] - planet[iOuter].fPosition[1], planet[iInner].fPosition[2] - planet[iOuter].fPosition[2]};
// Make fVector into a unit vector!!!
fVector[0] *= fForce;
fVector[1] *= fForce;
fVector[2] *= fForce;
// fVector is now your force vector that should be added to the force accumulator for planet[iOuter].
// The opposite force vector can be created by multiplying this one by -1 (since it's in the opposite direction) BUT it's not necessary because we're cycling through each pair of planets anyway. If you swap iOuter for iInner when you add the force, you'll see that the attractive force becomes repulsive - try it out!So the loop becomes (for each planet):
for (iOuter)
{
for (iInner)
{
Planet* p1 = &planet[iOuter];
Planet* p2 = &planet[iInner];
// Calculate force vector
p1.fAccumForce[0] += fForce[0];
p1.fAccumForce[1] += fForce[1];
p1.fAccumForce[2] += fForce[2];
}
}
for (iIndex)
{
Planet* p = &planet[iIndex];
// Calculate new velocity vector based on accumulated forces:
// Pseudo-code: fNewVelocity[3] = (p.fAccumulatedForces[3] / p.fMass) * fTimeStep;
// Note: fTimeStep is the time interval by which you advance the simluation. Smaller values will yield more accurate results, but the simulation will run slower. Experiment with this value!
// Add the new velocity vector to the planet's existing one to calculate the new velocity of the planet
// Pseduo-code: p.fVelocity[3] += fNewVelocity[3];
// Finally calculate the new position by first calculating the displacement of the planet
// Pseduo-code: p.fPosition[3] += (p.fVelocity[3] * fTimeStep);
// Done!
}A note on fTimeStep:
The best graphical simulations are the ones that run at the same rate regardless of the speed of the machine on which they are run and they always produce the same result. This means you cannot run the simulation step every frame AND you must keep fTimeStep constant at all times.BR Therefore, you only want to run the simulation loop after the amount of time described by fTimeStep has elapsed in the system time. It is very easy to do this:
// During initialisation: // Get the elapsed system time since the program was started as described above // Store it in a global variable, eg: dwLastTime // In the main loop: // Find the difference between the current elapsed system time since the program was started as described above and dwLastTime // If it exceeds fTimeStep, then run the simulation step and update dwLastTime to the current elapsed system time since the program was started.
If you run your program now you should see the spheres moving around (and through) each other.
You can perform collision detection on spheres by comparing the distance between their centres and the sum of their radii.BR Make sure you add another variable to each planet which says whether it's colliding or not. Set it to 0 at the beginning of the simluation loop and to 1 during the loop if you detect a collision.
// Find Euclidean distance between centres of a pair of spheres // If distance is less than radii, they're colliding (you could colour them differently if they collide, for example, so you can get visual feedback). Set colliding flag to 1.
When they collide, reverse their velocity vectors (multiplying by -1) so they bounce backward from each other. This must be done in the last for loop after the nested one (where the new velocity and position is calculated).
// Calculate new planet velocity as above // If colliding flag is set, multiply new planet velocity by -1 to reverse it (flag will be reset to 0 when next simulation is taken) // Calculate new planet position
This is a very simplistic n-body simulation, and no doubt it will do some strange things because the collision code is pretty naive. For example: planets getting stuck in each other) but the end result will be sure to impress!BR Hopefully for you, the most exciting aspect to this task is you can experiment and try all sorts of simulations.BR You can also improve the collision code. For example, you can make sure that you move a pair of planets so they're just touching when a collision is detected (so they're not penetrating) and try (in-)elastic collisions.
Hopefully you'll end up with something like this:
attachment:planets2.png
If you don't clear the colour buffer after the initial set up, you can get results like this:
attachment:planets.png
The next step is to change the spheres to cubes.
More Information
For those that are interested in dynamics simulations, please check out the following links - they're really cool:
[http://ode.org/ Open Dynamics Engine] - Free open source physics engine (I've used it to create a driving simulator and I think it's brilliant. Well done [http://www.q12.org/ Russell Smith]!)
[http://www.continuousphysics.com/Bullet/BulletFull/index.html Bullet] Another engine I found recently.
- There are big proprietary ones, eg: Havok (used in many popular games).
[http://www.gamasutra.com/resource_guide/20030121/jacobson_01.shtml Verlet integration] is a really neat & simple way to do some advanced simulations. For example you can create [http://www.spench.net/3421/verlet/ tornados] and [http://www.spench.net/3421/cloth/ tearable cloth simulations]. See it on [http://en.wikipedia.org/wiki/Verlet_integration Wikipedia] if you don't have a Gamasutra account.
ViSLAB