3D空间包围球(Bounding Sphere)的求法

引言

            在3D碰撞检測中,为了加快碰撞检測的效率,降低不必要的碰撞检測,会使用基本几何体作为物体的包围体(Bounding Volume, BV)进行測试。基本包围体的碰撞检測相对来说廉价也easy的多,所以假设在基本包围体的碰撞检測中都没有通过的话,那么就没有必要进行更加复杂的碰撞检測了。

           而对于不同性质,不同形状的模型,须要依据情况选择不同的包围体,一般来说,包围体分为例如以下的几种:

            Sphere, AABB, OBB, 8-DOP, Convex Hull这几种常见的。

           接下来将向大家讲述怎样使用Sphere包围体。




表示方法

           想要使用包围体,我们就要知道,怎样的表示一个包围球体。对于球体来说,表示它非常easy,例如以下所看到的:

struct Sphere
{
     VECTOR3 center ;
     float radious ;
};

           这样,在有了中点和半径之后,我们就能唯一的确定一个包围球体了。




包围球之间的碰撞检測

            对于包围球之间的碰撞检測,十分的简单,仅仅要推断两个包围球心之间的距离是否小于他们两个的半径之和就能够了。假设小于,那么这两个包围球发生了交叉,没有的话,就相互分离。下面是进行碰撞检測的代码:

int TestSphereSphere(Sphere a, Sphere b)

{

       VECTOR3 d = a.center - b.center ;

       float dist2 = Dot(d, d);

       float radisum = a.radious + b.radious ;

       if(dist2 < radisum * radisum)

             return 1 ;

       else

            return 0 ;

}

             非常easy不是嘛!因为进行开平方计算要消耗大量的CPU,所以,我们直接对球心之间距离的平方和他们半径之和的平方进行比較,结果与进行距离和半径之和的比較一致。




包围球体的计算

               球形包围体的计算有非常多的算法,在本篇文章中将讲述两种常见的计算方法。假设你使用DirectX,就会知道,DirectX内置了一个D3DXComputBoundingSphere的函数。这里将不会使用这个函数,而是使用我们自己创建的计算方法来进行包围球体的计算。

              首先来介绍第一种包围球体的计算方法。


均值法

              我们知道,在3D模型的表示中,一般都是用一系列的顶点来描写叙述一个模型。所以,要求一个包围球体,我们就必须确定这个包围球体的球心,然后计算每个顶点与球心的距离,选取最长的距离作为包围球体的半径。这个简单的算法就行确定一个包围球体了。那么,有一个问题,假设的确定这个模型的球心了?

              我们通过例如以下的简答方法来计算出这个模型的球心。我们将全部的顶点相加,然后除以顶点数,这样就能得到一个球心的位置了。

               到这里,这种方法的理论就介绍完成了,非常easy不是吗???以下来看看这种方法的代码部分:

void Sphere::computeBoundingSphereAverage(VECTOR3* vertices, unsigned int vertex_num)
{
	//Compute the center point
	VECTOR3 total ;
	total.x = 0 ;
	total.y = 0 ;
	total.z = 0 ;
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		total.x += vertices[i].x ;
		total.y += vertices[i].y ;
		total.z += vertices[i].z ;
	}// end for

	total.x /= vertex_num ;
	total.y /= vertex_num ;
	total.z /= vertex_num ;
	center = total ;

	//Compute the radious
	float r = 0 ;
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		VECTOR3 temp ;
		Vec3Sub(temp, total, vertices[i]);
		float length = 0 ;
		length = Vec3Length(length, temp);
		if(length > r)
			r = length ;
	}// end for

	radious = r ;
}// end for computeBoundingSphereAverage

             代码非常清晰,也非常easy明确,所以这里将不再进行解释了。

             看看使用这种方法计算出来的包围球的效果怎样:




Ritter方法

              Ritter,Jack提出了一种新的近似的计算包围球体的方法。它的思路是这种:

             首先我们分别找到这个模型在x,y,z正负六个方向上的最远距离的6个点。然后我们分别计算出这三对点之间的长度,也就是x轴向上两个点之间的长度,y轴向上两个点之间的长度,z轴向上两个点之间的长度。我们选取长度最长的那一个作为包围球的直径,以这个长度的两个点的中点作为包围球的球心。

            通过上面的方法,我们能近似的求出这个包围球,可是并不能保证模型中的每个顶点都在这个包围球里面,所以我们还须要对此进行修正。

            我们遍历全部的顶点,推断顶点是否在球体里面,假设在里面,则忽略它,假设不在里面,我们依照以下的算法来对球体进行修正,以使的新的球体可以包括这个点。

            请看下图:

           我们如果当前的球心为O,半径为r。如今我们发如今这个球体之外有一个点P。所以,我们须要对这个包围球体进行修正,以便于将这个点包围在球体里面。为了最紧凑的包围住这个点,我们将点P与球心O连线,交圆与T点。这时,你就会发现,TP就是我们要求的包围球的新直径了,那么球心也就是他们之间的中点了。

          求出T的计算比較庞大,所以我们计算新的半径使用以下的方法:

          因为P点和O点都是已知的,所以求他们之间的距离比較easy。也就是说新的半径为: (r + OP) * 0.5

          有了新的半径之后,我们须要做的就是平移球心点,平移的向量大小刚好就是SP的长度,方向是从O点指向P点,当中S点为PR的中点。

          所以有了上面的算法,我们依次的遍历全部的点,我们就行确定一个近似的包围球了。

          这个理论也非常easy,以下是实现的代码:

void Sphere::computeBoundingSphereRitter(VECTOR3* vertices, unsigned int vertex_num)
{
	unsigned int maxX = 0 , maxY = 0, maxZ = 0 , minX = -1, minY = -1, minZ = -1 ;
	
	//Find the max and min along the x-axie, y-axie, z-axie
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		if(vertices[i].x > maxX) maxX = i ;
		if(vertices[i].x < minX) minX = i ;
		if(vertices[i].y > maxY) maxY = i ;
		if(vertices[i].y < minY) minY = i ;
		if(vertices[i].z > maxZ) maxZ = i ;
		if(vertices[i].z < minZ) minZ = i ;
	}// end for

	float x = 0;
	VECTOR3 sub1 , sub2 ;
	sub1.x = vertices[maxX].x ; sub1.y = vertices[maxX].y ; sub1.z = vertices[maxX].z ;
	sub2.x = vertices[minX].x ; sub2.y = vertices[minX].y ; sub2.z = vertices[minX].z ;
	Vec3Sub(sub1, sub1, sub2);
	Vec3Dot(x, sub1, sub1);

	float y = 0 ;
	sub1.x = vertices[maxY].x ; sub1.y = vertices[maxY].y ; sub1.z = vertices[maxY].z ;
	sub2.x = vertices[minY].x ; sub2.y = vertices[minY].y ; sub2.z = vertices[minY].z ;
	Vec3Sub(sub1, sub1, sub2);
	Vec3Dot(y, sub1, sub1);

	float z = 0 ;
	sub1.x = vertices[maxZ].x ; sub1.y = vertices[maxZ].y ; sub1.z = vertices[maxZ].z ;
	sub2.x = vertices[minZ].x ; sub2.y = vertices[minZ].y ; sub2.z = vertices[minZ].z ;
	Vec3Sub(sub1, sub1, sub2);
	Vec3Dot(z, sub1, sub1);

	float dia = 0 ;
	int max = maxX , min = minX ;
	if( z > x && z > y)
	{
		max = maxZ ;
		min = minZ ;
		dia = z ;
	}else if(y > x && y > z)
	{
		max = maxY ;
		min = minY ;
		dia = y ;
	}

	//Compute the center point
	center.x = 0.5 * (vertices[max].x + vertices[min].x) ;
	center.y = 0.5 * (vertices[max].y + vertices[min].y) ;
	center.z = 0.5 * (vertices[max].z + vertices[min].z) ;

	//Compute the radious
	radious = 0.5 * sqrt(dia);

	//Fix it
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		VECTOR3 d ;
		Vec3Sub(d, vertices[i], center);
		float dist2 = 0 ;
		Vec3Dot(dist2, d, d);

		if(dist2 > radious * radious)
		{
			float dist = sqrt(dist2);
			float newRadious = (dist + radious) * 0.5 ;
			float k = (newRadious - radious) / dist ;
			radious = newRadious ;
			VECTOR3 temp ;
			Vec3Mul(temp, d, k);
			Vec3Add(center, center, temp);
		}// end if
	}// end for vertex_num
}// end for computeBoundingSphereRitter

          上面的代码应该非常清楚了,所以不须要在进行解释,假设有疑问能够在博客中留言。看下这个算法的效果怎样:



两种方法的对照

             上面两种方法,尽管第一种最简单,可是相同的他的效果不如另外一种的好,假设你不能直观的看出来,那么请看以下两种对照图:

第一种算法:

另外一种算法:


                 非常明显的看出,另外一种算法它的包围球体更加的紧凑点。


 



包围球类

                以下,我将这个包围球的类的全部代码列出来(唯独包围球的类,关于DirectX的操作部分,不属于这个类)

//---------------------------------------------------------------------------------
// declaration	: Copyright (c), by XJ , 2014 . All right reserved .
// brief		: This file will define the bounding sphere in collision system
// author		: XJ
// date			: 2014 / 6 / 20
// file			: Sphere.h
// version		: 1.0
//---------------------------------------------------------------------------------
#pragma once 
#include"XJMath.h"
namespace XJCollision
{
	class Sphere
	{
	public:
		Sphere();
		Sphere(VECTOR3 c, float r);
		~Sphere();

	public:
		/**
		* This method will use the average method to compute the bounding sphere of the
		* input vertices array
		*/
		void computeBoundingSphereAverage(VECTOR3* vertices, unsigned int vertex_num);

		/**
		* This method will use the Ritter's method to compute the bounding sphere
		*/
		void computeBoundingSphereRitter(VECTOR3* vertices, unsigned int vertex_num);
	public:
		VECTOR3 center ;
		float radious ;
	};
};

#include"Sphere.h"
#include<cmath>
using namespace std ;
using namespace XJCollision ;

Sphere::Sphere()
	:center(),
	radious(0.0f)
{

}

Sphere::Sphere(VECTOR3 c, float r)
	:center(c),
	radious(r)
{

}

Sphere::~Sphere()
{

}

void Sphere::computeBoundingSphereAverage(VECTOR3* vertices, unsigned int vertex_num)
{
	//Compute the center point
	VECTOR3 total ;
	total.x = 0 ;
	total.y = 0 ;
	total.z = 0 ;
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		total.x += vertices[i].x ;
		total.y += vertices[i].y ;
		total.z += vertices[i].z ;
	}// end for

	total.x /= vertex_num ;
	total.y /= vertex_num ;
	total.z /= vertex_num ;
	center = total ;

	//Compute the radious
	float r = 0 ;
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		VECTOR3 temp ;
		Vec3Sub(temp, total, vertices[i]);
		float length = 0 ;
		length = Vec3Length(length, temp);
		if(length > r)
			r = length ;
	}// end for

	radious = r ;
}// end for computeBoundingSphereAverage

void Sphere::computeBoundingSphereRitter(VECTOR3* vertices, unsigned int vertex_num)
{
	unsigned int maxX = 0 , maxY = 0, maxZ = 0 , minX = -1, minY = -1, minZ = -1 ;
	
	//Find the max and min along the x-axie, y-axie, z-axie
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		if(vertices[i].x > maxX) maxX = i ;
		if(vertices[i].x < minX) minX = i ;
		if(vertices[i].y > maxY) maxY = i ;
		if(vertices[i].y < minY) minY = i ;
		if(vertices[i].z > maxZ) maxZ = i ;
		if(vertices[i].z < minZ) minZ = i ;
	}// end for

	float x = 0;
	VECTOR3 sub1 , sub2 ;
	sub1.x = vertices[maxX].x ; sub1.y = vertices[maxX].y ; sub1.z = vertices[maxX].z ;
	sub2.x = vertices[minX].x ; sub2.y = vertices[minX].y ; sub2.z = vertices[minX].z ;
	Vec3Sub(sub1, sub1, sub2);
	Vec3Dot(x, sub1, sub1);

	float y = 0 ;
	sub1.x = vertices[maxY].x ; sub1.y = vertices[maxY].y ; sub1.z = vertices[maxY].z ;
	sub2.x = vertices[minY].x ; sub2.y = vertices[minY].y ; sub2.z = vertices[minY].z ;
	Vec3Sub(sub1, sub1, sub2);
	Vec3Dot(y, sub1, sub1);

	float z = 0 ;
	sub1.x = vertices[maxZ].x ; sub1.y = vertices[maxZ].y ; sub1.z = vertices[maxZ].z ;
	sub2.x = vertices[minZ].x ; sub2.y = vertices[minZ].y ; sub2.z = vertices[minZ].z ;
	Vec3Sub(sub1, sub1, sub2);
	Vec3Dot(z, sub1, sub1);

	float dia = 0 ;
	int max = maxX , min = minX ;
	if( z > x && z > y)
	{
		max = maxZ ;
		min = minZ ;
		dia = z ;
	}else if(y > x && y > z)
	{
		max = maxY ;
		min = minY ;
		dia = y ;
	}

	//Compute the center point
	center.x = 0.5 * (vertices[max].x + vertices[min].x) ;
	center.y = 0.5 * (vertices[max].y + vertices[min].y) ;
	center.z = 0.5 * (vertices[max].z + vertices[min].z) ;

	//Compute the radious
	radious = 0.5 * sqrt(dia);

	//Fix it
	for(int i = 0 ; i < vertex_num ; i ++)
	{
		VECTOR3 d ;
		Vec3Sub(d, vertices[i], center);
		float dist2 = 0 ;
		Vec3Dot(dist2, d, d);

		if(dist2 > radious * radious)
		{
			float dist = sqrt(dist2);
			float newRadious = (dist + radious) * 0.5 ;
			float k = (newRadious - radious) / dist ;
			radious = newRadious ;
			VECTOR3 temp ;
			Vec3Mul(temp, d, k);
			Vec3Add(center, center, temp);
		}// end if
	}// end for vertex_num
}// end for computeBoundingSphereRitter

                好了,今天到这里就结束了。以后会陆陆续续的解说其它的包围体的用法,希望大家喜欢!!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JAVA 3D开发工具包(3个版本),java3d-1_3、java3d-1_4、java3d-1_5,加一个例子。例子代码如下: import java.awt.GraphicsConfiguration; import javax.media.j3d.Alpha; import javax.media.j3d.BoundingSphere; import javax.media.j3d.BranchGroup; import javax.media.j3d.Canvas3D; import javax.media.j3d.RotationInterpolator; import javax.media.j3d.Transform3D; import javax.media.j3d.TransformGroup; import javax.swing.JApplet; import javax.vecmath.Point3d; import com.sun.j3d.utils.geometry.ColorCube; import com.sun.j3d.utils.universe.SimpleUniverse; import com.sun.j3d.utils.universe.ViewingPlatform; public class HelloUniverse extends JApplet { public BranchGroup createSceneGraph() { BranchGroup objRoot = new BranchGroup(); //新建一个变形组结点,初始化它 //变形组结点指定一个单一的空间变形,通过一个Transform3D对象, //能对它的子结点定位,定向,定尺寸 TransformGroup objTrans = new TransformGroup(); //指定结点可以写入它的对象的变形信息。这样我们的行为代码就能在运行进修改它。 objTrans.setCapability( TransformGroup.ALLOW_TRANSFORM_WRITE); //根节结中加入这个变形组结点 objRoot.addChild(objTrans); //新建一个例子Shape3D结点,并把它加入到场景图表中 objTrans.addChild(new ColorCube(0.4)); Transform3D yAxis = new Transform3D(); /* * 提供转换一个时间值到一个0到1之间的alpha值的方法 * 第一个参数loopCount:运行循环次数,-1表示无限循环 * 第二个参数increasingAlphaDuration:alpha值从0到1的时间周期 */ Alpha rotationAlpha = new Alpha(-1, 4000); //新建旋转者,它将执行变形操作 RotationInterpolator rotator = new RotationInterpolator( rotationAlpha, objTrans, yAxis, 0.0f, (float) Math.PI*2.0f); //用一个中心点和半径定义一个状范围区域。 BoundingSphere bounds = new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0); //设置旋转者的运行范围 rotator.setSchedulingBounds(bounds); //加它到根结点 objRoot.addChild(rotator); //执行优化 objRoot.compile(); return objRoot;
基本释义 积分 integrating sphere 具有高反射性内表面的空心体。 用来对处于内或放在外并靠近某个窗口处的试样对光的散射或发射进行收集的一种高效率器件。 上的小窗口可以让光进入并与检测器靠得较近。 积分又称为光通,是一个中空的完整壳。内壁涂白色漫反射层,且内壁各点漫射均匀。光源S在壁上任意一点B上产生的光照度是由多次反射光产生的光照度叠加而成的。 积分的涂层 积分内壁涂层反射率ρ(λ)和积分等效透过率τ(λ)是积分最重要的质量指标。 反射率:在给定方向照射下,物体反射到空间的辐射通量与与入射物体表面辐射通量之比 编辑本段光学光学(optics),是研究光(电磁波)的行为和性质,以及光和物质相互作用的物理学科。传统的光学只研究可见光,现代光学已扩展到对全波段电磁波的研究。光是一种电磁波,在物理学中,电磁波由电动力学中的麦克斯韦方程组描述;同时,光具有波粒二象性,需要用量子力学表达。 学科发现 光学的起源在西方很早就有光学知识的记载,欧几里得(Euclid,公元前约330~260)的<反射光学>(Catoptrica)研究了光的反射;阿拉伯学者阿勒·哈增(AI-Hazen,965~1038)写过一部<光学全书>,讨论了许多光学的现象。 历史发展 光学是一门有悠久历史的学科,它的发展史可追溯到2000多年前。 人类对光的研究,最初主要是试图回答“人怎么能看见周围的物体?”之类问题。约在公元前400多年(先秦时代),中国的《墨经》中记录了世界上最早的光学知识。它有八条关于光学的记载,叙述影的定义和生成,光的直线传播性和针孔成像,并且以严谨的文字讨论了在平面镜、凹面镜和凸面镜中物和像的关系。 自《墨经》开始,公元11世纪阿拉伯人伊本·海赛木发明透镜;公元1590年到17世纪初,詹森和李普希同时独立地发明显微镜;一直到17世纪上半叶,才由斯涅耳和笛卡儿将光的反射和折射的观察结果,归结为今天大家所惯用的反射定律和折射定律。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值