碰撞检测之Ray-Sphere检测

提要

最近要开一个物理相关的系列,首先整理总结一下去年写的一些东西,然后看能不能撸一个物理引擎出来。

射线检测在之前写光线追踪的时候有写过一些,但当时写得都比较简单,恰好最近工作上又要用到这些,所以就好好来写写。

今天写Ray sphere 的碰撞检测。

环境:

Unity5.2.3 Windows10 64bit


开工

定义一个类来记录相交的信息

 public class RaycastHitInfo
    {
        public Vector3 point;
        public Vector3 normal;
        public float distance;
    }


首先是交点,还有交点的法线,最后是射线射出的距离。


定义基础的形状

    public enum GeometryType
    {
        Sphere,
        Box,
        Capsule,
        Plane,
        Cylinder,
        Cone,
    }

    public class NGeometry
    {
        [HideInInspector]
        public GeometryType type;
        public NGeometry(GeometryType _type)
        {
            type = _type;
        }
    }

    public class Sphere : NGeometry
    {
        public Vector3 center;
        public float radius;

        public Sphere() : base(GeometryType.Sphere) { }
        public Sphere(Vector3 _center, float _radius)
            : base(GeometryType.Sphere)
        {
            center = _center;
            radius = _radius;
        }

        public bool Contains(Vector3 p)
        {
            return (p - center).magnitude <= radius * radius;
        }
    }


理解三维碰撞算法最快的方法就是先理解二维的算法。

首先看一下在二维空间中,射线和圆的位置情况。



就三种,相交,相切,相离。

这里的射线和球体相交,基本和二维情况无异,可以看作是射线和球体的一个切面相交。


核心代码

public static bool Raycast(Ray ray, float distance, Sphere sphere, out RaycastHitInfo hitInfo)
{
    if (!IntersectRaySphereBasic(ray, distance, sphere, out hitInfo))
    {
        return false;
    }

    hitInfo.normal = hitInfo.point - sphere.center;
    hitInfo.normal.Normalize();
    return true;
}

public static bool IntersectRaySphereBasic(Ray ray, float distance, Sphere sphere, out RaycastHitInfo hitInfo)
{
    hitInfo = new RaycastHitInfo();
    Vector3 offset = sphere.center - ray.origin;
    float rayDist = Vector3.Dot(ray.direction, offset);

    float off2 = Vector3.Dot(offset, offset);
    float rad2 = sphere.radius * sphere.radius;

    // we're in the sphere
    if (off2 <= rad2)
    {
        hitInfo.point = ray.origin;
        hitInfo.normal = Vector3.zero;
        hitInfo.distance = 0.0f;
        return true;
    }

    // moving away from object or too far away
    if (rayDist <= 0 || (rayDist - distance) > sphere.radius)
    {
        return false;
    }

    // find hit distance squared
    // ray passes by sphere without hitting
    float d = rad2 - (off2 - rayDist * rayDist);
    if (d < 0.0f)
    {
        return false;
    }

    // get the distance along the ray
    hitInfo.distance = rayDist - Mathf.Sqrt(d);
    if (hitInfo.distance > distance)
    {
        // hit point beyond length
        return false;
    }
    hitInfo.point = ray.origin + ray.direction * hitInfo.distance;
    return true;
}

提醒点1

float rayDist = Vector3.Dot(ray.direction, offset);

点乘又叫向量的内积、数量积,是一个向量和它在另一个向量上的投影的长度的乘积;

由于ray.direction是单位向量,所以这里计算的是offset在射线方向的投影长度。


提醒点2

float d = rad2 - (off2 - rayDist * rayDist);

这里的d计算的是图中标出的位置,蓝色的线段是上面计算的rayDist



测试代码

using UnityEngine;
using System.Collections;
using NPhysX;

public class RaySphereTester : MonoBehaviour {
    public GameObject sphere;
    Sphere _sphere;

    // Use this for initialization
    void Start () {
        _sphere = new Sphere(Vector3.zero, 1f);
    }
	
	// Update is called once per frame
	void Update () {
       //Ray sphere test.
            Ray ray = new Ray(Vector3.zero, new Vector3(1,1,1));
            _sphere.center = sphere.transform.position;
            _sphere.radius = 0.5f * sphere.transform.localScale.x;
            RaycastHitInfo hitinfo;
            float castDistance = 10f;
            if (NRaycastTests.Raycast(ray, castDistance, _sphere, out hitinfo))
            {
                Debug.DrawLine(ray.origin, ray.direction * hitinfo.distance, Color.red, 0, false);
                Debug.DrawLine(_sphere.center, _sphere.center + hitinfo.normal, Color.green, 0, false);
            }
            else
            {
                Debug.DrawLine(ray.origin, ray.direction * castDistance, Color.blue, 0, false);
            }
    }
}


运行结果





题外话

这里提到一个小问题,说由于CPU中浮点数计算单元的计算精确度问题,

the code solving the quadratic equation suffers from limited FPU accuracy and returns “no intersection” while there definitely is one. “Best” fix is to move the ray origin closer to the target sphere.

ray sphere相交结果可能会不精确,于是乎做了一些小的优化

public static bool IntersectRaySphere(Ray ray, float distance, Sphere sphere, out RaycastHitInfo hitInfo)
		{
			Vector3 x = ray.origin - sphere.center;
			float l = Vector3.Dot(x, x) - sphere.radius - 10.0f;

			l = Mathf.Max(l, 0.0f);
			Ray tmpRay = new Ray(ray.origin + l * ray.direction, ray.direction);
			bool status = IntersectRaySphereBasic(tmpRay, distance - l, sphere, out hitInfo);
			Debug.Log("l: " + l);
			//bool status = IntersectRaySphereBasic(ray, distance, sphere, out hitInfo);
			if(status)
			{
				hitInfo.distance += l;
			}
			return status;
		}

不过暂时我还没有发现这个问题。


参考

PhysX3.3 source code

real time renderring 3rd

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值