1

Fast Graphics Gems Ray/AABB Intersection Methodを C# で再実装しようとしました。

// Based on "Fast Ray-Box Intersection" algorithm by Andrew Woo, "Graphics Gems", Academic Press, 1990
public unsafe Vector? IntersectionWith(Cuboid other) {
    const int NUM_DIMENSIONS = 3;
    Assure.Equal(NUM_DIMENSIONS, 3); // If that value is ever changed, this algorithm will need some maintenance

    const byte QUADRANT_MIN = 0;
    const byte QUADRANT_MAX = 1;
    const byte QUADRANT_BETWEEN = 2;

    // Step 1: Work out which direction from the start point to test for intersection for all 3 dimensions, and the distance
    byte* quadrants = stackalloc byte[NUM_DIMENSIONS];
    float* candidatePlanes = stackalloc float[NUM_DIMENSIONS];
    float* cuboidMinPoints = stackalloc float[NUM_DIMENSIONS];
    float* cuboidMaxPoints = stackalloc float[NUM_DIMENSIONS];
    float maxDistance = Single.NegativeInfinity;
    byte maxDistanceDimension = 0;

    bool startPointIsInsideCuboid = true;

    cuboidMinPoints[0] = other.X;
    cuboidMinPoints[1] = other.Y;
    cuboidMinPoints[2] = other.Z;
    cuboidMaxPoints[0] = other.X + other.Width;
    cuboidMaxPoints[1] = other.Y + other.Height;
    cuboidMaxPoints[2] = other.Z + other.Depth;

    for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
        if (StartPoint[i] < cuboidMinPoints[i]) {
            quadrants[i] = QUADRANT_MIN;
            candidatePlanes[i] = cuboidMinPoints[i];
            startPointIsInsideCuboid = false;
        }
        else if (StartPoint[i] > cuboidMaxPoints[i]) {
            quadrants[i] = QUADRANT_MAX;
            candidatePlanes[i] = cuboidMaxPoints[i];
            startPointIsInsideCuboid = false;
        }
        else {
            quadrants[i] = QUADRANT_BETWEEN;
        }
    }

    if (startPointIsInsideCuboid) return StartPoint;

    // Step 2: Find farthest dimension from cuboid
    for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
        // ReSharper disable once CompareOfFloatsByEqualityOperator Exact check is desired here: Anything other than 0f is usable
        if (quadrants[i] != QUADRANT_BETWEEN && Orientation[i] != 0f) {
            float thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];
            if (thisDimensionDist > maxDistance) {
                maxDistance = thisDimensionDist;
                maxDistanceDimension = i;
            }
        }
    }

    if (maxDistance < 0f) return null;

    if (maxDistance - Length > MathUtils.FlopsErrorMargin) return null;

    float* intersectionPoint = stackalloc float[NUM_DIMENSIONS];

    for (byte i = 0; i < NUM_DIMENSIONS; ++i) {
        if (maxDistanceDimension == i) {
            intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
            if (cuboidMinPoints[i] - intersectionPoint[i] > MathUtils.FlopsErrorMargin || intersectionPoint[i] - cuboidMaxPoints[i] > MathUtils.FlopsErrorMargin) return null;
        }
        else intersectionPoint[i] = candidatePlanes[i];

    }

    Vector result = new Vector(intersectionPoint[0], intersectionPoint[1], intersectionPoint[2]);
    if (!IsInfiniteLength && Vector.DistanceSquared(StartPoint, result) > Length * Length) return null;
    else return result;
}

ただし、ある程度は機能しますが、単体テストの次の部分で誤った結果が得られます。

Cuboid cuboid = new Cuboid(frontBottomLeft: new Vector(0f, 7.1f, 0f), width: 0f, height: 5f, depth: 0f);
Ray testRayC = new Ray(startPoint: new Vector(30f, 30f, 30f), orientation: new Vector(-1f, -1f, -1f));

Assert.AreEqual(
    null, 
    testRayC.IntersectionWith(cuboid)
);

nullへの呼び出しを期待してtestRayC.IntersectionWith(cuboid)いますが、代わりに を返しますVector(0, 12.1, 0)。これは光線上の点ではありません。


では、計算された点が光線上にあるかどうかの最終チェックを追加するだけの場合ですか? または (これは私が疑っていることです)、コードの転記に誤りがありましたか? ダブルチェックとトリプルチェックしましたが、明らかなものは何も見られませんでした...

4

1 に答える 1

1

コードの問題は、いつ実行するかですif (maxDistanceDimension == i) {。元のコードはif (whichPlane != i) {. データ構造はありませんが、修正は次のようになります。

        for (byte i = 0; i < NUM_DIMENSIONS; ++i)
        {
            if (maxDistanceDimension != i)
            {
                intersectionPoint[i] = StartPoint[i] + maxDistance * Orientation[i];
                if (intersectionPoint[i] < cuboidMinPoints[i] - MathUtils.FlopsErrorMargin || intersectionPoint[i] > cuboidMaxPoints[i] + MathUtils.FlopsErrorMargin)
                    return null;
            }
            else
            {
                intersectionPoint[i] = candidatePlanes[i];
            }
        }

次に、以下は元のコードにはありません。これは何のため?

        if (maxDistance - Length > MathUtils.FlopsErrorMargin)
            return null;

ヒットが光線の範囲内にあるかどうかを確認しようとしている場合、これはバグである可能性があります。Orientationあなたが正規化されていないように見えることを考えると、maxDistance必ずしも長さの単位ではありません。maxDistanceこれは元のアルゴリズムでは問題にならないかもしれませんが、他の長さに対してチェックする場合は、正規化Orientation(無次元化) する必要があります。

                thisDimensionDist = (candidatePlanes[i] - StartPoint[i]) / Orientation[i];

長さの単位があります。

ちなみに、原文では以下の点が間違っていると思います。

if(inside)  {
    coord = origin;
    return (TRUE);
}

このコードが c++ ではなく c であると仮定すると、これはcoordポインターがポインターと同じ参照を持つように設定するだけoriginであり、呼び出し元には影響しません。ただし、この問題はお使いのバージョンには当てはまりません。

また、これを見る過程で、アルゴリズムのより文字通りの c# 転写を作成しました。

public static class RayXCuboid
{
    enum HitQuadrant
    {
        Right = 0,
        Left = 1,
        Middle = 2,
    }

    const int Dimension = 3;

    [Conditional("DEBUG")]
    static void AssertValidArguments<TDoubleList>(params TDoubleList[] args) where TDoubleList : IList<double>
    {
        Debug.Assert(Dimension == 3);
        foreach (var list in args)
            Debug.Assert(list != null && list.Count == Dimension);
    }

    public static bool HitBoundingBox<TDoubleList>(TDoubleList minB, TDoubleList maxB, TDoubleList origin, TDoubleList dir, TDoubleList coord) where TDoubleList : IList<double>
    {
        AssertValidArguments(minB, maxB, origin, dir, coord);

        HitQuadrant[] quadrant = new HitQuadrant[Dimension];
        double[] maxT = new double[Dimension];
        double[] candidatePlane = new double[Dimension];

        /* Find candidate planes; this loop can be avoided if
        rays cast all from the eye(assume perpsective view) */
        bool inside = true;
        for (int i = 0; i < Dimension; i++)
            if (origin[i] < minB[i])
            {
                quadrant[i] = HitQuadrant.Left;
                candidatePlane[i] = minB[i];
                inside = false;
            }
            else if (origin[i] > maxB[i])
            {
                quadrant[i] = HitQuadrant.Right;
                candidatePlane[i] = maxB[i];
                inside = false;
            }
            else
            {
                quadrant[i] = HitQuadrant.Middle;
            }

        /* Ray origin inside bounding box */
        if (inside)
        {
            CopyTo(origin, coord);
            return true;
        }

        /* Calculate T distances to candidate planes */
        for (int i = 0; i < Dimension; i++)
            if (quadrant[i] != HitQuadrant.Middle && dir[i] != 0.0)
                maxT[i] = (candidatePlane[i] - origin[i]) / dir[i];
            else
                maxT[i] = -1.0;

        /* Get largest of the maxT's for final choice of intersection */
        int whichPlane = 0;
        for (int i = 1; i < Dimension; i++)
            if (maxT[whichPlane] < maxT[i])
                whichPlane = i;

        /* Check final candidate actually inside box */
        if (maxT[whichPlane] < 0.0)
        {
            FillWithDefault(coord);
            return false;
        }

        for (int i = 0; i < Dimension; i++)
            if (whichPlane != i)
            {
                coord[i] = origin[i] + maxT[whichPlane] * dir[i];
                if (coord[i] < minB[i] || coord[i] > maxB[i])
                {
                    FillWithDefault(coord);
                    return false;
                }
            }
            else
            {
                coord[i] = candidatePlane[i];
            }
        return true;                /* ray hits box */
    }

    static void FillWithDefault<T>(IList<T> list)
    {
        for (int i = 0; i < list.Count; i++)
            list[i] = default(T);
    }

    static void CopyTo<T>(IList<T> from, IList<T> to)
    {
        int arrayIndex = 0;
        foreach (var item in from)
            to[arrayIndex++] = item;
    }
}
于 2014-12-12T03:31:47.023 に答える