1

我正在尝试编写一个 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);
        }
    //}

当然,我在询问之前检查了许多参考资料,但找不到代码有任何问题......不胜感激。

4

2 回答 2

1

I made some simple refactorings through your code, just to get a feeling what it does, what is happening.

Let me mention one thing at start: It's the revival of the sphaghetti-monster, isn't it? Do you like global variables? Long, superpotent let's do it all right now and here methoods?

If you introduce variables as late as possible, the reader of your code needn't search upwards, what this variable has been been before - if something is overridden, for example, if there is a ungood reuse.

If your variables don't change: Make them final. That simplifies reasoning about them. final int killRadius = 200; means, that get the type information, together with the value, and hopefully shortly before first usage, and that it will never get changed. Configuration only in source code. Probably not a too complicated candidate. In contrast to double dx - not initialized, because it get's initialized inside the loop,

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);
    }

If you structure your code like this, you not only see, that some value like xc is 200 and never changed - in the head of the while loop you see that theta is not declared inside the loop, so either it is used later outside the loop, or it is sequentially modified inside the loop. To do an x += 4; you can't initialize x in ever loop passing.

In the end of the big while, you have two similar blocks:

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

but xp, xc and dx aren't changed meanwhile - nor the y-equivalents. Is this a mistake, or why do you assign them again?

Then, you can get rid of your endless while this way: Since both conditions terminate the while, put the conditions into the while - test, and call the second block (without repeating the assignment) only if the first wasn't entered - keyword to do so is 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;
        }
    }   

What does it help in finding the error? Not so much. If two circles are placed wrongly, and the rest fine, a screenshot would be fine, and values, which lead to bad circles, and values which are alright.

于 2012-06-14T01:10:37.553 回答
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 库,并得到你可以验证的结果是正确的。一旦你有了工作代码和检查代码是否工作的方法,如果你需要更快的速度,开始优化它的部分,最好是在分析正在运行的程序(产生正确的结果)之后,看看哪些部分限制了你的性能。

于 2012-06-13T20:21:55.197 回答