You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

C#求解Unity3D中z=c直线与Mesh四边形的交点问题

在Unity中寻找z=c平面内直线与Mesh四边形的交点

嘿,这个问题我之前做Unity项目时刚好碰到过!看你描述的是直线在z=c形式的平面上(应该是指平行于XY平面的固定Z值平面内的直线吧?),目标是找到它和Mesh四边形的交点,咱们一步步来解决:

核心思路

Unity里的Mesh四边形本质是由两个三角面组成的,但咱们不用拆三角面,直接利用平面相交的思路更高效:先找z=c平面和四边形的交线段,再求这条线段和你的目标直线的交点,最后验证这个点是否在四边形范围内。

具体步骤(附C#代码片段)

1. 准备工作:获取四边形的世界空间顶点

首先把Mesh的局部顶点转换为世界坐标(毕竟你的直线大概率是在世界空间里定义的):

using System.Linq; // 后面提取顶点会用到

Mesh quadMesh = yourQuadObject.GetComponent<MeshFilter>().mesh;
Transform quadTransform = yourQuadObject.transform;
Vector3[] worldVertices;

// 情况1:已知Mesh前4个顶点是四边形的四个顶点
worldVertices = new Vector3[4];
for (int i = 0; i < 4; i++)
{
    worldVertices[i] = quadTransform.TransformPoint(quadMesh.vertices[i]);
}

// 情况2:不确定顶点顺序,提取Mesh中唯一的4个顶点(适配任意四边形Mesh)
// HashSet<int> uniqueIndices = new HashSet<int>();
// foreach (int idx in quadMesh.triangles)
// {
//     uniqueIndices.Add(idx);
//     if (uniqueIndices.Count == 4) break;
// }
// worldVertices = uniqueIndices.Select(idx => quadTransform.TransformPoint(quadMesh.vertices[idx])).ToArray();

2. 快速过滤无交点情况

先用mesh.bounds或者顶点Z值范围做初步判断,如果四边形完全在z=c平面的一侧,直接返回空结果:

float targetZ = 5f; // 你的固定Z值c
float minZ = Mathf.Min(worldVertices[0].z, worldVertices[1].z, worldVertices[2].z, worldVertices[3].z);
float maxZ = Mathf.Max(worldVertices[0].z, worldVertices[1].z, worldVertices[2].z, worldVertices[3].z);

// 也可以用mesh.bounds做更粗略的判断:if (!quadMesh.bounds.Contains(new Vector3(0,0,targetZ)))
if (targetZ < minZ || targetZ > maxZ)
{
    Debug.Log("无交点:四边形不在z=c平面的范围内");
    return null;
}

3. 计算z=c平面与四边形四条边的交点

遍历四边形的四条边,找出和z=c平面相交的边,通过线性插值计算交点(这些交点会组成一条线段):

List<Vector3> planeIntersections = new List<Vector3>();
// 定义四边形的四条边(顶点顺序0-1,1-2,2-3,3-0)
int[,] edges = new int[,] { {0,1}, {1,2}, {2,3}, {3,0} };

for (int i = 0; i < 4; i++)
{
    Vector3 v1 = worldVertices[edges[i,0]];
    Vector3 v2 = worldVertices[edges[i,1]];
    
    // 判断这条边是否跨z=c平面(包含顶点刚好在z=c上的情况)
    if ((v1.z - targetZ) * (v2.z - targetZ) <= 0)
    {
        // 避免除以0(两个顶点都在z=c上的情况)
        if (Mathf.Abs(v2.z - v1.z) < 0.001f)
        {
            planeIntersections.Add(v1);
            continue;
        }
        // 线性插值计算交点
        float t = (targetZ - v1.z) / (v2.z - v1.z);
        Vector3 intersection = Vector3.Lerp(v1, v2, t);
        planeIntersections.Add(intersection);
    }
}

// 去重(防止两个相邻顶点都在z=c上导致重复点)
planeIntersections = planeIntersections.Distinct(new Vector3Comparer()).ToList();
// 辅助类:用于Vector3去重
public class Vector3Comparer : IEqualityComparer<Vector3>
{
    public bool Equals(Vector3 a, Vector3 b) => Vector3.Distance(a, b) < 0.001f;
    public int GetHashCode(Vector3 v) => v.x.GetHashCode() ^ v.y.GetHashCode() ^ v.z.GetHashCode();
}

4. 求目标直线与交线段的交点

把目标直线和交线段都转换为XY平面的二维坐标(因为Z值固定为c),用二维直线相交算法计算交点:

// 假设你的目标直线由两个点lineP1、lineP2定义(Z值都是targetZ)
Vector2 lineA = new Vector2(lineP1.x, lineP1.y);
Vector2 lineB = new Vector2(lineP2.x, lineP2.y);

// 交线段的两个端点
if (planeIntersections.Count != 2)
{
    Debug.Log("特殊情况:四边形完全在z=c平面上,直接用直线与四边形的二维交点即可");
    // 这种情况可以直接用二维直线与四边形的交点算法,这里就不展开了
    return null;
}
Vector2 segA = new Vector2(planeIntersections[0].x, planeIntersections[0].y);
Vector2 segB = new Vector2(planeIntersections[1].x, planeIntersections[1].y);

// 计算二维直线与线段的交点
if (GetLineSegmentIntersection(lineA, lineB, segA, segB, out Vector2 intersection2D))
{
    Vector3 finalIntersection = new Vector3(intersection2D.x, intersection2D.y, targetZ);
    Debug.Log("找到交点:" + finalIntersection);
    return finalIntersection;
}
else
{
    Debug.Log("直线与四边形的交线段无交点");
    return null;
}

5. 二维直线与线段相交的辅助函数

这是核心的几何判断算法,用来验证并计算交点:

bool GetLineSegmentIntersection(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, out Vector2 intersection)
{
    intersection = Vector2.zero;
    
    float denominator = (p2.x - p1.x) * (p4.y - p3.y) - (p2.y - p1.y) * (p4.x - p3.x);
    if (Mathf.Abs(denominator) < 0.001f) return false; // 直线平行或重合
    
    float t = ((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / denominator;
    float u = -((p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x)) / denominator;
    
    // 判断交点是否在线段范围内
    if (t >= 0 && t <= 1 && u >= 0 && u <= 1)
    {
        intersection = p1 + t * (p2 - p1);
        return true;
    }
    return false;
}

额外注意事项

  • 如果你的四边形是空间四边形(非平面),z=c平面和它的交线可能是两条线段,这时候需要分别计算目标直线和两条线段的交点。
  • 代码里的0.001f是精度阈值,可以根据你的项目需求调整。
  • 如果需要频繁计算交点,可以把顶点缓存起来,避免重复转换世界坐标。

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

火山引擎 最新活动