From Gossip@caterpillar

Computer Graphics: 煙火與煙

 

 


事實上之前的煙火在施放時並沒有煙,只有火花,可以結合兩個粒子系統來製作煙火在燃燒過程中所產生的煙硝軌跡。

在結合兩個粒子系統時,要注意,每個粒子都是獨立的,火花粒子就是火花粒子,不會與煙粒子相互影響,只不過煙粒子產生的位置是火花的目前位置,只要將位置的資訊傳遞給煙粒子物件就可以了。

指定煙粒子的方式採用比較簡單的方式,首先產生足夠的煙粒子,它們的狀態都為false,然後等待重新指定它們為復活狀態,每次繪製完火花後,在一堆煙粒子物件中尋找狀態為false的煙粒子,然後指定新的初始位置給這些粒子,如此煙粒子就會跟隨著火花的軌跡而產生了。

這邊需要之前製作的FireworkParticle與SmokeParticle類別,您可以直接看看 範例

  • FireworkAndSmoke.java
package onlyfun.caterpillar.graphics.particle;

import java.awt.*;
import javax.swing.JApplet;

public class FireworkAndSmoke extends JApplet
implements Runnable {
private final int fireworkMax = 200;
private final int smokeMax = fireworkMax * 10;
private FireworkParticle fireworkParticles[]; // 煙火粒子
private SmokeParticle smokeParticles[]; // 煙粒子
private int appletWidth, appletHeight, xCenter,yCenter;
private Image offScreen;
private Graphics drawOffScreen;

public void init() {
setBackground(Color.black); // 背景為黑色

fireworkParticles =
new FireworkParticle[fireworkMax]; // 建立粒子
smokeParticles = new SmokeParticle[smokeMax];

// 取得顯像區域
appletWidth = getSize().width;
appletHeight = getSize().height;

// 煙火初始位置
xCenter = appletWidth/2 +
(int)(Math.random()* 150 - 150);
yCenter = appletHeight/2 +
(int)(Math.random()* 150 - 150);
for(int i = 0; i < fireworkMax; i++)
fireworkParticles[i] = new FireworkParticle();

for(int i = 0; i < smokeMax; i++) {
smokeParticles[i] = new SmokeParticle();
}

// 建立次畫面
offScreen = createImage(appletWidth, appletHeight);
drawOffScreen = offScreen.getGraphics();
}

public void start() {
(new Thread(this)).start();
}

public void update(Graphics g) {
paint(g);
}

public void paint(Graphics g) {
g.drawImage(offScreen, 0, 0, this);
}

public void run() {
Color color;
boolean replay;

while(true) {
replay = true;
drawOffScreen.clearRect(
0,0, appletWidth, appletHeight);

for(int i = 0; i < fireworkMax; i++) {
if(fireworkParticles[i].getState()) {
color = fireworkParticles[i].getColor();
double x =
fireworkParticles[i].getPoint().getX();
double y =
fireworkParticles[i].getPoint().getY();
drawOffScreen.setColor(color);
drawOffScreen.fillOval((int)x, (int)y, 1, 1);

// 為煙火加上煙
for(int j = 0; j < smokeMax; j++) {
if(!smokeParticles[j].getState()) {
smokeParticles[j].setLife(
(int)(50*Math.random()));
smokeParticles[j].resume(
new Point((int)x, (int)y));
break;
}
}

fireworkParticles[i].nextState();
}
}

for(int j = 0; j < smokeMax; j++) {
if(smokeParticles[j].getState()) {
color = smokeParticles[j].getColor();
double sx =
smokeParticles[j].getPoint().getX();
double sy =
smokeParticles[j].getPoint().getY();
drawOffScreen.setColor(color);
drawOffScreen.fillOval(
(int) sx, (int) sy, 2, 2);
smokeParticles[j].nextState();
}
}

for(int i = 0; i < fireworkMax; i++) {
if(fireworkParticles[i].getState()) {
replay = false;
break;
}
}

// 是否重新施放
if(replay) {
// 煙火初始位置
xCenter = appletWidth/2 +
(int)(Math.random()* 150 - 150);
yCenter = appletHeight/2 +
(int)(Math.random()* 150 - 150);

for(int i = 0; i < fireworkMax; i++) {
fireworkParticles[i].resume(
new Point(xCenter, yCenter),
fireworkMax);
fireworkParticles[i].setLife(
(int) (Math.random()*20));
}
}

// 重繪畫面
repaint();

// 暫停執行緒 150 毫秒
try {
Thread.sleep(150);
}
catch (InterruptedException e) { }
}
}
}