Table of content

Previous: 4.a: The theory behind the effect

b. Putting the trail together

New to the really new stuff: we add to function leader() the instructions which will produce the tail:

...
var tail_p = 0.2;		
var tail_position;
...

// now for the green tail
// first compute velocity length
my.skill_a = 
sqrt(my.vel_x*my.vel_x+my.vel_y*my.vel_y+my.vel_z*my.vel_z);

// then compute the number of particles necessary for each frame
my.skill_b = ceiling(my.skill_a*time/(tail_size*tail_p));

// finally the tail is constructed here
my.skill_c = 0;
while(my.skill_c<my.skill_b)
{
	tail_position.x = my.x - time*my.vel_x*(my.skill_c/my.skill_b-1);
	tail_position.y = my.y - time*my.vel_y*(my.skill_c/my.skill_b-1);
	tail_position.z = my.z - time*my.vel_z*(my.skill_c/my.skill_b-1);

	effect(tail,1,tail_position.x,nullvector);
	
	my.skill_c += 1;
}

What these mathematical equations do is illustrated by Figure 4.2

Figure 4.2: Creating a trail of particles. The green dashed line is the trajectory of the leader. The leader is represented by a dark circle and we show its positions at instants t-time and t. The pink squares are particles which make up the tail. Their position is computed from that of the leader at instant t, and they are placed using the variable my.skill_c = 0, 1, ..., my.skill_b-1 by function leader(). my.skill_b contains the number of times the image used for the tail can be fitted between two consecutive positions of the leader on the trajectory. The gray vector is the vector velocity (vel_x,vel_y,vel_z) of the leader at instant t multiplied by time.

Figure 4.2 shows a snapshot of the leader at instant t, and it also shows where the leader was at instant t-time. The gray arrow is a vector equal to the velocity of the leader at instant t multiplied by time: (my.vel_x, my.vel_y, my.vel_z)*time. According to equation (2.6), this vector is also equal to the difference between the vector of the current position of the leader, my.x(t), and the vector pointing where the leader was at instant t-time, my.x(t-time):

my.x(t) = my.x(t-time) + (vel_x, vel_y, vel_z)*time

so

(vel_x, vel_y, vel_z)*time = my.x(t) - my.x(t-time)	(4.1)

Therefore, we can compute from the current location of the leader, my.x(t), the equation of the line joining it to my.x(t-time), its length, and the number and position of tail particles we can fit in there. To be more precise, we will repeat the following computation once at each frame.

Using equation (1.7), we start by computing the length of the velocity vector of the leader:

my.skill_a = |vel| = sqrt(my.vel_x2 + my.vel_y2 + my.vel_z2).	(4.2)

where vel is just short-hand for the vector (vel_x, vel_y, vel_z). From equation (4.1), we deduce that time*|vel| is equal to the distance between the positions of the leader at instants t and t-time. Dividing this distance by tail_size*tail_p gives the number of particles we need to squeeze between positions my.x(t) and my.x(t-time) to have a nice, compact tail between them. We store this number in my.skill_b:

my.skill_b = ceiling(|vel|*time/(tail_size*tail_p)) 		(4.3)

where the function ceiling() is defined in equation (3.3).

The last thing we need to do is place the my.skill_b tail particles along the line joining my.x(t) and my.x(t-time). This is done by putting the following formula in a loop on the tail particles, where particles are numbered by the counter my.skill_c (my.skill_c = 0, 1, ..., my.skill_b-1):

tail_position = my.x - time*vel*my.skill_c/my.skill_b - time*vel		(4.4)

The vector tail_position, so defined, points to the locations where we put the tail particles (vel is the velocity of the leader - see above). This equation is very similar to equation (3.4) for the laser beam. For each value of my.skill_c, we create the particle at the location tail_position with an effect() instruction. Let's see how the formula works.

First, there is the last term - time*vel. We put it there, just like in equation (2.7) (see notice on this subject in lesson 2), to make sure that the tail particles are created on the leader path, and not ahead of it. Neglecting this term in the following discussion, let us replace my.skill_c by a few of its values to see where tail_position points.

If my.skill_c = 0, tail_position = my.x: the first tail particle is placed where the leader is (see Figure 4.2). If my.skill_c = 1, tail_position = my.x - time*vel*1/my.skill_b: this particle is on the line joining the leader positions at t and t-time, but just back of the leader by time*vel*/my.skill_b which is roughly equal to tail_size*tail_p (see Figure 4.2). If my.skill_c = 3, same thing but a little further back by roughly 2*tail_size*tail_p. I say roughly since equation (4.4) is not quite as precise as the one used for the laser beam. It is however simpler to program using skill variables. The loop repeats the process of putting particles along a straight line, each time a little further back from the leader, until my.skill_c = my.skill_b-1. This last particle is set right next to the position my.x(t-time) of the leader at the previous frame (see Figure 4.2).

Here are snapshots of the trail effect produced by these few instructions in function leader():

Figure 4.3: Snapshots of the roman candle effect with only the green trail. The pointy look of the trail is obtained by fading out the tail particles. The effect still looks nice even if the frame rate drops to around 30 fps.

Not bad when you think that all it took is a little algebra! The effect is also quite economical both in particles (each green trail contains only around 40 tail particles) and in frame rate (which almost does not drop at all).

Now for the finishing touch. Compact trails are nice but one can always add more. In the case of a missile, for instance, a convincing exhaust would require not only a flame but also sparks and smoke to simulate the combustion and movement of the weapon. To complete this example, we will add some white sparks to the effect, which will be released by the leader in the last half of its life. We already introduced the spark() function for the sparks. All we need now is to add the lines of code in the leader() function which will produce those sparks. Here are the necessary instructions:

var spark_vel;
...

if (my.lifespan<(0.5*leader_life))
	{
		// spark is created with a little random velocity in (xy)
		spark_vel.x = 10*(random(1)-0.5);
		spark_vel.y = 10*(random(1)-0.5);
		spark_vel.z = 0;
		effect(spark,1,my.pos,spark_vel);
	}

They are very simple. A condition on my.lifespan ensures that white sparks are only created during the second half of the leader's life time. We also use the vector spark_vel, to which we give a small, somewhat random direction in the (xy) plane, as initial velocity for the particles. Finally, the sparks are created at the leader's position.

Here is a view of the final effect:

Figure 4.4: View of the final effect including the white sparks of function spark().

Well, that is all for the trail algorithm. Now, it's time for you to modify it and generalize it for your needs. Have fun with that cool toy!

Next: Lesson 5. Polar coordinates: going in circles and defining orbits