圆圈碰撞

时间:2012-06-13 19:39:51

标签: java graphics collision-detection

我正在尝试编写一个Java程序来支持非晶格扩散有限的聚合模拟。模拟运动粒子的基本代码就位,直到所述粒子击中静态中心粒子。在那一刻,我试图确保移动的粒子只是接触(切向)静态粒子。然而,由于未知原因,它有时会失败(8个粒子的前2个相交,其他6个很好)。

以下是代码:

    boolean killed, collide;
    double xp, yp, dx, dy, theta, xpp, ypp, length;

    int xc = 200;
    int yc = 200;
    int killRadius = 200;
    int releaseRadius = 150;
    int partRadius = 14;
    int partDiam = 2 * partRadius;

    drawCircle(xc, yc, killRadius); // kill
    drawCircle(xc, yc, releaseRadius); // release
    drawCircle(xc, yc, partRadius); // center particle

    //while (true) {
        killed = false;
        collide = false;

        theta = Math.random() * Math.PI * 2;
        xp = xc + releaseRadius * Math.cos(theta);
        yp = yc + releaseRadius * Math.sin(theta);

        while (true) {

            theta = Math.random() * Math.PI * 2;
            length = partDiam;

            xpp = xp;
            ypp = yp;

            xp = xp + length * Math.cos(theta);
            yp = yp + length * Math.sin(theta);

            //drawCircle((int) xp, (int) yp, partRadius);

            // Should it be killed ? (maybe could use a box to fasten
            // computations...
            // Would switching the test for kill w test for collision
            // improve perf ?
            dx = xp - xc;
            dy = yp - yc;
            if ((dx * dx) + (dy * dy) > killRadius * killRadius) {
                killed = true;
                break;
            }

            // Does it collide with center? replace by any particle...
            dx = xp - xc;
            dy = yp - yc;
            if ((dx * dx) + (dy * dy) < (partDiam) * (partDiam)) {
                collide = true;
                break;
            }
        }
// Probably something is wrong here...
        if (collide) {
            // no absolute value because particles move at most by diameter
            double depthPenetration = partDiam
                    - Math.sqrt((dx * dx) + (dy * dy));
            dx = xpp - xp;
            dy = ypp - yp;
            // shorten distance travelled by penetration length to ensure
            // that
            // particle is tangeant
            length = Math.sqrt((dx * dx) + (dy * dy)) - depthPenetration;
            xp = xpp + length * Math.cos(theta);
            yp = ypp + length * Math.sin(theta);
            drawCircle((int) xp, (int) yp, partRadius);
        }
    //}

当然我在询问之前检查了很多参考文献,但是找不到代码有什么问题...帮助将不胜感激。

2 个答案:

答案 0 :(得分:1)

我通过你的代码做了一些简单的重构,只是为了感受它做了什么,发生了什么。

让我在开始时提一件事:这是sphaghetti-monster的复兴,不是吗?你喜欢全局变量吗?很长,超级强大的让我们现在就做好这件事有条件的吗?

如果你尽可能晚地引入变量,你的代码的读者不需要向上搜索,这个变量以前是什么 - 如果某些内容被覆盖,例如,如果有重复使用的话。

如果您的变量没有改变:让它们成为最终的。这简化了对它们的推理。 final int killRadius = 200;表示获取类型信息以及值,并希望在第一次使用之前不久,并且它永远不会被更改。仅在源代码中配置。可能不是一个太复杂的候选人。与双dx相反 - 未初始化,因为它在循环内初始化,

static void foo () {

    final int xc = 200;
    final int yc = 200;
    final int killRadius = 200;
    final int releaseRadius = 150;
    final int partRadius = 14;

    drawCircle (xc, yc, killRadius); // kill
    drawCircle (xc, yc, releaseRadius); // release
    drawCircle (xc, yc, partRadius); // center particle

    //while (true) {
    boolean killed = false;
    boolean collide = false;

    double theta = Math.random() * Math.PI * 2;     
    double xp = xc + releaseRadius * Math.cos (theta);
    double yp = yc + releaseRadius * Math.sin (theta);
    double dx, dy, xpp, ypp;

    while (true) {

        theta = Math.random () * Math.PI * 2;
        final int partDiam = 2 * partRadius;
        final double length = partDiam;

        xpp = xp;
        ypp = yp;

        xp += length * Math.cos (theta);
        yp += length * Math.sin (theta);

        dx = xp - xc;
        dy = yp - yc;
        if ((dx * dx) + (dy * dy) > killRadius * killRadius) {
            killed = true;
            break;
        }

        // Why again assign dx = xp -xc? Did any of these values change meanwhile? 
        // I don't think so.
        // dx = xp - xc;
        // dy = yp - yc;
        if ((dx * dx) + (dy * dy) < (partDiam) * (partDiam)) {
            collide = true;
            break;
        }
    }
    if (collide) {
        // no absolute value because particles move at most by diameter
        double depthPenetration = partDiam - Math.sqrt((dx * dx) + (dy * dy));
        dx = xpp - xp;
        dy = ypp - yp;
        // shorten distance travelled by penetration length to ensure
        // that
        // particle is tangeant
        final double length = Math.sqrt((dx * dx) + (dy * dy)) - depthPenetration;
        xp = xpp + length * Math.cos (theta);
        yp = ypp + length * Math.sin (theta);
        drawCircle ((int) xp, (int) yp, partRadius);
    }

如果你像这样构造你的代码,你不仅可以看到,像xc这样的某个值是200并且永远不会改变 - 在while循环的开头你会看到theta没有在循环内声明,所以要么使用它稍后在循环之外,或者在循环内顺序修改它。做一个x + = 4;你不能在循环传递中初始化x。

在最重要的时候,你有两个相似的块:

    dx = xp - xc;
    dy = yp - yc;
    if ((dx * dx) + (dy * dy) (OP) a OP b) {
        c = true;
        break;
    }

但xp,xc和dx不会同时改变 - 也不是y等价物。这是一个错误,或者为什么要再次分配它们?

然后,你可以这样摆脱你的无尽:由于两个条件终止了while,将条件放入while - test,并调用第二个块(不重复赋值)只有当第一个不是输入 - 关键字是else

    while (!killed && !collide) {

        // ...

        dx = xp - xc;
        dy = yp - yc;
        if ((dx * dx) + (dy * dy) > killRadius * killRadius) {
            killed = true;
        }
        else if ((dx * dx) + (dy * dy) < (partDiam) * (partDiam)) {
            collide = true;
        }
    }   

找到错误有什么帮助?没那么多。如果错误地放置了两个圆圈,其余的都很好,那么屏幕截图就可以了,导致坏圆圈的值和正常的值。

答案 1 :(得分:0)

我认为我不能调试你的代码,但是我在很多年前(十多年前)编写了一些基于java.awt.Shape的碰撞例程,这可能会有所帮助......从这里查看ShapeUtils.java:

http://www.cs101.org/psets/breakout/solns/breakout-src.jar

您可能遇到的一个非常可能的问题是java圆是具有4个点的样条曲线,因此如果您检查定义圆的所有点是否在另一个形状内,您仍将错过样条曲线的情况凸出到另一个形状,但没有一个点实际上在对象内。我的解决方案(对你的情况来说可能不够好)是重新制作形状,直到它们各自边缘点之间的距离小于最大可接受误差:

   /**
     * Convert any AWT shape into a shape with a specified precision.
     * The specified precision indicates the maximum tolerable distance
     * between knot points. If the shape is already precise enough then 
     * it is returned unmodified.
     *
     * @param aShape the shape to be converted if necessary.
     * @param precision the maximum tolerable distance between knot points.
     * @return A more precise version of the shape, or the same shape if
     *         the precision is already satisfied.
     */
    public static Shape getPreciseShape(Shape aShape, float precision) {

这个精度以及模拟物体的速度告诉我在模拟中我必须检查碰撞的频率(最大增量时间)......快速移动的模拟物体可以在一次性中直接穿过固体物体间隔,如果您不将时间间隔缩放到小于所需精度所需的时间。

基本上这样就成了计算成本和结果精度之间的直接权衡。在我的情况下,我只是希望游戏看起来很好,所以我只需要精确到确保对象永远不会重叠超过半个像素左右。

编辑:让我总结一下我的建议......在重新阅读中我意识到我迷失在细节中并且从未设法指出关键概念...... Java有很多例程非常适合测试Shape的各种实现(包括圆圈)。我的建议是尽可能使用java库,并获得可以验证的结果是正确的。一旦你有了检查代码工作的代码和方法,如果你需要更快的速度,开始优化它的一部分,最好在分析正在运行的程序(产生正确的结果)之后,看看哪些部分限制了你的性能。