Intersection of line segment with axis-aligned box in C#

后端 未结 3 1360
梦毁少年i
梦毁少年i 2020-12-17 02:16

I\'m looking for an algorithm that determines the near and far intersection points between a line segment and an axis-aligned box.

Here is my method definition:

相关标签:
3条回答
  • 2020-12-17 02:46

    Here is another highly-efficient and elegant solution.

    It's written in C++ but is trivially translatable to Python (per links to this thread from other SO questions) or C# (per original PO). It assumes an access to a 3D vector class/struct, here Vector3f, with basic algebraic operations/operators defined. In C++, something like Eigen::Vector3f can be used; in Python, a simple NumPy array can be used; and in C# the Vector3 can probably be used.

    The routine is optimized to check for intersections with multiple grid-aligned boxes.

    Definition of the segment class with simple endpoint-based constructor:

    class Segment {
    public:
        Segment(const Vector3f& startPoint, const Vector3f& endPoint) :
                origin(startPoint), direction(endPoint - startPoint),
                inverseDirection(Vector3f(1.0) / direction),
                sign{(inverseDirection.x < 0),(inverseDirection.y < 0),(inverseDirection.z < 0)}
                {}
    
        float length(){
            return sqrtf(direction.x * direction.x + direction.y * direction.y +
                         direction.z * direction.z);
        }
        Vector3f origin, endpoint, direction;
        Vector3f inverseDirection;
        int sign[3];
    };
    

    Actual routine that performs the check:

    bool SegmentIntersectsGridAlignedBox3D(Segment segment, Vector3f boxMin, Vector3f boxMax){
        float tmin, tmax, tymin, tymax, tzmin, tzmax;
        Vector3f bounds[] = {boxMin, boxMax};
    
        tmin = (bounds[segment.sign[0]].x - segment.origin.x) * segment.inverseDirection.x;
        tmax = (bounds[1 - segment.sign[0]].x - segment.origin.x) * segment.inverseDirection.x;
        tymin = (bounds[segment.sign[1]].y - segment.origin.y) * segment.inverseDirection.y;
        tymax = (bounds[1 - segment.sign[1]].y - segment.origin.y) * segment.inverseDirection.y;
    
        if ((tmin > tymax) || (tymin > tmax)){
            return false;
        }
        if (tymin > tmin) {
            tmin = tymin;
        }
        if (tymax < tmax){
            tmax = tymax;
        }
    
        tzmin = (bounds[segment.sign[2]].z - segment.origin.z) * segment.inverseDirection.z;
        tzmax = (bounds[1 - segment.sign[2]].z - segment.origin.z) * segment.inverseDirection.z;
    
        if ((tmin > tzmax) || (tzmin > tmax)){
            return false;
        }
        if (tzmin > tmin){
            tmin = tzmin;
        }
        if (tzmax < tmax){
            tmax = tzmax;
        }
        // this last check is different from the 'ray' case in below references: 
        // we need to check that the segment is on the span of the line
        // that intersects the box
        return !(tmax < 0.0f || tmin > 1.0f); 
    }
    

    Credit for this answer mostly goes to scratchpixel.com and the author of this tutorial, which is based on:

    Williams, Amy, Steve Barrus, R. Keith Morley, and Peter Shirley. "An efficient and robust ray-box intersection algorithm." Journal of graphics tools 10, no. 1 (2005): 49-54 link

    You can find a very detailed explanation of the code in this tutorial.

    All I did was slightly modify the code to address the segment-along-ray rather than ray intersection problem.

    0 讨论(0)
  • 2020-12-17 02:47

    Here's what I ended up using:

    public static List<Point3D> IntersectionOfLineSegmentWithAxisAlignedBox(
        Point3D segmentBegin, Point3D segmentEnd, Point3D boxCenter, Size3D boxSize)
    {
        var beginToEnd = segmentEnd - segmentBegin;
        var minToMax = new Vector3D(boxSize.X, boxSize.Y, boxSize.Z);
        var min = boxCenter - minToMax / 2;
        var max = boxCenter + minToMax / 2;
        var beginToMin = min - segmentBegin;
        var beginToMax = max - segmentBegin;
        var tNear = double.MinValue;
        var tFar = double.MaxValue;
        var intersections = new List<Point3D>();
        foreach (Axis axis in Enum.GetValues(typeof(Axis)))
        {
            if (beginToEnd.GetCoordinate(axis) == 0) // parallel
            {
                if (beginToMin.GetCoordinate(axis) > 0 || beginToMax.GetCoordinate(axis) < 0)
                    return intersections; // segment is not between planes
            }
            else
            {
                var t1 = beginToMin.GetCoordinate(axis) / beginToEnd.GetCoordinate(axis);
                var t2 = beginToMax.GetCoordinate(axis) / beginToEnd.GetCoordinate(axis);
                var tMin = Math.Min(t1, t2);
                var tMax = Math.Max(t1, t2);
                if (tMin > tNear) tNear = tMin;
                if (tMax < tFar) tFar = tMax;
                if (tNear > tFar || tFar < 0) return intersections;
    
            }
        }
        if (tNear >= 0 && tNear <= 1) intersections.Add(segmentBegin + beginToEnd * tNear);
        if (tFar >= 0 && tFar <= 1) intersections.Add(segmentBegin + beginToEnd * tFar);
        return intersections;
    }
    

    public enum Axis
    {
        X,
        Y,
        Z
    }
    

    public static double GetCoordinate(this Point3D point, Axis axis)
    {
        switch (axis)
        {
            case Axis.X:
                return point.X;
            case Axis.Y:
                return point.Y;
            case Axis.Z:
                return point.Z;
            default:
                throw new ArgumentException();
        }
    }
    
    public static double GetCoordinate(this Vector3D vector, Axis axis)
    {
        switch (axis)
        {
            case Axis.X:
                return vector.X;
            case Axis.Y:
                return vector.Y;
            case Axis.Z:
                return vector.Z;
            default:
                throw new ArgumentException();
        }
    }
    
    0 讨论(0)
  • 2020-12-17 03:02

    Well, for an axis-aligned box it's pretty simple: you have to find intersection of your ray with 6 planes (defined by the box faces) and then check the points you found against the box vertices coordinates limits.

    0 讨论(0)
提交回复
热议问题