最近要开一个物理相关的系列,首先整理总结一下去年写的一些东西,然后看能不能撸一个物理引擎出来。
射线检测在之前写光线追踪的时候有写过一些,但当时写得都比较简单,恰好最近工作上又要用到这些,所以就好好来写写。
今天写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; }
float rayDist = Vector3.Dot(ray.direction, offset);
点乘又叫向量的内积、数量积,是一个向量和它在另一个向量上的投影的长度的乘积;
由于ray.direction是单位向量,所以这里计算的是offset在射线方向的投影长度。
提醒点2
float d = rad2 - (off2 - rayDist * 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