Simulating Smoke Particle System

By Adam Sotona and Michal Skvor, December 12, 2008

This example shows a CPU intensive particle simulation of smoke. The application demonstrates how to use CustomNode to create a desired image, and then place it on the JavaFX stage for viewing.

Understanding the Code

Particle

This creates the class Particle, with attributes that help define the size and placement of the smoke particle, as well as how it moves when the timer begins. Notice that the while the image is loaded from outside the file, this class manipulates the image by binding its opacity to an attribute. The following code delcares the Particle class.

Source Code
public class Particle extends CustomNode {
    attribute x : Number;
    attribute y : Number;
    attribute vx : Number;
    attribute vy : Number;
    attribute timer : Number;
    attribute acc : Number;
    
    function create(): Node {
        return ImageView {
            transform: [ 
            Translate{ x : bind x, y : bind y } ]
            image : 
                Image { url: "{__DIR__}/../resources/texture.png" }
            opacity: bind timer / 100
        };
    }
 
    function update(): Void {
        timer -= 2.5;
        x += vx;
        y += vy;
        vx += acc;
    }
    
    function isdead(): Boolean {
        return timer <= 0;
    }    
}

Figure 1: Particle.fx Class

CustomCanvas

The CustomCanvas class contains the body of the animation, first by creating instances of the Particle class and populating them in an array that it holds as an attribute. There are two functions in CustomCanvas class. The create function populates the background drawing with geometries and mouse functions, all the parts of the graphic that remain unchanged in the life of the application. The update function creates the particles. It initializes the vx and vy values that make the particle differ from frame to frame, creating the illusion of a smoking particle.

The following code declares the CustomCanvas class.

Source Code
public class CustomCanvas extends CustomNode {

    private attribute acc : Number;
    private attribute timeline : Timeline;
    private attribute parts : Particle[];
    private attribute random : Random;;    

    function update() : Void {
        insert 
        Particle {
            x : 84
            y : 164
            vx : 0.3 * random.nextGaussian()
            vy : 0.3 * random.nextGaussian() - 1
            timer : 100
            acc : bind acc
        } into parts;
        var i = sizeof parts - 1;
        while( i >= 0 ) {
                parts
            [i.intValue()].update();
            if( parts
            [i.intValue()].isdead()) {
                delete parts[i.intValue()];
            }
            i--;
        }
    }

    public function create(): Node {
        random = new Random();
        timeline = Timeline {
            repeatCount: java.lang.Double.POSITIVE_INFINITY // HACK
            keyFrames : 
                KeyFrame {
                    time : 16.6ms
                    action: 
                        function() {
                            update();
                        }                
                }
        };
        timeline.start();


        return Group {
            content : bind [
                Rectangle {
                    width : 200, height : 200
                    fill : Color.BLACK
                    blocksMouse : true

                    onMouseMoved : 
                        function( 
                        e : MouseEvent ): Void {
                            acc = ( e.getX() - 100 ) / 1000;
                        }
                },
                Line {
                    startX : bind 100 + ( 500 * acc )
                    startY : 50
                    endX : 100
                    endY : 50
                    stroke : Color.WHITE
                },
                Line {
                    startX : bind 100 + ( 500 * acc )
                    startY : 50
                    endX : bind 100 + ( 500 * acc ) - 4 * acc / Math.abs( acc )
                    endY : 48
                    stroke : Color.WHITE
                },
                Line {
                    startX : bind 100 + ( 500 * acc )
                    startY : 50
                    endX : bind 100 + ( 500 * acc ) - 4 * acc / Math.abs( acc )
                    endY : 52
                    stroke : Color.WHITE
                },
                parts
            ]
        };
    }
}

Figure 2: CustomCanvas Class

Frame

This last part populates the stage with an instance of CustomCanvas (calling the create function). Now you can see the smoking particle in a popup frame when we run the app. The following code adds the frame.

Source Code
Frame {     
    stage : 
        Stage {
            fill : Color.BLACK
            content : 
                CustomCanvas {}
        }

    visible : true
    title : "Smoke Particle System"
    width : 200
    height : 232
    closeAction : 
        function() { java.lang.System.exit( 0 ); 
        }
}

Figure 3: Creating the Frame