Author: makc
Date: May 1st 2008
version: 3.0.3
In this 2nd tutorial we will see how to employ StarField class to create something other than star field. Our goal will be to make virtual dust devil following the mouse around.
To save some time with boring stuff that you should already know from other countless tutorials, we will use this code stub:
package {
import flash.display.*;
import flash.events.*
import flash.filters.*
import sandy.core.*;
import sandy.core.data.*;
import sandy.core.scenegraph.*;
import sandy.primitive.*;
import sandy.materials.*;
public class DustDevil extends Sprite {
// screen size
private var w = 400;
private var h = 300;
// sandy scene
private var scene:Scene3D;
// number of dust particles
private var N:Number = 5000;
// dust particles
private var dust:StarField;
// animation "time"
private var t:Number = 0;
public function DustDevil () {
// set up scene
scene = new Scene3D ("", this, new Camera3D (w, h), new Group (""))
scene.camera.y = 100;
scene.camera.z = -400;
// set up dust
dust = new StarField ()
dust.fadeFrom = -scene.camera.z; dust.fadeTo = h * 1.5;
scene.root.addChild (dust);
// subscribe to Event.ENTER_FRAME
addEventListener (Event.ENTER_FRAME, enterFrameHandler);
}
private function enterFrameHandler (event:Event):void {
// advance animation time
t += 0.1; if (t > Math.PI * 2) t -= Math.PI * 2;
// update dust particles
for (var i:int = 0; i < N; i++)
{
// if no particle yet, make it
if (i <= dust.stars.length)
dust.stars [i] = new Vertex ();
// TODO complete the code :)
}
// follow mouse
dust.z = 0.9 * dust.z + 0.1 * (h - mouseY - 150);
dust.x = 0.9 * dust.x + 0.1 * (mouseX - w/2);
scene.render ();
}
}
} This code is a document class for empty 400×300 FLA. It provides visible (on black background) StarField object following the mouse around. We will now concentrate on completing update loop.
| From quick look at dust devil photographs we can see that its basic shape is tubular. We will model this shape in several steps.
First, we will arrange our N dust particles in cylinder-like pattern. To do that, we will need some unique number for every particle, so that we could later place them all differently using same formula. Obvious choise would be // TODO complete the code :) var u:Number = i / N; We can now use scaled unit circle coordinates formula for particle x and z coordinates: var r0:Number = 15; var u0:Number = u * 1e3 var x0:Number = r0 * Math.cos (u0); var z0:Number = r0 * Math.sin (u0); We multiplied u with some big number (1000) here to make our particles to cover many full circles with radius r0 = 15. To spread our particles vertically, we will lineary increase their y coordinates: var y0:Number = 300 * u; dust.stars [i].x = x0; dust.stars [i].y = y0; dust.stars [i].z = z0; The result of this code is shown on the left. Not bad for a start, but a bit boring, hence the next step. What can we do to change cylinder into something less regular? We are free to use non-linear formula for y coordinate, and we can alter radius r0 for every particle. There is no quick recipee here, so we will simply have to play with formulas until we like the result. In my case, I ended up with this: var r0:Number = 10 + 5 * Math.cos (u * 4); ... var y0:Number = 300 * u * u * u; but you could put there anything you like, really… any way, this approximation is shown on the left, again. |
So far, our dust devil is essentially static - if you don't move your mouse, dust particles just hang in there. Boring! We can change this in several ways. For example, we could go back to formula for x and z coordinates, and make our particles jump to random positions on circle instead of getting same x and z every frame:
var u0:Number = u * 1e3 * (1 + Math.random ()); This gives our thing a “noisy” look instead of regular “spiral”-like appearance. But… still boring :) Let us remember here that we have special variable reserved for animation - t. We can use this variable in many creative ways, but for the sake of example we will do this: var x1:Number = 5 * Math.cos (5 * u + t); var z1:Number = 5 * Math.sin (5 * u + t); dust.stars [i].x = x0 + x1; dust.stars [i].y = y0; dust.stars [i].z = z0 + z1;
|
Ok, last thing to add is the dust cloud near the ground. Again, we will do this by adding some random offset to particle coordinates; the trick is to add a lot near the ground, and add a little elsewhere. And again, there is no recipee, you have to play with formula until it's not ugly. I gave up after this:
var r2:Number = 5 / (6 * u * u + 0.1) * (1 + 0.3 * Math.random ());
but I don't really like it, I suggest you to try and come up with something better. This formula gives us radius; to calculate offset, we will put two random angles through spherical coordinates transformation with this radius:
var u2:Number = 2 * Math.PI * Math.random (); var v2:Number = 1 * Math.PI * Math.random (); var x2:Number = r2 * Math.cos (u2) * Math.cos (v2); var z2:Number = r2 * Math.sin (u2) * Math.cos (v2); var y2:Number = r2 * Math.sin (v2); dust.stars [i].x = x0 + x1 + x2; dust.stars [i].y = y0 + y2; dust.stars [i].z = z0 + z1 + z2;
Ok, that's enough of math for single tutorial, we do not want your head to explode :) Few more things left to do, though. In order for our dust devil to fade out at the top we will alter particles color using y coordinate values:
dust.starColors [i] = ((1 + 0xFFFFFF) * Math.floor(255 * (300 - y0) / 300)) + 0xFFFFFF;
Finally, we will add a blur filter to our particles, to make them look more like dust:
// (in class constructor) dust.container.filters = [ new BlurFilter (2, 2, 1) ];
Ok, let's put some desert picture in the background and publish.
If you actually followed the tutorial, you should have ended up with the code like this:
package {
import flash.display.*;
import flash.events.*
import flash.filters.*
import sandy.core.*;
import sandy.core.data.*;
import sandy.core.scenegraph.*;
import sandy.primitive.*;
import sandy.materials.*;
public class DustDevil extends Sprite {
// screen size
private var w = 400;
private var h = 300;
// sandy scene
private var scene:Scene3D;
// number of dust particles
private var N:Number = 5000;
// dust particles
private var dust:StarField;
// animation "time"
private var t:Number = 0;
public function DustDevil () {
// set up scene
scene = new Scene3D ("", this, new Camera3D (w, h), new Group (""))
scene.camera.y = 100;
scene.camera.z = -400;
// set up dust
dust = new StarField ()
dust.fadeFrom = -scene.camera.z; dust.fadeTo = h * 1.5;
dust.container.filters = [ new BlurFilter (2, 2, 1) ];
scene.root.addChild (dust);
// subscribe to Event.ENTER_FRAME
addEventListener (Event.ENTER_FRAME, enterFrameHandler);
}
private function enterFrameHandler (event:Event):void
{
// advance animation time
t += 0.1; if (t > Math.PI * 2) t -= Math.PI * 2;
// update dust particles
for (var i:int = 0; i < N; i++)
{
// if no particle yet, make it
if (i <= dust.stars.length)
dust.stars [i] = new Vertex ();
// unique number for every particle
var u:Number = i / N;
var r0:Number = 10 + 5 * Math.cos (u * 4);
var u0:Number = u * 1e3 * (1 + Math.random ());
var x0:Number = r0 * Math.cos (u0 * 1e3);
var z0:Number = r0 * Math.sin (u0 * 1e3);
var y0:Number = 300 * u * u * u;
var x1:Number = 5 * Math.cos (5 * u + t);
var z1:Number = 5 * Math.sin (5 * u + t);
var r2:Number = 5 / (6 * u * u + 0.1) * (1 + 0.3 * Math.random ());
var u2:Number = 2 * Math.PI * Math.random ();
var v2:Number = 1 * Math.PI * Math.random ();
var x2:Number = r2 * Math.cos (u2) * Math.cos (v2);
var z2:Number = r2 * Math.sin (u2) * Math.cos (v2);
var y2:Number = r2 * Math.sin (v2);
dust.starColors [i] = ((1 + 0xFFFFFF) * Math.floor(255 * (300 - y0) / 300)) + 0xFFFFFF;
dust.stars [i].x = x0 + x1 + x2;
dust.stars [i].y = y0 + y2;
dust.stars [i].z = z0 + z1 + z2;
}
// follow mouse
dust.z = 0.9 * dust.z + 0.1 * (h - mouseY - 150);
dust.x = 0.9 * dust.x + 0.1 * (mouseX - w/2);
scene.render ();
}
}
}