Simulating Smoke Particle System
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.
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.
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.
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