3

3D空間に2D凸多角形と多角形の面積を測定する関数があります。

public double area() {
    if (vertices.size() >= 3) {
        double area = 0;
        Vector3 origin = vertices.get(0);
        Vector3 prev = vertices.get(1).clone();
        prev.sub(origin);
        for (int i = 2; i < vertices.size(); i++) {
            Vector3 current = vertices.get(i).clone();
            current.sub(origin);
            Vector3 cross = prev.cross(current);
            area += cross.magnitude();
            prev = current;
        }
        area /= 2;
        return area;
    } else {
        return 0;
    }
}

この方法がポリゴンのすべての方向で機能することをテストするために、プログラムで反復ごとに少し回転させて、面積を計算しました。そのようです...

Face f = poly.getFaces().get(0);
        for (int i = 0; i < f.size(); i++) {
            Vector3 v = f.getVertex(i);
            v.rotate(0.1f, 0.2f, 0.3f);
        }
        if (blah % 1000 == 0)
            System.out.println(blah + ":\t" + f.area());

20x20の正方形でテストする場合、私の方法は正しいようです。ただし、rotateメソッド(Vector3クラスのメソッド)は、ポリゴン内の各頂点の位置にエラーを導入し、面積の計算に影響を与えるようです。これがVector3.rotate()メソッドです

public void rotate(double xAngle, double yAngle, double zAngle) {
    double oldY = y;
    double oldZ = z;
    y = oldY * Math.cos(xAngle) - oldZ * Math.sin(xAngle);
    z = oldY * Math.sin(xAngle) + oldZ * Math.cos(xAngle);

    oldZ = z;
    double oldX = x;
    z = oldZ * Math.cos(yAngle) - oldX * Math.sin(yAngle);
    x = oldZ * Math.sin(yAngle) + oldX * Math.cos(yAngle);

    oldX = x;
    oldY = y;
    x = oldX * Math.cos(zAngle) - oldY * Math.sin(zAngle);
    y = oldX * Math.sin(zAngle) + oldY * Math.cos(zAngle);
}

これが私のプログラムの「反復:領域」の形式での出力です。

0:  400.0
1000:   399.9999999999981
2000:   399.99999999999744
3000:   399.9999999999959
4000:   399.9999999999924
5000:   399.9999999999912
6000:   399.99999999999187
7000:   399.9999999999892
8000:   399.9999999999868
9000:   399.99999999998664
10000:  399.99999999998386
11000:  399.99999999998283
12000:  399.99999999998215
13000:  399.9999999999805
14000:  399.99999999998016
15000:  399.99999999997897
16000:  399.9999999999782
17000:  399.99999999997715
18000:  399.99999999997726
19000:  399.9999999999769
20000:  399.99999999997584

これは最終的には物理エンジンを対象としているため、Vector3.rotate()メソッドが非常に定期的に使用されるため、累積エラーを最小限に抑える方法を知りたいと思います。

ありがとう!

いくつかの奇妙なメモ:

  • 誤差は回転量に比例します。すなわち。反復ごとの回転数が大きい->反復ごとのエラーが大きい。

  • 回転関数にdoubleを渡す場合は、floatを渡す場合よりもエラーが多くなります。

4

2 に答える 2

9

浮動小数点の三角関数操作を繰り返すと、常に累積エラーが発生します。これがまさにその動作方法です。これに対処するには、基本的に2つのオプションがあります。

  1. 無視してください。あなたの例では、20,000回の反復(!)の後でも、面積は小数点以下13桁まで正確であることに注意してください。doublesは、最初に小数点以下16桁しか格納できないことを考えると、これは悪いことではありません。

    実際、グラフをプロットすると、正方形の面積はほぼ直線的に減少しているように見えます。これは、近似回転行列有効な行列式が約1 −3.417825× 10-18であり、通常の範囲内であると
    プロット
    仮定すると、理にかなっています。 1の倍精度浮動小数点エラー範囲。その場合、正方形の面積はゼロに向かって非常にゆっくりと指数関数的に減衰し続けるため、20億(2×10 9面積を399まで下げるための7.3× 1014回の反復。1秒あたり100回の反復を想定すると、約7ヶ月半23万年。

    編集:私が最初にその面積が399に達するのにかかる時間を計算したとき、私は間違いを犯したようで、どういうわけか崩壊率を約40万倍過大評価することができました(!)。上記の間違いを修正しました。

  2. それでも累積エラーが必要ないと思われる場合、答えは簡単です。浮動小数点の回転を繰り返さないでください。代わりに、オブジェクトに現在の方向をメンバー変数に格納させ、その情報を使用して、オブジェクトを常に元の方向から現在の方向に回転させます。

    角度を保存するだけなので、これは2Dでは簡単です。3Dでは、クォータニオンまたはマトリックスのいずれかを保存し、場合によっては、ノルム/行列式がほぼ1になるように再スケーリングすることをお勧めします(マトリックスを使用して剛体の方向を表す場合は、そのままにします)ほぼ直交)。

    もちろん、このアプローチではオブジェクトの向きの累積エラーを排除することはできませんが、再スケーリングにより、オブジェクトのボリューム、領域、および/または形状が影響を受けないことが保証されます。

于 2012-04-08T11:28:17.450 に答える
1

累積エラーがあるとおっしゃっていますが、エラーが発生するとは思われません(出力が常に低下するとは限らないことに注意してください)。残りのエラーは、浮動小数点数の丸めと精度の低下が原因です。

私は大学で2D物理エンジン(Java)で作業しましたが、doubleの方が正確であることがわかりました(もちろん、oraclesのデータ型のサイズを参照してください)。

つまり、この動作を取り除くことはできません。精度の制限を受け入れる必要があります。

編集:

今、私はあなたの.area関数を見て、おそらくいくつかの累積的なものがあります

+= cross.magnitude

しかし、私は関数全体が少し奇妙に見えると言わなければなりません。現在の面積を計算するために、なぜ以前の頂点を知る必要があるのですか?

于 2012-04-08T10:26:44.867 に答える