You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

P5中射线与PShape类型Box前后面相交检测失败求助

射线与PShape盒子前后面相交检测失败问题

我实现了一个检测射线与PShape类型Box相交的getIntersection函数,目前能正常检测盒子的左、右、上、下面相交,但无法检测前后面(Front and Back faces)的相交情况。已经定位到问题出在frontIntersection和backIntersection的计算逻辑里,但还没找到解决办法。相关代码如下:

public PVector getIntersection(PVector rayOrigin, PVector rayDirection, PShape shape) {

    if (shape.getKind() == PShape.BOX) {
        // Get the dimensions of the box
        float x = shape.getWidth();
        float y = shape.getHeight();
        float z = shape.getDepth();

        PVector frontIntersection = intersect(rayOrigin, rayDirection, new PVector(-x/2, -y/2, -z/2), new PVector(x/2, -y/2, -z/2));

        PVector backIntersection = intersect(rayOrigin, rayDirection, new PVector(-x/2, -y/2, z/2), new PVector(x/2, -y/2, z/2));

        // Calculate left intersection
        PVector leftIntersection = intersect(rayOrigin, rayDirection, new PVector(-x/2, -y/2, -z/2), new PVector(-x/2, y/2, -z/2));

        // Calculate right intersection
        PVector rightIntersection = intersect(rayOrigin, rayDirection, new PVector(x/2, -y/2, -z/2), new PVector(x/2, y/2, -z/2));

        // Calculate top intersection
        PVector topIntersection = intersect(rayOrigin, rayDirection, new PVector(-x/2, y/2, -z/2), new PVector(x/2, y/2, -z/2));

        // Calculate bottom intersection
        PVector bottomIntersection = intersect(rayOrigin, rayDirection, new PVector(-x/2, -y/2, -z/2), new PVector(x/2, -y/2, z/2));


        if ( backIntersection != null && pointTouchesBox(backIntersection, shape) ) {
            test = backIntersection ;
        }


        // Check for intersections and return the closest one
        if (frontIntersection != null && pointTouchesBox(frontIntersection, shape) ) {
            println("return front");
            return frontIntersection;
        } else if (backIntersection != null && pointTouchesBox(backIntersection, shape) ) {
            println("return back");
            return backIntersection;
        } else if (leftIntersection != null && pointTouchesBox(leftIntersection, shape)  ) {
            return leftIntersection;
        } else if (rightIntersection != null && pointTouchesBox(rightIntersection, shape) ) {
            return rightIntersection;
        } else if (topIntersection != null && pointTouchesBox(topIntersection, shape) ) {
            return topIntersection;
        } else if (bottomIntersection != null && pointTouchesBox(bottomIntersection, shape) ) {
            return bottomIntersection;
        } else {
            // If no intersection was found, return null
            return null;
        }

    } else {
        // For other shape types, use the original getIntersection function
        for (int i = 0; i < shape.getChildCount(); i++) {
            PVector intersection = getIntersection(rayOrigin, rayDirection, shape.getChild(i));
            if (intersection != null) {
                return intersection;
            }
        }
        return null;
    }
}

public PVector intersect(PVector rayOrigin, PVector rayDirection, PVector p1, PVector p2) {
    PVector v1 = PVector.sub(rayOrigin, p1);
    PVector v2 = PVector.sub(p2, p1);
    PVector v3 = new PVector(-rayDirection.y, rayDirection.x, 0);
    float dot = PVector.dot(v2, v3);
    if (abs(dot) < 0.0001) {
        return null;
    }
    float t1 = cross(v2, v1).z / dot;
    float t2 = PVector.dot(v1, v3) / dot;
    if (t1 >= 0 && (t2 >= 0 && t2 <= 1)) {
        return PVector.add(rayOrigin, PVector.mult(rayDirection, t1));
    } else {
        return null;
    }
}

public static PVector cross(PVector v1, PVector v2) {
    float x = v1.y * v2.z - v1.z * v2.y;
    float y = v1.z * v2.x - v1.x * v2.z;
    float z = v1.x * v2.y - v1.y * v2.x;
    return new PVector(x, y, z);
}

问题原因

你的intersect函数本质是XY平面内的线段-射线相交检测:构造的v3向量(-rayDirection.y, rayDirection.x, 0)完全忽略了Z轴分量,仅适用于平行于XY平面的线段(比如左右面、上下面的线段)。而盒子前后面的线段是沿X轴延伸的(Z值固定),此时v2的Z分量为0,与v3点积后结果趋近于0,直接返回null;就算通过了判断,后续计算也只考虑XY平面的位置,无法正确验证Z轴方向的相交有效性。

解决方案

替换原有线段检测逻辑为通用平面-射线相交检测,针对每个盒子平面单独计算交点,再验证交点是否在面的边界范围内。

1. 实现平面相交检测函数

public PVector intersectPlane(PVector rayOrigin, PVector rayDirection, PVector planeNormal, float planeOffset) {
    // 平面方程:normal·(point) = offset
    float denom = PVector.dot(planeNormal, rayDirection);
    // 射线与平面平行,无交点
    if (abs(denom) < 0.0001) {
        return null;
    }
    // 计算射线参数t,t>=0表示射线前进方向的交点
    float t = (planeOffset - PVector.dot(planeNormal, rayOrigin)) / denom;
    if (t >= 0) {
        return PVector.add(rayOrigin, PVector.mult(rayDirection, t));
    }
    return null;
}

2. 修正getIntersection中的面检测逻辑

统一用平面检测处理六个面,同时验证交点是否在面的矩形范围内:

public PVector getIntersection(PVector rayOrigin, PVector rayDirection, PShape shape) {
    if (shape.getKind() == PShape.BOX) {
        float xHalf = shape.getWidth() / 2f;
        float yHalf = shape.getHeight() / 2f;
        float zHalf = shape.getDepth() / 2f;

        List<PVector> validIntersections = new ArrayList<>();

        // 前面:Z = -zHalf
        PVector front = intersectPlane(rayOrigin, rayDirection, new PVector(0,0,1), -zHalf);
        if (front != null && front.x >= -xHalf && front.x <= xHalf && front.y >= -yHalf && front.y <= yHalf) {
            validIntersections.add(front);
        }

        // 后面:Z = zHalf
        PVector back = intersectPlane(rayOrigin, rayDirection, new PVector(0,0,1), zHalf);
        if (back != null && back.x >= -xHalf && back.x <= xHalf && back.y >= -yHalf && back.y <= yHalf) {
            validIntersections.add(back);
        }

        // 左面:X = -xHalf
        PVector left = intersectPlane(rayOrigin, rayDirection, new PVector(1,0,0), -xHalf);
        if (left != null && left.y >= -yHalf && left.y <= yHalf && left.z >= -zHalf && left.z <= zHalf) {
            validIntersections.add(left);
        }

        // 右面:X = xHalf
        PVector right = intersectPlane(rayOrigin, rayDirection, new PVector(1,0,0), xHalf);
        if (right != null && right.y >= -yHalf && right.y <= yHalf && right.z >= -zHalf && right.z <= zHalf) {
            validIntersections.add(right);
        }

        // 上面:Y = yHalf
        PVector top = intersectPlane(rayOrigin, rayDirection, new PVector(0,1,0), yHalf);
        if (top != null && top.x >= -xHalf && top.x <= xHalf && top.z >= -zHalf && top.z <= zHalf) {
            validIntersections.add(top);
        }

        // 下面:Y = -yHalf
        PVector bottom = intersectPlane(rayOrigin, rayDirection, new PVector(0,1,0), -yHalf);
        if (bottom != null && bottom.x >= -xHalf && bottom.x <= xHalf && bottom.z >= -zHalf && bottom.z <= zHalf) {
            validIntersections.add(bottom);
        }

        // 返回离射线原点最近的交点
        if (validIntersections.isEmpty()) {
            return null;
        }
        PVector closest = validIntersections.get(0);
        float minDistSq = PVector.distSq(rayOrigin, closest);
        for (PVector p : validIntersections) {
            float distSq = PVector.distSq(rayOrigin, p);
            if (distSq < minDistSq) {
                minDistSq = distSq;
                closest = p;
            }
        }
        return closest;
    } else {
        // 处理其他形状类型
        for (int i = 0; i < shape.getChildCount(); i++) {
            PVector intersection = getIntersection(rayOrigin, rayDirection, shape.getChild(i));
            if (intersection != null) {
                return intersection;
            }
        }
        return null;
    }
}

3. 额外优化

  • 使用distSq替代dist计算距离,避免开方运算,提升性能
  • 收集所有有效交点后返回最近的一个,而非按顺序返回第一个,符合射线相交的实际逻辑

内容的提问来源于stack exchange,提问作者Gene Takavic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 04:07:15