碰撞检测之Ray-Capsule检测

Capsule的射线检测和Cylinder的类似,只是把上下两个面换成了两个半球,代码上稍作区别即可。


Capsule类型定义

 public class Capsule : NGeometry
    {
        public Vector3 p0;
        public Vector3 p1;
        public float radius;

        public Capsule(Vector3 _p0, Vector3 _p1, float _radius)
            : base(GeometryType.Capsule)
        {
            p0 = _p0;
            p1 = _p1;
            radius = _radius;
        }
        public Capsule() : base(GeometryType.Capsule) { }

        public Vector3 ComputeDirection()
        {
            return p1 - p0;
        }
    }


射线检测

  public static bool Raycast(Ray ray, float distance, Capsule capsule, out RaycastHitInfo hitInfo)
        {
            hitInfo = new RaycastHitInfo();
            Vector3 capsuleDir = capsule.ComputeDirection();
            Vector3 kW = capsuleDir;
            float fWLength = kW.magnitude;
            kW.Normalize();

            // PT: if the capsule is in fact a circle, switch back to dedicated plane code.
            // This is not just an optimization, the rest of the code fails otherwise.
            if (fWLength <= 1e-6f)
            {
                return Raycast(ray, distance, new Sphere(capsule.p0, capsule.radius), out hitInfo);
            }

            // generate orthonormal basis
            //cylinder along the z direction
            Vector3 kU = Vector3.zero;
            if (fWLength > 0.0f)
            {
                float fInvLength;
                if (Mathf.Abs(kW.x) >= Mathf.Abs(kW.y))
                {
                    fInvLength = 1.0f / Mathf.Sqrt(kW.x * kW.x + kW.z * kW.z);
                    kU.x = -kW.z * fInvLength;
                    kU.y = 0.0f;
                    kU.z = kW.x * fInvLength;
                }
                else
                {
                    // W.y or W.z is the largest magnitude component, swap them
                    fInvLength = 1.0f / Mathf.Sqrt(kW.y * kW.y + kW.z * kW.z);
                    kU.x = 0.0f;
                    kU.y = kW.z * fInvLength;
                    kU.z = -kW.y * fInvLength;
                }
            }
            Vector3 kV = Vector3.Cross(kW, kU);
            kV.Normalize();

            // compute intersection
            //Transform the ray to the cylinder's local coordinate
            //new Ray direction
            Vector3 kD = new Vector3(Vector3.Dot(kU, ray.direction), Vector3.Dot(kV, ray.direction), Vector3.Dot(kW, ray.direction));
            float fDLength = kD.magnitude;
            kD.Normalize();

            float fInvDLength = 1.0f / fDLength;
            Vector3 kDiff = ray.origin - capsule.p0;
            //new Ray origin
            Vector3 kP = new Vector3(Vector3.Dot(kU, kDiff), Vector3.Dot(kV, kDiff), Vector3.Dot(kW, kDiff));

            float fRadiusSqr = capsule.radius * capsule.radius;

            // Is the ray direction parallel to the cylinder direction? (or zero)
            if (Mathf.Abs(kD.z) >= 1.0f - Mathf.Epsilon || fDLength < Mathf.Epsilon)
            {
                float fAxisDir = Vector4.Dot(ray.direction, capsuleDir);

                float fDiscr = fRadiusSqr - kP.x * kP.x - kP.y * kP.y;
                // direction anti-parallel to the capsule direction
                if (fAxisDir < 0 && fDiscr >= 0.0f)
                {
                    float fRoot = Mathf.Sqrt(fDiscr);
                    //Ray origin in the top of capsule.
                    if (kP.z > fWLength + fRoot)
                    {
                        hitInfo.distance = (kP.z - fWLength - fRoot) * fInvDLength;
                    }
                    //Ray origin in the bottom of capsule.
                    else if (kP.z < -fRoot)
                    {
                        return false;
                    }
                    //Ray origin one the capsule.
                    else if (kP.z > -fRoot && kP.z < fWLength + fRoot)
                    {
                        hitInfo.distance = (kP.z + fRoot) * fInvDLength;
                    }

                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;
                    return true;
                }
                // direction parallel to the capsule direction
                else if (fAxisDir > 0 && fDiscr >= 0.0f)
                {
                    float fRoot = Mathf.Sqrt(fDiscr);
                    if (kP.z > fWLength + fRoot)
                    {
                        return false;
                    }
                    else if (kP.z < -fRoot)
                    {
                        hitInfo.distance = (-kP.z - fRoot) * fInvDLength;
                    }
                    else if (kP.z > -fRoot && kP.z < fWLength + fRoot)
                    {
                        hitInfo.distance = (fWLength - kP.z + fRoot) * fInvDLength;
                    }
                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;
                    return true;
                }
                else
                {
                    //ray origin out of the circle
                    return false;
                }
            }

            // Test intersection with infinite cylinder
            // set up quadratic Q(t) = a*t^2 + 2*b*t + c
            float fA = kD.x * kD.x + kD.y * kD.y;
            float fB = kP.x * kD.x + kP.y * kD.y;
            float fC = kP.x * kP.x + kP.y * kP.y - fRadiusSqr;
            float delta = fB * fB - fA * fC;
            // line does not intersect infinite cylinder
            if (delta < 0.0f)
            {
                return false;
            }

            // line intersects infinite cylinder in two points
            if (delta > 0.0f)
            {
                float fRoot = Mathf.Sqrt(delta);
                float fInv = 1.0f / fA;
                float fT = (-fB - fRoot) * fInv;
                float fTmp = kP.z + fT * kD.z;
                float dist0 = 0f, dist1 = 0f;

                float fT1 = (-fB + fRoot) * fInv;
                float fTmp1 = kP.z + fT * kD.z;

                //cast two point
                //fTmp <= fWLength to check intersect point between slab.
                if ((0.0f <= fTmp && fTmp <= fWLength) && (0.0f <= fTmp1 && fTmp1 <= fWLength))
                {
                    dist0 = fT * fInvDLength;
                    dist1 = fT1 * fInvDLength;
                    hitInfo.distance = Mathf.Min(dist0, dist1);
                    return true;
                }
                else if ((0.0f <= fTmp && fTmp <= fWLength))
                {
                    dist0 = fT * fInvDLength;
                    hitInfo.distance = dist0;
                    return true;
                }
                else if ((0.0f <= fTmp1 && fTmp1 <= fWLength))
                {
                    dist1 = fT1 * fInvDLength;
                    hitInfo.distance = dist1;
                    return true;
                }
            }
            // line is tangent to infinite cylinder
            else
            {
                float fT = -fB / fA;
                float fTmp = kP.z + fT * kD.z;
                if (0.0f <= fTmp && fTmp <= fWLength)
                {
                    hitInfo.distance = fT * fInvDLength;
                    return true;
                }
            }

            // test intersection with bottom hemisphere
            // fA = 1
            fB += kP.z * kD.z;
            fC += kP.z * kP.z;
            float distanceSqrt = fB * fB - fC;
            if (distanceSqrt > 0.0f)
            {
                float fRoot = Mathf.Sqrt(distanceSqrt);
                float fT = -fB - fRoot;
                float fTmp = kP.z + fT * kD.z;
                if (fTmp <= 0.0f)
                {
                    hitInfo.distance = fT * fInvDLength;
                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;
                    return true;
                }

            }
            else if (distanceSqrt == 0.0f)
            {
                float fT = -fB;
                float fTmp = kP.z + fT * kD.z;
                if (fTmp <= 0.0f)
                {
                    hitInfo.distance = fT * fInvDLength;
                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;
                    return true;

                }
            }

            // test intersection with top hemisphere
            // fA = 1
            fB -= kD.z * fWLength;
            fC += fWLength * (fWLength - 2.0f * kP.z);

            distanceSqrt = fB * fB - fC;
            if (distanceSqrt > 0.0f)
            {
                float fRoot = Mathf.Sqrt(distanceSqrt);
                float fT = -fB - fRoot;
                float fTmp = kP.z + fT * kD.z;
                if (fTmp >= fWLength)
                {
                    hitInfo.distance = fT * fInvDLength;
                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;

                    return true;
                }

                fT = -fB + fRoot;
                fTmp = kP.z + fT * kD.z;
                if (fTmp >= fWLength)
                {
                    hitInfo.distance = fT * fInvDLength;
                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;
                    return true;

                }
            }
            else if (distanceSqrt == 0.0f)
            {
                float fT = -fB;
                float fTmp = kP.z + fT * kD.z;
                if (fTmp >= fWLength)
                {
                    hitInfo.distance = fT * fInvDLength;
                    if (hitInfo.distance > distance)
                        return false;
                    hitInfo.point = hitInfo.distance * ray.direction;
                    return true;
                }
            }

            return false;
        }


测试代码

public class RayCapsuleTester : MonoBehaviour {
    public CapsuleCollider capsule;
    Capsule _capsule;
    Ray ray;
    float castDistance = 10f;
    // Use this for initialization
    void Start()
    {
        _capsule = new Capsule();
        ray = new Ray(Vector3.zero, new Vector3(1, 1, 1));
    }

    // Update is called once per frame
    void Update()
    {
        _capsule.radius = capsule.radius;
        if (capsule.height < 2.0f * capsule.radius)
        {
            _capsule.p0 = capsule.transform.position;
            _capsule.p1 = capsule.transform.position;
        }
        else
        {
            //In unity capsule collider height include hemisphere height.
            float realHeight = capsule.height - 2.0f * capsule.radius;
            _capsule.p0 = capsule.transform.position + capsule.transform.rotation * Vector3.down * realHeight * 0.5f;
            _capsule.p1 = capsule.transform.position + capsule.transform.rotation * Vector3.up * realHeight * 0.5f;
        }

        Debug.DrawLine(_capsule.p0, _capsule.p1, Color.black);


        RaycastHitInfo hitinfo = new RaycastHitInfo();

        if (NRaycastTests.Raycast(ray, castDistance, _capsule, out hitinfo))
        {
            Debug.DrawLine(ray.origin, ray.origin + ray.direction * hitinfo.distance, Color.red, 0, true);
        }
        else
        {
            Debug.DrawLine(ray.origin, ray.origin + ray.direction * castDistance, Color.blue, 0, true);
        }
    }
}


运行结果




参考

PhysX 3.3 source code

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Unity3d中有两种检测碰撞发生的方式,一种是利用碰撞器,另一种是利用触发器。碰撞器是一组组件,包括Box Collider、Capsule Collider等,它们必须添加到GameObject上。而触发器只需要在碰撞器组件的检视面板中勾选IsTrigger属性选择框。在Unity3d中,我们可以使用以下接口函数来处理这两种碰撞检测: - 触发信息检测: - MonoBehaviour.OnTriggerEnter(Collider other):当进入触发器时调用。 - MonoBehaviour.OnTriggerExit(Collider other):当退出触发器时调用。 - MonoBehaviour.OnTriggerStay(Collider other):当逗留在触发器中时调用。 - 碰撞信息检测: - MonoBehaviour.OnCollisionEnter(Collision collisionInfo):当进入碰撞器时调用。 - MonoBehaviour.OnCollisionExit(Collision collisionInfo):当退出碰撞器时调用。 - MonoBehaviour.OnCollisionStay(Collision collisionInfo):当逗留在碰撞器中时调用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [Unity3D入门(二):碰撞检测](https://blog.csdn.net/zooen2011/article/details/12245529)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Unity3D —— 碰撞检测](https://blog.csdn.net/wxy15979115440/article/details/103609899)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Unity 3D之碰撞检测](https://blog.csdn.net/m0_74427422/article/details/129170479)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值