使用Java2D有效地绘制数千个形状?

时间:2014-06-28 23:18:39

标签: java java-2d

我遇到了使用Java2D绘制可能数十万个矩形的问题。

起初我的所作所为是这样的:

private void render() {
   BufferStrategy bs = getBufferStrategy();
   if (bs == null) {
      createBufferStrategy(3);
      return;
   }

   Graphics g = bs.getDrawGraphics();
   //Draw Graphics here
   g.dispose();
   bs.show();
}

我在测试后意识到,当有500个以上的矩形时,这个效率非常低,所以我认为可能将图形绘制到BufferedImage然后显示BufferedImage会更有效率,所以我想出了这个。

private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

private void render() {
   BufferStrategy bs = getBufferStrategy();
   if (bs == null) {
      createBufferStrategy(3);
      return;
   }

   Graphics g = image.createGraphics();
   //Draw Graphics here
   g.dispose();

   Graphics g2 = bs.getDrawGraphics();
   g2.drawImage(image, 0, 0, width, height, null);
   g2.dispose();
   bs.show();
}

然而,这仍然和第一种方式一样效率低下,我很难想出更好的方法来做到这一点,而且我宁愿远离像OpenGL这样的东西。

在500-1000个矩形之间,它变得非常慢,而且任何高于该程序的程序都会很快冻结。

所以似乎碰撞检测似乎是主要问题,而不是Java2D渲染。这就是我处理检测的方式,它检查两个矩形是否在任何一侧接触。如果某人有更好的方法,我会很感激,因为我看到这是多么低效。

public boolean collides(Entity e) {
    Point2D upperLeftIn = new Point2D.Double(bounds.getX() + 1, bounds.getY());
    Point2D upperRightIn = new Point2D.Double(bounds.getX() + 8, bounds.getY());
    Point2D lowerLeftIn = new Point2D.Double(bounds.getX() + 1, bounds.getY() + 9);
    Point2D lowerRightIn = new Point2D.Double(bounds.getX() + 8, bounds.getY() + 9);

    Point2D upperLeftDown = new Point2D.Double(bounds.getX(), bounds.getY() + 1);
    Point2D lowerLeftUp = new Point2D.Double(bounds.getX(), bounds.getY() + 8);
    Point2D upperRightDown = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + 1);
    Point2D lowerRightUp = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + 8);

    Line2D top = new Line2D.Double(upperLeftIn, upperRightIn);
    Line2D bottom = new Line2D.Double(lowerLeftIn, lowerRightIn);
    Line2D left = new Line2D.Double(upperLeftDown, lowerLeftUp);
    Line2D right = new Line2D.Double(upperRightDown, lowerRightUp);

    if (e.bounds.intersectsLine(top)) {
        return true;
    }
    if (e.bounds.intersectsLine(bottom)) {
        return true;
    }
    if (e.bounds.intersectsLine(left)) {
        return true;
    }
    if (e.bounds.intersectsLine(right)) {
        return true;
    }

    return false;
}

1 个答案:

答案 0 :(得分:4)

虽然我不确定如何处理由于编辑问题的内容/目标完全改变的事实...

总结一下:正如评论中指出的那样(你似乎已经预料到),甚至数千个矩形的渲染也不应该是Java2D中的一个问题。在内部, 使用DirectX或OpenGL的硬件支持,你真的必须将大量的抗锯齿文本和复杂的,纹理的或渐变绘制的形状溢出到屏幕上,以便真正减慢速度。< / p>


话虽如此,碰撞检测确实不是这里的瓶颈。

据推测,您发布的方法是Entity类。大概,e.boundsRectangle2D。在这种情况下,您只需使用

测试交叉点
public boolean collides(Entity e) {
    return this.bounds.intersects(e.bounds);
}

在那里创建Line2D对象并不清楚你想要实现什么,你应该用语言来解释。 (也许在实际界限周围有某种“门槛”?)。但是你应该记住,intersectsLine方法的计算成本可能很高,至少与你实际想要做的测试相比。您应该尝试将其归结为间隔检查。


但是,即使你对collides方法进行了这种(微? - )优化,问题也可能更为普遍:从你到目前为止所写的内容来看,你必须假设你是测试每个实体与其他实体的冲突,并在类似

的循环中调用此方法
for (int i=0; i<allEntities.size(); i++)
{ 
    for (int j=i+1; j<allEntities.size(); j++)
    {
        Entity ei = allEntities.get(i);
        Entity ej = allEntities.get(j);
        if (ei.collides(ej)) handleCollision();
    }
}   

如果是这种情况,实现的优化不会对您有所帮助,因为问题出在渐近复杂性:交叉测试的数量(即,对collides - 方法的调用次数随着对象的数量增加二次。对于500个对象,您必须对该方法执行~125.000次调用。这已经很多了,很难做到每秒60次。但是对于5000个对象,你不需要1.250.000个呼叫,但是12.500.000 - 这是100倍,当然,60 FPS是不可能的。

对于这么多对象的(成对)碰撞检测有复杂的数据结构。不幸的是,这里的“复杂”通常意味着“实施起来非常复杂”。 Bounding Volume Hierarchies可能是一种有助于通过合理努力加速碰撞检测的方法。但是如果你有“很多”和“小”的对象在“小”空间, Spatial Hashing 可能是更合适的解决方案。您可以使用此关键字快速查找教程/博客条目和示例代码,一个示例位于Spatial hashing implementation for fast 2D collisions,但还有其他几个。