码迷,mamicode.com
首页 > 其他好文 > 详细

碰撞检测之Ray-Sphere检测

时间:2016-02-22 01:40:58      阅读:588      评论:0      收藏:0      [点我收藏+]

标签:

提要

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

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

今天写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

碰撞检测之Ray-Sphere检测

标签:

原文地址:http://blog.csdn.net/silangquan/article/details/50709517

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!