窄阶段碰撞检测

1、功能简介

Peridyno提供了多种几何基本单元之间的碰撞检测算法函数。用户可以通过位置信息对几何基本单元进行初始化,进而调用相关函数进行位置关系的判断。

2、几何基本单元介绍

Peridyno提供了13种几何基本单元的构造以及相关的碰撞检测算法,具体实现可以参考src/Framework/Topology/Primitive3D.inl。

TPoint3D

TPoint3D定义了三维空间中的一个点。既通过点坐标进行初始化,

Coord3D position = Coord(0,1,0);
Point3D point0(position); //使用坐标三元数进行初始化
Point3D point1(0,1,0);//使用三个坐标进行初始化

也可以用另一个TPoint3D进行初始化

Point3D point0(0,1,0);
Point3D point1(point_0);	

TAlignedBox3D

TAlignedBox3D定义了轴对齐包围盒,是几何单元计算轴对齐包围盒的方法aabb()的返回对象。它可以通过两个三维点坐标v0、v1(v0的三个坐标值均需要小于v1对应坐标值)进行初始化

Coord v0(0,0,0);
Coord v1(1,1,1);

AABB aabb(v0, v1);	//v0的三个坐标值均需要小于v1对应坐标值

也可以通过另一个TAlignedBox3D进行初始化

AABB aabb1;
...
AABB aabb2(aabb1);	

TAlignedBox3D中提供的方法如下:

  • intersect intersect计算了该AABB与另一个aabb之间是否相交以。返回值为布尔类型,代表两个AABB是否相交。如果发生相交,那么将交叠的AABB以传引用的方式返回。

    例如

AABB aabb1, aabb2;
...
AABB interBox;
if(aabb1.intersect(aabb2, interBox)) //如果aabb1和aabb2发生交叉,返回1并且将交叉的AABB存入interBox
{
...
}	
  • isValid

    isValid返回值为布尔类型,判断v0的三个坐标值均需要小于v1对应坐标值,

return v1[0] > v0[0] && v1[1] > v0[1] && v1[2] > v0[2];	

使用方法如

AABB aabb;
...
if(aabb.isValid()) //判断aabb的v0的三个坐标值是否不大于v1的对应坐标值
{
...
}	
  • length length(unsigned int i)计算了aabb在i轴边长v1[i] - v0[i],例如
AABB aabb;
...
Real l_x = aabb.length(0);//x轴的边长
Real l_y = aabb.length(1);//y轴的边长
Real l_z = aabb.length(2);//z轴的边长

TSphere3D

TSphere3D定义了三维空间中的一个球体。既通过球心坐标和球半径进行初始化,

Coord3D center = Coord(0,1,0);
Real radius = 0.5f;
Sphere3D sphere(center, radius); 

也可以用另一个TSphere3D进行初始化

Sphere3D sphere0(center, radius);
Sphere3D sphere1(sphere0);	

TSphere3D中提供的方法如下:

  • aabb

    aabb计算了该球体的轴对齐包围盒,主要应用于宽阶段碰撞检测,例如

AABB box;
switch (eleType)
{
	case ET_SPHERE:
	{
		box = spheres[tId].aabb(); //计算该球体的轴对齐包围盒
		break;
	}
...
}
  • volume

    volume计算了球体的体积,可以用于初始化例子质量等,例如

mass = sphere.volume() * density; //计算该球体的体积

TTet3D

TTet3D定义了三维空间中的四面体,初始化方法包括使用四个顶点坐标进行初始化

Coord3D p1,p2,p3,p4;
...
Tet3D tet(p1,p2,p3,p4); 

以及使用另一个TTet3D进行初始化

Tet3D tet0;
...
Tet3D tet1(tet0); 

TTet3D中提供的方法如下:

  • aabb

    aabb计算了该四面体的轴对齐包围盒,主要应用于宽阶段碰撞检测,例如

AABB box;
TTet3D tet;
...
box = tet.aabb(); //计算该球体的轴对齐包围盒
  • volume volume计算该四面体的体积,返回值为一个Real类型的实数。
TTet3D tet;
...
Real volume_tet = tet.volume(); //计算该球体的轴对齐包围盒
  • circumcenter

    circumcenter计算该四面体的外接圆圆心,返回值为一个TPoint3D类型的变量。

    计算方法可参考http://rodolphe-vaillant.fr/entry/127/find-a-tetrahedron-circumcenter。

TTet3D tet;
...
TPoint3D circumcenter_tet = tet.circumcenter();
  • barycenter

    barycenter计算该四面体的重心,返回值为一个TPoint3D类型的变量。

TTet3D tet;
...
TPoint3D barycenter_tet = tet.barycenter();

TOrientedBox3D

TOrientedBox3D定义了三维空间中的长方体,有三种初始化方法。

第一种初始化方法使用长方体的中心三个轴的方向以及三个轴方向边长的一半初始化。

Coord3D center;//长方体中心坐标
Coord3D dir0, dir1, dir2;//均为三维向量,应两两相互垂直,分别代表长方体边的三个方向
Coord3D ext;//三维向量,第1、2、3维的数值均应非负,分别代表dir0、dir1、dir2方向边长的一半
...
Box3D obb(center, dir0, dir1, dir2, ext); 

第二种初始化方法使用长方体的中心用四元数表达的旋转角度以及三个轴方向边长的一半初始化。

Coord3D center;//长方体中心坐标
Quat rot;//四元数代表的三个轴方向的旋转角
Coord3D ext;//三维向量,第1、2、3维的数值均应非负,分别代表dir0、dir1、dir2方向边长的一半
...
Box3D obb(center, rot, ext); 

这里,长方体的三条边以(1,0,0),(0,1,0),(0,0,1)为初始方向,使用四元数进行旋转后即为长方体边的方向,具体可参考实现

auto mat = rot.toMatrix3x3();
u = mat.col(0);
v = mat.col(1);
w = mat.col(2);

第三种初始化方法为使用另一个TOrientedBox3D进行初始化

Box3D Box0;
...
Box3D Box1(Box0); 

TOrientedBox3D中提供的方法如下:

  • aabb

    aabb计算了该长方体的轴对齐包围盒,主要应用于宽阶段碰撞检测,例如

AABB box;
Box3D obb;
...
box = obb.aabb(); //计算该球体的轴对齐包围盒
  • volume volume计算该长方体的体积,返回值为一个Real类型的实数。
AABB box;
Box3D obb;
...
box = obb.volume(); //计算该球体的轴对齐包围盒

3、碰撞检测算法介绍

Peridyno可以检测几何基本单元之间的接触点、接触点法向和穿透距离,具体实现可以参考src/Framework/Collision/CollisionDetectionAlgorithm.inl。

进行一次碰撞检测包含如下步骤:

  • 定义一个TManifold用来存储碰撞信息
  TManifold<Real> manifold;

其中TManifold在src/Framework/Collision/CollisionData.h中的定义如下,

template<typename Real>
struct TManifold
{
public:
	Vector<Real, 3> normal;		//接触点法向		
	TContact<Real> contacts[8];		//接触点位置以及穿透距离
	int contactCount = 0; 	//两个几何基本单元的接触点数量
};

其中contacts的定义如下,存储接触点的坐标以及穿透距离

template<typename Real>
	class TContact
	{
	public:
		Vector<Real, 3> position;			// 接触点坐标
		Real penetration;			// 穿透距离
	};
  • 调用CollisionDetection::request进行碰撞检测

    定义好TManifold后,可以调用CollisionDetection::request系列函数进行碰撞检测。CollisionDetection::request为定义在src/Framework/Collision/CollisionDetectionAlgorithm.h中的一系列函数,其功能为计算并返回两个几何单元几何单元1几何单元2之间的碰撞信息,存储在manifold中。它可以在CPU和GPU上调用,调用方法为

CollisionDetection<Real>::request(manifold, 几何单元1, 几何单元2);

例如,如果希望进行长方体(OBB)和四面体的碰撞检测,可以进行如下操作

Tet3D tetA; //创建一个四面体
tetA.v[0] = tetPos0; //初始化四面体顶点位置
tetA.v[1] = tetPos1;
tetA.v[2] = tetPos2;
tetA.v[3] = tetPos3;

Box3D boxB;//创建一个长方体
boxB.center = center; //长方体中心点位置
boxB.extent = halfLength;//长方体三个方向的边长

Mat3f rot = rotation.toMatrix3x3(); //用长方体的旋转角初始化长方体的边方向
boxB.u = rot * Vec3f(1, 0, 0);
boxB.v = rot * Vec3f(0, 1, 0);
boxB.w = rot * Vec3f(0, 0, 1);

TManifold<Real> manifold; //定义manifold用于存储穿透信息
CollisionDetection<Real>::request(manifold, tetA, boxB); //调用request,将穿透信息存储在manifold中

目前CollisionDetection::request支持的几何单元类型如下

DYN_FUNC static void request(Manifold& m, const OBox3D box0, const OBox3D box1); //长方体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const Sphere3D& sphere, const OBox3D& box); //长方体和球体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const OBox3D& box, const Sphere3D& sphere);//长方体和球体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const Sphere3D& sphere0, const Sphere3D& sphere1);//球体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const Tet3D& tet0, const Tet3D& tet1);//四面体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const Tet3D& tet, const OBox3D& box);//长方体和四面体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const OBox3D& box, const Tet3D& tet);//长方体和四面体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const Sphere3D& sphere, const Tet3D& tet);//球体和四面体之间的碰撞检测

DYN_FUNC static void request(Manifold& m, const Tet3D& tet, const Sphere3D& sphere);//球体和四面体之间的碰撞检测

对于长方体与四面体之间的碰撞检测,peridyno中采用分离轴定理(http://www.randygaul.net/2014/05/22/deriving-obb-to-obb-intersection-sat/)进行碰撞检测。

  • 查询TManifold中的碰撞信息

在调用后,几何单元之间的碰撞信息被存储在TManifold中,可以通过访问TManifold中的信息获得碰撞接触点数量、接触点位置、接触点法向量和穿透距离。其中,接触点法向量由几何单元1指向几何单元2;穿透距离为一个负数,其绝对值代表两个该接触点沿接触点法向量的穿透的深度;如果两个集合体没有发生碰撞,那么接触点数量为0。

例如,可以遍历TManifold中的contacts获得每个接触点的穿透深度。

for (int n = 0; n < manifold.contactCount; n++)//遍历每个穿透点
{
	...
	cp.pos1 = manifold.contacts[n].position; //获取穿透点位置
	cp.normal1 = -manifold.normal;//获取穿透法向量,对于相同的一对几何单元,每个接触点的穿透的法向量都相同
	cp.interpenetration = -manifold.contacts[n].penetration;//获取穿透深度
	...
}