# 碰撞检测之Ray-Cylinder检测

### 正交基和标准正交基

Let S = {v1v2, ... , vkbe a set of vectors in Rn, then S is called an orthogonal if

vi . vj=0

for all i not equal to j. An orthogonal set of vectors is called orthonormal if all vectors in S are unit vectors.

vi . vj = 0  (i != j)

1.给定一组正交基S = {v1, v2, ... , vk}，那么他们是线性相关的。

2.对于 Rn中的一个正交基 S={v1v2, ... , vk},则 Rn中的向量 v 的以S为基的第 i 个坐标为

vj

(5,10).(3/5,4/5) = 11

(5,10).(-4/5, 3/5) = 2

[(5,10)]= (11,2)

### 给定一个空间向量，求出以这个向量为轴的正交基

v = (a,b,c);

a*x + b*y + c*z = 0

w = cross(u,v)

### 圆柱体的射线求交

 public class Cylinder : NGeometry
{
public Vector3 p0;
public Vector3 p1;

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

public Cylinder() : base(GeometryType.Cylinder) { }

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

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

//two thin for check
if (fWLength <= 1e-6f)
{
return false;
}

//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;
Debug.Log("fDLength: " + fDLength);
kD.Normalize();

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

// 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, cylinderDir);

float fDiscr = fRadiusSqr - kP.x * kP.x - kP.y * kP.y;
// ray direction anti-parallel to the cylinder direction
if (fAxisDir < 0 && fDiscr >= 0.0f)
{
if (kP.z > fWLength)
{
hitInfo.distance = (kP.z - fWLength) * fInvDLength;
}
else if (kP.z < 0)
{
return false;
}
else if (kP.z > 0 && kP.z < fWLength)
{
hitInfo.distance = kP.z * fInvDLength;
}

if (hitInfo.distance > distance)
return false;
hitInfo.point = hitInfo.distance * ray.direction;
return true;
}
// ray direction parallel to the cylinder direction
else if (fAxisDir > 0 && fDiscr >= 0.0f)
{
if (kP.z > fWLength)
{
return false;
}
else if (kP.z < 0)
{
hitInfo.distance = -kP.z * fInvDLength;
}
else if (kP.z > 0 && kP.z < fWLength)
{
hitInfo.distance = (fWLength - kP.z) * 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;
}

//If intersect the infinite cylinder but point not between slab, the ray may intersect cylinder's caps.
//Test intersection with caps
float deltaAngle = Vector4.Dot(ray.direction, cylinderDir);

// Ray direction anti-parallel to the capsule direction
if (deltaAngle < 0)
{
if (kP.z > fWLength)
{
float deltaZ = kP.z - fWLength;
float angle = Vector3.Angle(ray.direction, -cylinderDir);
hitInfo.distance = (kP.z - fWLength) * fInvDLength / Mathf.Cos(angle * Mathf.Deg2Rad);
}
else if (kP.z < 0)
{
Debug.Log("No cap0");
return false;
}

if (hitInfo.distance > distance)
return false;
hitInfo.point = ray.origin + hitInfo.distance * ray.direction;
{
return false;
}
return true;
}
// Direction parallel to the cylinder direction
else if (deltaAngle > 0)
{
if (kP.z > fWLength)
{
Debug.Log("No cap1");
return false;
}
else if (kP.z < 0)
{
float angle = Vector3.Angle(ray.direction, cylinderDir);
hitInfo.distance = -kP.z * fInvDLength / Mathf.Cos(angle * Mathf.Deg2Rad);
}
if (hitInfo.distance > distance)
return false;
hitInfo.point = ray.origin + hitInfo.distance * ray.direction;
{
return false;
}
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;
}
}
return false;
}

1.将坐标系转换到了以圆柱体方向为轴的坐标系，用的就是给定一个空间向量，求出以这个向量为轴的正交基中提到的方法；

2.将Ray的origin和Direction都转到新的坐标系下，用的是上面的定理2。新的ray的origin为kP，direction为kD，射线上的点可以表示为kP+t*kD；

3.判断射线方向是否与Z轴方向平行，如果是，判断是否是圆柱体的上下面相交；

4.判断是否是圆柱相交，这里先假设圆柱是无限的，判断的方法是求解二次方程。这里详细说一下，二次函数的形式为

Q(t) = a*t^2 + 2*b*t + c

(kP.x +t*kD.x)^2 +(kP.y +t*kD.y)^2 = R^2

using UnityEngine;
using System.Collections;
using NPhysX;

public class RayCylinderTester : MonoBehaviour {

public GameObject cylinder;
Cylinder _cylinder;
Ray ray;
float castDistance = 10f;
// Use this for initialization
void Start () {
_cylinder = new Cylinder();
}

// Update is called once per frame
void Update () {
ray = new Ray(Vector3.zero, new Vector3(1, 1, 1));
_cylinder.p0 = cylinder.transform.position + cylinder.transform.rotation * Vector3.down * cylinder.transform.localScale.y;
_cylinder.p1 = cylinder.transform.position + cylinder.transform.rotation * Vector3.up * cylinder.transform.localScale.y;

Debug.DrawLine(_cylinder.p0, _cylinder.p1, Color.green);

RaycastHitInfo hitinfo = new RaycastHitInfo();

if (NRaycastTests.Raycast(ray, castDistance, _cylinder, 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);
}
}
}


### 参考

Orthonormal Bases in Rn - http://ltcconline.net/greenl/courses/203/vectors/orthonormalbases.htm

PhysX 3.3  source code

Create orthonormal basis from a given vector - http://www.mathworks.com/matlabcentral/answers/72631-create-orthonormal-basis-from-a-given-vector

03-05

11-24 1857
08-29 6740
11-13 2448
01-16 6139