Samstag, 12. September 2009

Chunk 52

Chunk 52


A Curve Example 2

The previous chapter used acceleration and deceleration to show some interesting effects that can be achieved with curves. In this chapter we will continue with curves and have a bit of fun with some very basic physics using a simple fireworks simulation. Fireworks will give us a chance to use curves with trajectories and will allow us to display some pretty colours. Fireworks are basically shot up in to the air using an initial force which is acted on by gravity causing them to decelerate as they gain height. At a certain point in time the firework explodes throwing out a multitude of particles, each of which are also acted on from the force of the explosion and gravity. This should provide us with an interesting algorithm to show some rather pretty patterns.

Fireworks

First of all let us have a look at which elements and variables will be required to implement a firework simulation. Without gravity, the firework would continue unhindered on a straight line trajectory, so adding it will introduce a nice believable curved projectile trajectory to the simulation. The firework will require an initial force to launch it followed by a simple time delay as to when the explosion should take place. Initially the firework will draw the launching rocket as a particle, once the explosion occurs, the firework stops drawing the rocket and instead draws each of the explosion particles.

Some (Very) Basic Physics

There are some extremely complicated formulas for working out projectile trajectories which take into account wind resistance and all sorts of other variables. In order to keep the simulation simple, the fireworks code below uses a very simple algorithm for working out the x and y coordinates of a particle at a given time. We will not delve into the mathematics behind this formula or the sin and cos methods as there is plenty of material on the web available and this demo is not intended to be a maths lesson.

x = (velocity * cos(angle) * time)
y = (velocity * sin(angle) * time) - (½ gravity * time²)

The x and y coordinates given from this formula assume that the projectile was initially at 0, 0. This is not the case for either the rocket or the particles. The rocket will be launched from the middle of the ground of the screen. Processing uses the typical Java representation of 0, 0 being located at the top left of the window so our rocket will initially be launched from a point 200 pixels left and 400 pixels down. The explosion particles will have their initial position at the last position of the rocket.

OO Principles


Although this section does not primarily deal with Object Oriented principles, the fireworks program will make use of a couple of OO design principals in order to reduce the amount of code through reuse. The fireworks code is split into two main classes, namely the 'Rocket' class and secondly the 'Particle' class. The rocket class uses composition and contains a main particle, the rocket, and an array of particles which are part of the explosion. Using this model allows both the rocket and the particles to be drawn using the same code.

Analysing the code

As this is a fairly large program in comparison to some of the other examples, we will not analyse the code line for line but rather give an overview of how the code reaches its objectives. The code itself is commented and should be fairly self explanatory.

The setup method simply sets the background colour of our canvas to white, sets the size of the window and sets the frame rate to 30 frames per second ensuring that the animation will not run too fast. The firework variable is set to a new instance of firework.

The main draw method checks if the currently assigned firework is alive. A firework is defined as being alive if the main rocket is being drawn, the explosion has not yet happened, or if any of the explosion particles are still being drawn, their time to live has not expired. Each particle is assigned a random time to live. Its time to live is simply decremented by one during each draw cycle.

Initially the program sets up a single rocket using the Firework constructor. This in turn creates a firework with a single particle known as the rocket and a random number of particles for the explosion. Processing calls the main draw() method which delegates to the firework. If the firework is no longer alive then the main draw method replaces the old firework with a new one. This process will run forever until the user closes the window.

The firework will draw its rocket until the pre-defined explosionOffset is reached, at which point the firework will instead draw each explosion particle.

One important point to note is that both Processing and Java expect the value for the cos and sin functions to be given in radians.



Figure 1: A screenshot showing a few fireworks after the program has been running for a few seconds.

The Code


The complete 'Fireworks' program is shown below.





// Setup the gravity constant.
float gravity = 9;

// Create the initial firework.
Firework firework = new Firework();

void setup() {
// Setup the canvas.
background(255);
smooth();
size(400,400);
frameRate(30);
}

void draw() {
// Comment / uncomment the next line in order to hide / show the previously drawn elements.
//background(255);
smooth();

// Check if the firework is alive, if not create a new firework.
if (!firework.isAlive()) {
firework = new Firework();
}

// Draw the firework
firework.draw();
}

/**
* Represent a rocket together with its explosion particles.
**/
class Firework {
Particle rocket;
Particle[] particles;
boolean explosion = false;
float explosionOffset;

Firework() {
// Setup the rocket as a new particle to be launched from the bottom middle of the canvas.
rocket = new Particle(
200, 400,
random(80, 100), random(1.4, 1.8),
random(0, 200), random(0, 200), random(0, 200));

// Setup the explosion particles
particles = new Particle[(int) random(80, 200)];

// Setup the time when the rocket should explode.
explosionOffset = random(2.5, 4.5);
}

void draw() {
strokeWeight(2);

// If the explosion has occurred, draw the explosion particles, otherwise draw the rocket.
if (explosion) {
int i = 0;
for (i = 0; i < particles.length; i++) {
// Increase the colour values of the particles, should give the effect of the particle fading out.
particles[i].fadeOut();

// Draw the particle.
particles[i].draw();
}

} else {

// Check if the explosion time has elapsed.
// If the explosion has occurred, setup each explosion particle to initially start where the rocket exploded
// and given each a random colour.
if (rocket.particleTime > explosionOffset) {
explosion = true;
int i = 0;
for (i = 0; i < particles.length; i++) {

particles[i] = new Particle(
rocket.initialX + rocket.dx, rocket.initialY - rocket.dy,
random(20, 40), random(0.5, 2.5),
random(200, 255), random(100, 200), random(0, 255));
}
}

// Draw the rocket
rocket.draw();
}
}

boolean isAlive() {
// If the explosion has not yet occurred, the firework is alive.
boolean alive = !explosion;
int i;

// If the explosion has occurred and the particles have been initialised,
// check if each particle is still alive.
for (i = 0; i < particles.length; i++) {
if (particles[i] != null) {
alive = alive || particles[i].isAlive();
}
}
return alive;
}
}

/**
* Represent a particle which has a trajectory.
**/
class Particle {
float dy = 0;
float dx = 0;
float particleTime = 0;
float timeToLive = random(15, 50);
float velocity, angle, initialX, initialY, initialisationTime;
float colourRed, colourGreen, colourBlue;

/**
* @param x Initial X position.
* @param y Initial Y position.
* @param v Initial velocity.
* @param a Initial angle of the launch trajectory.
* @param r Initial red colour.
* @param g Initial green colour.
* @param b Initial blue colour.
**/
Particle(float x, float y, float v, float a, float r, float g, float b) {
velocity = v;
angle = a;
initialX = x;
initialY = y;
colourRed = r;
colourGreen = g;
colourBlue = b;
}

/**
* Cause the colours to tend towards white (255, 255, 255).
**/
void fadeOut() {
colourRed++;
colourGreen++;
colourBlue++;
}

void draw() {
// Here we use the trajectory formula. Note that the cos and sin functions expect angles in radians.
dx = velocity * cos(angle) * particleTime;
dy = (velocity * sin(angle) * particleTime) - (0.5 * gravity * particleTime * particleTime);

stroke(colourRed, colourGreen, colourBlue);
point(initialX + dx, initialY - dy);

particleTime = particleTime + 0.1;
timeToLive--;
}

/**
* Check if the particle is alive and should be drawn.
* The particle is alive if the time to live is greater than zero.
**/
boolean isAlive() {
return timeToLive > 0;
}
}

Keine Kommentare:

Kommentar veröffentlichen