有效地绘制大量粒子

时间:2012-11-22 23:34:35

标签: java performance algorithm graphics2d

我写了一个粒子系统小程序;目前我正在创建,并分别绘制每个粒子。 (这是代码)

BufferedImage backbuffer;
Graphics2D g2d;

public void init(){
    backbuffer = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_RGB);
    g2d = backbuffer.createGraphics();
    setSize(WIDTH, HEIGHT);

    //creates the particles
    for (int i = 0; i < AMOUNTPARTICLES; i++) {
        prtl[i] = new particleO();
        prtl[i].setX(rand.nextInt(STARTX));
        prtl[i].setY(rand.nextInt(STARTY));
        prtl[i].setVel(rand.nextInt(MAXSPEED)+1);
        prtl[i].setFAngle(Math.toRadians(rand.nextInt(ANGLESPREAD)));

        }

    //other code
}



    public void update(Graphics g) {        

    g2d.setTransform(identity);

    //set background
    g2d.setPaint(BGCOLOUR);
    g2d.fillRect(0,0,getSize().width,getSize().height);
    drawp();
    paint(g);           
    }


public void drawp() {

    for (int n = 0; n < AMOUNTPARTICLES; n++) {

    if (prtl[n].getAlive()==true){
            g2d.setTransform(identity);
            g2d.translate(prtl[n].getX(), prtl[n].getY());
            g2d.setColor(prtl[n].getColor());

            g2d.fill(prtl[n].getShape());


            }
    }

}

它的表现还不错,我可以得到~40FPS的20,000粒子(虽然,我有一台不错的笔记本电脑)。但是在我添加了碰撞检测(见下文)后,这个数字急剧下降到2000以下,

public void particleUpdate(){
 for (int i = 0; i < AMOUNTPARTICLES; i++) {
        //other update code (posx, posy, angle etc etc)

          for (int j = 0; j < AMOUNTPARTICLES; j++) {

                if (i!=j && prtl[j].getAlive()==true){

                     if(hasCollided(i, j)){
                        prtl[i].setcolor(Color.BLACK);
                        prtl[j].setcolor(Color.BLACK);
     }
            }
  }

public boolean hasCollided(int prt1, int prt2){

        double dx = prtl[prt1].getX() - prtl[prt2].getX();
        double dy = prtl[prt1].getY() - prtl[prt2].getY();
        int edges =  prtl[prt1].getRadius() + prtl[prt2].getRadius();

        double distance = Math.sqrt( (dx*dx) + (dy*dy) );
        return (distance <= edges);


    }

我已经搜索了一些更好的方法来将粒子绘制到屏幕上,但这些例子要么让我困惑,要么不适用。

我正在做一大堆计算(太多)。但我想不出另一种做法,欢迎提出建议。

3 个答案:

答案 0 :(得分:5)

首先,添加类似碰撞检测的东西总会占用大量内存。但是,让我们来看看你的碰撞检测算法

public void particleUpdate(){
 for (int i = 0; i < AMOUNTPARTICLES; i++) {
        //other update code (posx, posy, angle etc etc)

          for (int j = 0; j < AMOUNTPARTICLES; j++) {

                if (i!=j && prtl[j].getAlive()==true){

                     if(hasCollided(i, j)){
                        prtl[i].setcolor(Color.BLACK);
                        prtl[j].setcolor(Color.BLACK);
                }
            }
  }

让我们假装只有2个粒子,1和2.您将按顺序检查 1,1 1,2 2,1 2,2

事实是,在这种情况下,你只需要检查1对2。如果1次点击2,则2也会达到1.因此,请更改先前测试的for循环跳过,以及相同的数字。

public void particleUpdate(){
 for (int i = 0; i < AMOUNTPARTICLES; i++) {
        //other update code (posx, posy, angle etc etc)

          for (int j = i+1; j < AMOUNTPARTICLES; j++) {

我注意到的另一件事是你做了sqrt操作,但只是比较看起来像静态数字。如果你删除它,并将其与平方数进行比较,你将获得很大的改进,尤其是你做了很多的事情。

    double distance_squared = ( (dx*dx) + (dy*dy) );
    return (distance <= edges*edges);

继续寻找这样的改进。然后你可能会仔细查看其他选项,比如使用不同的类,线程等,这些都可以改善系统。但请确保您可以首先优化代码。这是我将尝试的其他事项的列表,大致按顺序。

  1. 在我进入视野后,检查粒子i是否还活着,然后再计算其他任何东西。
  2. 快速通过这些对,甚至还要费心去检查它们是否接近。一种简单的方法是在进行sqrt操作之前先检测它们是否在x和y维度内。在做复杂的测试之前,一定要先做最便宜的测试。
  3. 查看您的代码,看看您是否真的使用了所有计算出的值,或者是否能够以某种方式减少操作次数。
  4. 也许您可以使用宽笔划定期聚类图像,然后仅细化通过初始聚类一段时间的对象,然后执行广泛的聚类算法。
  5. 你可以解决碰撞检测问题。但是,如果要执行此操作,则应该仅检查检查是否存在冲突,并在完成所有这些线程后更新视图上的对象。
  6. 研究替代架构,这可能会加速一些。

答案 1 :(得分:4)

绘制是一个复杂的过程,并且可以由于多种原因触发绘制请求,操作系统可能希望窗口更新,重绘管理器可能想要重绘,程序员可能想要重新绘制。

更新paint进程中的粒子是个坏主意。您要做的是在单独的缓冲区中的单独线程中更新粒子。当你准备好了,请求负责绘制缓冲区的组件执行重绘,传递缓冲区的新副本以重新绘制(你不想在开始更新到屏幕的缓冲区上绘画,你'最终会弄脏油漆。)

很难从您的代码中看出来,但看起来您正在使用java.awt.Applet,个人而言,我会升级到javax.swing.JApplet

我将这幅画移到java.swing.JPanel。 Swing组件提供双缓冲(以及其他缓冲策略)。此面板的唯一工作是在粒子引擎具有新帧时为屏幕绘制缓冲区。

粒子引擎负责更新所有粒子并将这些结果绘制到后备缓冲区(BufferedImage),然后将其传递给面板,面板将制作副本并安排更新。 / p>

摆动不是安全的。也就是说,您不应该从事件调度线程以外的任何线程更改UI。为此,您可能希望通过Concurrency in Swing读取解决方案,以将屏幕外缓冲区重新同步到客户端。

答案 2 :(得分:0)

你正在检查所有粒子碰撞的所有粒子,这是一个非常复杂的,n ^ 2的顺序(2,000粒子意味着4,000,000种组合,每帧)。

问题不是java而是算法。必须有更好的选择,但首先你可以减少比较;如果您有最大速度S并且世界中的时间增加T,则使用T * S获得可能与您正在考虑的粒子碰撞的粒子的最大距离D.将搜索范围减少到距离等于或小于该距离的粒子。也许更容易将搜索限制在以粒子为中心的正方形中的高度/宽度D(这将包括一些太远的粒子,但会使检查更容易)。

此外,在您的代码中,您正在检查P1与P2以及P2与P1的冲突是否相同,这是一种可以轻松避免的冗余。