Saturday, December 26, 2009

Stardust v1.1 - Now with Linked Lists



Stardust project homepage
Particle collection documentation

After a few days of struggle, I've finally done it! Now Stardust implements linked lists internally for particle collections instead of arrays. Linked lists are better than arrays for rapidly adding and removing elements at random positions, theoretically. I found the performance to remain pretty much the same after this major change. However, the internal particle list implementation is now hidden from clients, which makes it much safer, preventing direct access to the particle array as in version 1.0.

To iterate through particles in a particle list, you use the ParticleIterator interface. You shall be familiar with this interface if you have worked with the C++ STL library or the Java collection APIs.

I'll illustrate this iterator interface by some sample code. Here's how you set all the particles' positions in an emitter to (0, 0), the origin.


var particles:ParticleCollection = emitter.getParticles();
var iter:ParticleIterator = particles.getIterator();
var particle:Particle2D;
while (particle = iter.particle as Particle2D) {
particle.x = particle.y = 0;
iter.next();
}

And this is how you add completely-customized particles to an emitter.

var particles:ParticleCollection = new ParticleList();
for (var i:int = 0; i < 100; i++) {
var p:Particle2D = new Particle2D();
p.x = 100 * Math.random();
p.y = 100 * Math.random();
particles.add(p);
}
emitter.addParticles(particles);

Pretty clean and self-explanatory code, isn't it? I'm quite satisfied with how the interface ended up. From now on, I can tweak the internal implementation and algorithm of the particle list without having to change the code outside of the black box. Gosh, I should have done this long ago.

Tuesday, December 22, 2009

Stardust Video Tutorial - More On The Main Loop



View Video
View playlist
Stardust project homepage

It's been a long time since my last update for Stardust video tutorial. Thanks a lot to Clockmaker - his article introducing Stardust gave me a great motivation boost to get down to recording more new vids. Clockmaker inspires me a lot, including tilting illustrations in blog articles for 5 degrees :p

This video covers more in-depth details on the main loop, including emitter events, initializer/action priorities, and particle masking, which are very important must-knows if you really want to do some serious coding and complex behavior handling with Stardust.

Monday, December 21, 2009

Clockmaker's Stardust Butterfly Demo



View Clockmaker's article & demo
Stardust project homepage

I'm very honored to come to know that Clockmaker, one of my favorite Japanese ActionScripters, has gotten his hands on Stardust! He's created a couple of awesome Stardust demos at Wonderfl, a Japanese ActionScript community that provides online Flex compiling service. You don't have to have the Flex compiler to test your ActionScript code: just type the code on the website and see it work the magic on the fly, literally. You can check out his demos in his Wonderfl gallery.

Clockmaker's Stardust Butterfly demo is very, very astonishing. The glow effect, the fluid butterfly motion, and the reflection...they've all left me in silent awe. Just watch the demo on his blog and you'll know that I'm not joking!

Saturday, December 12, 2009

Boundary Collision Sparks



View SWF
View source
Stardust project homepage

That's it. I've always wanted to create an action trigger that is triggered by "collision" with a deflector, or "deflection" with a deflector not acting as an obstacle. Now I've finally got it done :)

The magic behind the scene is quite simple: the Deflect class is modified so as to map a deflector to a boolean value, a flag for determining if a "collision" or "deflection" has occurred, through a dictionary object; the DeflectorTrigger class then simply check the flag to determine if the trigger is triggered.

However, extra caution is now required when creating custom deflectors. If you decide that a deflector does not "deflect" a particle, the overrided calculateMotionData4D method must return a null value instead of a "new MotionData(particle.x, particle.y, particle.vx, particle.vy)" copy of the particle's original position and velocity, which tells the DeflectorTrigger class that it should not be triggered.

My next target should be creating a trigger that can detect interparticle collision, which would not be an easy task. Well, it's easy, but what I mean is that it's not going to be easy to implement it with action trigger structures; at least now I don't have the slightest idea about how to do it. I'll try and figure out a way to do it cleanly and efficiently.

Thursday, December 10, 2009

Stardust + ZedBox Example: Duo Firewheel



View SWF
View source
Stardust project homepage

I've always felt like the ZedBox extension for Stardust has been left out for quite a while, so today I decided to spent some time revising the extension by improving the performance, and add a couple of new features, one of which being the ZBBillboardOriented action, which is essentially a ZedBox version of the BillboardOriented action.

This example demonstrates the use of the ZBBillboardOriented action and the use of a custom gravity fields, the VortexField3D class. It also makes use of what the native Stardust 3D engine cannot do: hierarchical object structures. The two wheels are actually rotated "disk" ZedSprite objects added to the display list of a parent ZedSprite. These two disks originally lied on the XZ plane ,Y axis being the horizontal axis on the screen.

I'm planning on using the ZBBillboardOriented action heavily on one of my personal projects. I'll bring it into public once it's finished.

Monday, December 7, 2009

Asynchronous Computation

Trevor McCauley's article about asynchronous ActionScript execution provides a brilliant way to distribute CPU-consuming computation from one frame to multiple frames. I was so excited when I first read this article, eager to try it out so much, and finally, today I had the chance to give it a try.

One of my classmate was working on a project for a course on social networks. He used ActionScript for his project, trying to parse an XML file representing a graph of over 80 thousand nodes and reconstruct the graph using his own classes. If you, whether intentional or not, had somehow make your Flash program enter an infinite loop, you might know that Flash Player throws a time-out error if a single frame takes more than 15 seconds to render.

His XML file was so big that even parsing it from string to the AS3 internal XML type took a couple of seconds, let alone instantiating his own classes representing nodes and edges for that graph, and as I expected, he was faced with the 15-second-error nightmare.

I applied Trevor's idea to solve this problem by factoring out the instantiation of his classes and distribute it across multiple frames, and things worked out perfectly and beautifully!

Thanks Trevor. Love you :)

Here's some code snippet:


//loader for loading external XML file
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, onLoad);

//start loading the XML file
loader.load(new URLRequest("myXML.XML"));

//XML variables
var xml:XML;
var nodes:XMLList;
var edges:XMLList;

//graph model
var graph:Graph = new Graph();

//asynchronous computation variables
var currentIndex:int; //this index keeps track of the progress of asynchronous computations
var length:int;
var startTime:int;
var allcatedTimePerFrame:int = 16; //approximately 60fps

//loader complete event handler
function onLoad(e:Event):void {
xml = XML(loader.data);
nodes = xml.node;
edges = xml.edge;

//initialize asynchronous computation variables
currentIndex = 0;
length = nodes.length();

//start building nodes asynchronously
addEventListener(Event.ENTER_FRAME, buildNodes);
}

//asynchronous computation for building nodes
function buildNodes(e:Event):void {
//take a snapshot of the current time
startTime = getTimer();

for (var i:int = currentIndex; i < length; i++) {
//build nodes in the graph model
graph.createNode(nodes[i].@name);
currentIndex++;

//check if the computation is complete
if (currentIndex == length) {
//stop this listener
removeEventListener(Event.ENTER_FRAME, buildNodes);

//initialize asynchronous computation variables
currentIndex = 0;
length = edges.length();

//start buliding edges
addEventListener(Event.ENTER_FRAME, buildEdges);
return;
}

//check if the time spent on this loop exceeds the allocated time per frame
if (getTimer() - startTime() > allocatedTimePerFrame) {
//display the current progress in a text field
progress_txt.text = "building nodes: " + 100 * (currentIndex / length) + "%";
return;
}
}
}

//asynchronous computation for building edges
function buildEdges(e:Event):void {
//take a snapshot of the current time
startTime = getTimer();

for (var i:int = currentIndex; i < length; i++) {
//build nodes in the graph model
graph.createEdge(edges[i].@node1Name, edges[i].@node2Name);
currentIndex++;

//check if the computation is complete
if (currentIndex == length) {
//stop this listener
removeEventListener(Event.ENTER_FRAME, buildEdges);

//we're done!!
//display the graph on the stage
graph.buildView();
addChild(graph.getView());

return;
}

//check if the time spent on this loop exceeds the allocated time per frame
if (getTimer() - startTime() > allocatedTimePerFrame) {
//display the current progress in a text field
progress_txt.text = "building edges: " + 100 * (currentIndex / length) + "%";
return;
}
}
}

Thursday, December 3, 2009

Synchronized Movie Clip



View SWF
View source
Stardust project homepage

The new SynchronizedMovieClip class is a StardustMovieClip subclass, which is for "time step synchronization" I mentioned in this article. In this demo you can notice that there's a slider bar for tweaking the time scale of the particle simulation (which tweaks the Emitter.stepTimeInterval property).

In the absence of the SynchronizedMovieClip class, the movie clips will play at the original speed even if the simulation time is scaled down, which is pretty logical since the frame rate is not changed, only the internal time scale for simulation changed.

Now the movie clips are SynchronizedMovieClip instances and the emitter has a StardustSpriteInit initializer and StardustSpriteUpdate action added to it, the movie clips can be synchronized with the particle simulation time scale. The StardustSpriteInit initializer stops the movie clips at the very beginning by calling the init() method defined in the IStardustSprite interface, which the StardustSpriteMovieClip class implements, and the StardustSpriteUpdate action sets the movie clips' frames according to the simulation time scale through the update() method, also defined in the IStardustSprite interface.

This is how the synchronization of movie clips and Stardust particle simulation is done.

Wednesday, December 2, 2009

Stardust Movie Clip



Stardust project homepage


If you've played with Stardust for a while, you might notice that there's an Emitter.stepTimeInterval property which allows you to dynamically adjust the particle simulation timescale. Here's one demo showing you the adjustable timescale feature in action.

Now that you can adjust the timescale of particle simulation, the next problem is how to synchronize your movie clips with the timescale.

Originally, there was only a StardustSprite class that extends the Sprite class. My original intent was simple: allow a display object to respond to each Emitter.step() method call and to have access to timescale information, in turn updating itself correspondingly. If a particle's display object is a subclass of the StardustSprite class, and the emitter has a StardustSpriteUpdate action added to it, then the display object's update() method is invoked upon each emitter step, with a reference to the emitter, timescale information, and a reference to the particle's data passed in as parameters.

In order to synchronize movie clips with the dynamically adjusted timescale, I wrote the StardustMovieClip class, which pretty much is just a movie clip version of the StardustSprite class, and I extracted the update() method into a separate interface, the IStardustSprite interface. Now if you use a movie clip that extends the StardustMovieClip class for the particles and add the StardustSpriteUpdate action to the emitter, you can handle timescale change by overriding the update method() in your class. I'll write a base class just for this purpose when I have time. But for now, you can try it yourself if you're interested.

Another addition to the StardustSprite family is the IStardustSprite.init() method. My original intention was to provide a way to "reset" recycled display objects for particles initialized by the PooledDisplayObjectClass. Since used display objects are recycled into a pool and taken out again for future use, they indeed need a way to reset themselves when they are taken out from the pool and used again. For instance, you might want reset the playhead of your movie clips back to the first frame. To invoke the IStardustSprite.init() method, add the StardustSpriteInit initializer to the emitter.

Tuesday, December 1, 2009

Pooled Display Object Initializer for Stardust



Stardust project homepage


One of the major CPU consumption cause of Stardust is the display object instantiation, since new display objects are created rather rapidly along the particle simulation. So I think using an object pool to store display objects that are no longer needed for future use is a nice way to improve performance.

The new PooledDisplayObjectClass and PooledDisplayObjectClass3D initializers are "pooled" versions of their counterparts, the DisplayObjectClass and DisplayObjectClass3D initializers, respectively.

I've run a quick performance test, and found that when using the PooledDisplayObjectClass initializer to initialize particles with display objects, the performance improves by approximately 10%~15%, compared to the original DisplayObjectClass initializer. It's not a significant improvement, but at least it's better :)