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

コードが何をしているのか、何が起こっているのかを理解するために、コードをいくつか簡単にリファクタリングしました。

最初に一つ言っておきますが、これはスパゲッティモンスターの復活ですよね?グローバル変数は好きですか?長い、超強力、今すぐすべてやりましょう、そしてここで方法は?

変数をできるだけ遅く導入すると、コードの読者は、この変数が以前にあったものを上方に検索する必要がなくなります。

変数が変わらない場合: それらを最終的なものにします。それはそれらについての推論を単純化します。final int killRadius = 200;つまり、値とともに型情報を取得し、できれば最初の使用の直前に取得し、決して変更されないことを意味します。ソースコードのみの構成。おそらくあまり複雑な候補ではありません。double 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 を初期化することはできません。

大きな時間の終わりには、2 つの同様のブロックがあります。

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

ただし、xp、xc、および dx はその間変更されません。また、y に相当するものも変更されません。これは間違いですか、それともなぜ再度割り当てるのですか?

次に、この方法で無限の while を取り除くことができます: 両方の条件が while を終了するので、条件を while に入れます - テストし、最初のブロックが入力されていない場合にのみ (割り当てを繰り返さずに) 2 番目のブロックを呼び出します - キーワードそうすることは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;
        }
    }   

エラーを見つけるのに何が役立ちますか? それほどでもない。2 つの円が間違って配置され、残りは問題ない場合、スクリーンショットは問題なく、値は悪い円につながり、値は問題ありません。

于 2012-06-14T01:10:37.553 に答える
0

私はあなたのコードをデバッグできるとは思いませんが、何年も前(10年以上前)にjava.awt.Shapeベースの衝突ルーチンを作成しました...これからShapeUtils.javaをチェックしてください:

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

発生する可能性が非常に高い問題の1つは、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) {

この精度とシミュレートされたオブジェクトの速度は、シミュレーションで衝突をチェックする必要がある頻度(最大デルタ時間)を教えてくれました...高速で移動するシミュレートされたオブジェクトは、単一の時間間隔でソリッドオブジェクトを直接圧縮できます。時間間隔を、希望の精度で移動するのにかかる時間よりも短くするようにスケーリングしないでください。

基本的にこのようにして、計算コストと結果の精度の間の直接的なトレードオフになりました。私の場合、ゲームの見栄えを良くしたかったので、オブジェクトが0.5ピクセル以上重ならないように正確にする必要がありました。

編集:私の提案を要約しましょう...再読すると、私は詳細に迷い、重要な概念を指摘することができませんでした... Javaには、Shapeのさまざまな実装をテストするために非常にうまく機能するルーチンがたくさんあります(円を含む)。私の提案は、Javaライブラリを可能な限り使用し、正しいことを確認できる結果を取得することです。動作するコードとコードが機能することを確認する方法がわかったら、より高速にする必要がある場合は、できれば実行中のプログラム(正しい結果を生成する)をプロファイリングしてパフォーマンスを制限している部分を確認した後、コードの一部の最適化を開始します。

于 2012-06-13T20:21:55.197 に答える