Unity3D C#数学系列之判断两条线段是否相交并求交点

1 引言
问题:已知三维空间中四点A、B、C、D,如何判断线段AB与CD是否相交,若相交则求出交点。
在这里插入图片描述

分析:

AB、CD要相交,则AB、CD必须要在同一平面内
快速排斥和跨立实验判断是否相交
几何法分析求出交点
先来看看效果,紫色小球为交点。
在这里插入图片描述

2 求解
2.1 AB、CD是否共面与平行
要判断AB、CD是否共面,其实就是判断A、B、C、D四个点是否共面。我们知道三点确定一个平面,如果AB垂直于ACD三点所在平面的法线,则说明A、B、C、D四点共面。
A、C、D三点所在平面的法线怎么求?
很简单,两个向量的叉乘就是这两个向量所在平面的法线。
在这里插入图片描述

在这里插入图片描述

这里我们用点积来写代码。

Vector3 ab = b - a;
Vector3 ca = a - c;
Vector3 cd = d - c;

Vector3 v1 = Vector3.Cross(ca, cd);

if (Mathf.Abs(Vector3.Dot(v1, ab)) > 1e-6)
{
    // 不共面
    return false;
}

AB和CD平行时,两者的夹角为0°或180°,也可用点积或叉积来判断,这里就不再多说了。

2.2 快速排斥与跨立实验判断AB、CD是否相交
2.2.1 快速排斥
快速排斥的作用是先预处理一下,先排出掉根本不可能相交的情况,以减少不必要的运算。
其原理就是判断AB的包围盒与CD的包围盒是否有重叠的部分。什么意思呢?看图就明白了。
在这里插入图片描述

三维的同理。
在这里插入图片描述

代码怎么写呢?如下。

// 快速排斥
if (Mathf.Min(a.x, b.x) > Mathf.Max(c.x, d.x) || Mathf.Max(a.x, b.x) < Mathf.Min(c.x, d.x)
   || Mathf.Min(a.y, b.y) > Mathf.Max(c.y, d.y) || Mathf.Max(a.y, b.y) < Mathf.Min(c.y, d.y)
   || Mathf.Min(a.z, b.z) > Mathf.Max(c.z, d.z) || Mathf.Max(a.z, b.z) < Mathf.Min(c.z, d.z)
)
    return false;

2.2.2 跨立实验
什么是跨立呢?比如A、B两点分别位于CD的左右两边,我们就说A、B跨立CD。
如果两条线段相交,它们必然是互相跨立的嘛。
怎么判断跨立呢?用向量的叉积。
在这里插入图片描述

代码如下:

// 跨立试验
if (Vector3.Dot(Vector3.Cross(-ca, ab), Vector3.Cross(ab, ad)) > 0
    && Vector3.Dot(Vector3.Cross(ca, cd), Vector3.Cross(cd, cb)) > 0)
{
    // 相交
    return true;
}

2.3 计算交点
咱们先写结论。
在这里插入图片描述

AO与AB的比值求到了,O点也就能求到了。
结论怎么来的?分析如下:

① 咱们先做条辅助线CE,CE平行于AB,且与AB等长。
在这里插入图片描述

② 则有ΔAFO相似于ΔEGC,则有
在这里插入图片描述

③ 则有

在这里插入图片描述

④根据叉积的定义,又有
在这里插入图片描述

⑤于是乎,就有
在这里插入图片描述

在这里插入图片描述

这里我们用点积来计算,主要是为了能够求出AB和CD不直接相交,但AB的延长线和CD相交的交点。比如我们把下面提供的代码中的快速排斥和跨立试验给注释掉,就能求出延长线的交点。
在这里插入图片描述

3 完整项目
如下。

/// <summary>
/// 计算AB与CD两条线段的交点.
/// </summary>
/// <param name="a">A点</param>
/// <param name="b">B点</param>
/// <param name="c">C点</param>
/// <param name="d">D点</param>
/// <param name="intersectPos">AB与CD的交点</param>
/// <returns>是否相交 true:相交 false:未相交</returns>
private bool TryGetIntersectPoint(Vector3 a, Vector3 b, Vector3 c, Vector3 d, out Vector3 intersectPos)
{
    intersectPos = Vector3.zero;

    Vector3 ab = b - a;
    Vector3 ca = a - c;
    Vector3 cd = d - c;

    Vector3 v1 = Vector3.Cross(ca, cd);

    if (Mathf.Abs(Vector3.Dot(v1, ab)) > 1e-6)
    {
        // 不共面
        return false;
    }

    if (Vector3.Cross(ab, cd).sqrMagnitude <= 1e-6)
    {
        // 平行
        return false;
    }

    Vector3 ad = d - a;
    Vector3 cb = b - c;
    // 快速排斥
    if (Mathf.Min(a.x, b.x) > Mathf.Max(c.x, d.x) || Mathf.Max(a.x, b.x) < Mathf.Min(c.x, d.x)
       || Mathf.Min(a.y, b.y) > Mathf.Max(c.y, d.y) || Mathf.Max(a.y, b.y) < Mathf.Min(c.y, d.y)
       || Mathf.Min(a.z, b.z) > Mathf.Max(c.z, d.z) || Mathf.Max(a.z, b.z) < Mathf.Min(c.z, d.z)
    )
        return false;

    // 跨立试验
    if (Vector3.Dot(Vector3.Cross(-ca, ab), Vector3.Cross(ab, ad)) > 0
        && Vector3.Dot(Vector3.Cross(ca, cd), Vector3.Cross(cd, cb)) > 0)
    {
        Vector3 v2 = Vector3.Cross(cd, ab);
        float ratio = Vector3.Dot(v1, v2) / v2.sqrMagnitude;
        intersectPos = a + ab * ratio;
        return true;
    }

    return false;
}

————————————————
原文链接:https://blog.csdn.net/sinat_25415095/article/details/114293638

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值