22

光線とボックスの交点を特定したいと考えています。ボックスはその最小 3D 座標と最大 3D 座標によって定義され、光線は原点とそれが指す方向によって定義されます。

現在、ボックスの各面に対して平面を形成しており、光線と平面を交差させています。光線が平面と交差する場合は、交点が実際にボックスの表面上にあるかどうかを確認します。そうであれば、それがこのレイの最も近い交差点であるかどうかを確認し、最も近い交差点を返します。

平面の交点がボックスの表面自体にあるかどうかを確認する方法は、関数を使用することです

bool PointOnBoxFace(R3Point point, R3Point corner1, R3Point corner2)
{
  double min_x = min(corner1.X(), corner2.X());
  double max_x = max(corner1.X(), corner2.X());
  double min_y = min(corner1.Y(), corner2.Y());
  double max_y = max(corner1.Y(), corner2.Y());
  double min_z = min(corner1.Z(), corner2.Z());
  double max_z = max(corner1.Z(), corner2.Z());
  if(point.X() >= min_x && point.X() <= max_x && 
     point.Y() >= min_y && point.Y() <= max_y &&
     point.Z() >= min_z && point.Z() <= max_z)
     return true;

  return false;
}

ここで、corner1はそのボックス面の長方形の 1 つの角であり、corner2は反対側の角です。私の実装はほとんどの場合機能しますが、時々間違った交差点が表示されます。画像をご覧ください:

代替テキスト

この画像は、カメラの目から出てボックスの表面に当たる光線を示しています。他の光線は、ボックス サーフェスの法線です。特に 1 つのレイ (実際に表示されているのは法線) がボックスの「背面」から出ているのに対し、法線はボックスの上部から出ていることがわかります。ボックスの上部に正しく当たる光線が他にも複数あるため、これは奇妙に思えます。

交点がボックス上にあるかどうかを確認する方法が正しいかどうか、または他のアルゴリズムを使用する必要があるかどうか疑問に思っていました。

ありがとう。

4

5 に答える 5

17

イプシロンで物事を増やすことは、実際にはこれを行うための優れた方法ではありません。これは、光線が通過できるボックスの端にイプシロンのサイズの境界線があるためです。したがって、この (比較的一般的な) 奇妙なエラー セットを取り除くと、別の (よりまれな) 奇妙なエラー セットが発生します。

光線がそのベクトルに沿ってある速度で移動し、各平面と交差する時間を見つけていることをすでに想像していると思います。したがって、たとえば、平面と で交差していてx=x0、光線が(rx,ry,rz)からの方向に進んでいる(0,0,0)場合、交差の時間は ですt = x0/rx。が負の場合tは無視してください。逆の方向に進んでいます。がゼロの場合t、その特殊なケースをどのように処理するかを決定する必要があります。すでに飛行機に乗っている場合、飛行機で跳ね返りますか、それとも通り抜けますか? また、特別なケースとして処理することもrx==0できます (ボックスの端にヒットできるようにするため)。

とにかく、これで飛行機に衝突した正確な座標がわかりました(t*rx , t*ry , t*rz)t*ryこれで、 と が必要な長方形内にあるかどうかを読み取ることができますt*rz(つまり、これらの軸に沿った立方体の最小値と最大値の間)。 x 座標はテストしません。これは、ヒットしたことを既に知っているためです。 ここでも、特殊なケースとしてコーナーへのヒットを処理するかどうか/どのように処理するかを決定する必要があります。さらに、さまざまなサーフェスとの衝突を時間で並べ替えて、最初のサーフェスを衝突ポイントとして選択できるようになりました。

これにより、任意のイプシロン係数に頼ることなく、光線がキューブと交差するかどうか、およびどこで交差するかを、浮動小数点演算で可能な精度で計算できます。

したがって、すでに持っているような 3 つの関数yzが必要xです。xzxyyz


編集:(詳細に)追加されたコードは、軸ごとに異なるテストを行う方法を示しています:

#define X_FACE 0
#define Y_FACE 1
#define Z_FACE 2
#define MAX_FACE 4

// true if we hit a box face, false otherwise
bool hit_face(double uhit,double vhit,
                 double umin,double umax,double vmin,double vmax)
{
  return (umin <= uhit && uhit <= umax && vmin <= vhit && vhit <= vmax);
}

// 0.0 if we missed, the time of impact otherwise
double hit_box(double rx,double ry, double rz,
                double min_x,double min_y,double min_z,
                double max_x,double max_y,double max_z)
{
  double times[6];
  bool hits[6];
  int faces[6];
  double t;
  if (rx==0) { times[0] = times[1] = 0.0; }
  else {
    t = min_x/rx;
    times[0] = t; faces[0] = X_FACE; 
    hits[0] = hit_box(t*ry , t*rz , min_y , max_y , min_z , max_z);
    t = max_x/rx;
    times[1] = t; faces[1] = X_FACE + MAX_FACE;
    hits[1] = hit_box(t*ry , t*rz , min_y , max_y , min_z , max_z);
  }
  if (ry==0) { times[2] = times[3] = 0.0; }
  else {
    t = min_y/ry;
    times[2] = t; faces[2] = Y_FACE;
    hits[2] = hit_box(t*rx , t*rz , min_x , max_x , min_z , max_z);
    t = max_y/ry;
    times[3] = t; faces[3] = Y_FACE + MAX_FACE;
    hits[3] = hit_box(t*rx , t*rz , min_x , max_x , min_z , max_z);
  }
  if (rz==0) { times[4] = times[5] = 0.0; }
  else {
    t = min_z/rz;
    times[4] = t; faces[4] = Z_FACE;
    hits[4] = hit_box(t*rx , t*ry , min_x , max_x , min_y , max_y);
    t = max_z/rz;
    times[5] = t; faces[5] = Z_FACE + MAX_FACE;
    hits[5] = hit_box(t*rx , t*ry , min_x , max_x , min_y , max_y);
  }
  int first = 6;
  t = 0.0;
  for (int i=0 ; i<6 ; i++) {
    if (times[i] > 0.0 && (times[i]<t || t==0.0)) {
      first = i;
      t = times[i];
    }
  }
  if (first>5) return 0.0;  // Found nothing
  else return times[first];  // Probably want hits[first] and faces[first] also....
}

(私はこれを入力しただけで、コンパイルしていないので、バグに注意してください。)(編集:i->を修正しただけfirstです。)

とにかく、ポイントは、3 つの方向を別々に扱い、衝撃が (u,v) 座標の右側のボックス内で発生したかどうかをテストすることです。ここで、(u,v) は (x,y)、(x) のいずれかです。 ,z) または (y,z) は、ヒットする平面に応じて異なります。

于 2010-04-01T23:24:17.963 に答える
2

PointOnBoxFace三次元ではなく二次元のチェックを行う必要があります。たとえば、平面に対してテストしている場合は、とをそれぞれの境界z = z_minと比較するだけで済みます。座標が正しいことはすでにわかっています。3 番目の座標を「再確認」すると、浮動小数点の精度につまずく可能性があります。xyz

たとえば、z_minが 1.0 の場合、最初にその平面に対してテストします。x( , y, 0.999999999)の交点を見つけます。xyが範囲内にあるとしても、zは正しくありません。

于 2010-04-01T23:57:35.883 に答える
0

コードは問題ないようです。この特定のレイを見つけてデバッグしてみてください。

于 2010-04-01T21:47:30.160 に答える
0

編集:この回答は無視してください(以下のコメントを参照してください。ここでは、私のやり方の誤りが非常に説得力を持って示されています)。

ポイントがボリュームの内側にあるかどうかをテストしていますが、ポイントはそのボリュームの周辺にあるため、ボリュームの外側の「無限小」距離であることがわかる場合があります。いくつかの小さなイプシロンでボックスを拡大してみてください。

double epsilon = 1e-10; // Depends the scale of things in your code.
double min_x = min(corner1.X(), corner2.X()) - epsilon;
double max_x = max(corner1.X(), corner2.X()) + epsilon;
double min_y = min(corner1.Y(), corner2.Y()) - epsilon;
...

技術的には、ほぼ等しい数値を比較する正しい方法は、ビット表現を int にキャストし、整数をいくつかの小さなオフセットで比較することです。たとえば、C では次のようになります。

#define EPSILON 10 /* some small int; tune to suit */
int almostequal(double a, double b) {
    return llabs(*(long long*)&a - *(long long*)&b) < EPSILON;
}

もちろん、これは C# ではそれほど簡単ではありませんが、安全でないセマンティクスでも同じ効果が得られる可能性があります。(@Novox のコメントに感謝します。これにより、この手法を詳細に説明する素晴らしいページにたどり着きました。)

于 2010-04-01T21:49:41.543 に答える
0

その光線がボックスの端を正確に通過することになるのでしょうか? 浮動小数点の丸め誤差により、右面と背面の両方で見落とされる可能性があります。

于 2010-04-01T21:50:47.307 に答える