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
好了,今天到这里就结束了。以后会陆陆续续的解说其它的包围体的用法,希望大家喜欢!!!!
3D空间包围球(Bounding Sphere)的求法的更多相关文章
- 3D空间中的AABB(轴向平行包围盒, Aixe align bounding box)的求法
引言 在前面的一篇文章中讲述了怎样通过模型的顶点来求的模型的包围球,而且还讲述了基本包围体除了包围球之外,还有AABB包围盒.在这一章,将讲述怎样依据模型的坐标求得它的AABB盒. 表示方法 AABB ...
- WebGL简易教程(十二):包围球与投影
目录 1. 概述 2. 实现详解 3. 具体代码 4. 参考 1. 概述 在之前的教程中,都是通过物体的包围盒来设置模型视图投影矩阵(MVP矩阵),来确定物体合适的位置的.但是在很多情况下,使用包围盒 ...
- Construct Bounding Sphere
点集的包围球 http://en.wikipedia.org/wiki/Bounding_sphere http://blogs.agi.com/insight3d/index.php/2008/02 ...
- NeHe OpenGL教程 第五课:3D空间
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- AABB包围盒、OBB包围盒、包围球的比較
1) AABB 包围盒: AABB 包围盒是与坐标轴对齐的包围盒, 简单性好, 紧密性较差(尤其对斜对角方向放置的瘦长形对象, 採用AABB, 将留下非常大的边角空隙, 导致大量不是必需的包围盒相交測 ...
- Direct3D 11 Tutorial 4: 3D Spaces_Direct3D 11 教程4:3D空间
概述 在上一个教程中,我们在应用程序窗口的中心成功渲染了一个三角形. 我们没有太注意我们在顶点缓冲区中拾取的顶点位置. 在本教程中,我们将深入研究3D位置和转换的细节. 本教程的结果将是渲染到屏幕的3 ...
- WebGL和ThreeJs学习6--射线法确定3D空间中所选物体
一.在 threejs 中如何确定下图3D空间中鼠标点击位置的 object 对象? 二.射线法确定步骤及代码 //Three.js提供一个射线类Raycaster来拾取场景里面的物体.更方便的使用鼠 ...
- 视觉SLAM的数学基础 第一篇 3D空间的位置表示
视觉SLAM中的数学基础 第一篇 3D空间的位置表示 前言 转眼间一个学期又将过去,距离我上次写<一起做RGBD SLAM>已经半年之久.<一起做>系列反响很不错,主要由于它为 ...
- OpenGL学习进程(9)在3D空间的绘制实例
本节将演示在3D空间中绘制图形的几个简单实例: (1)在3D空间内绘制圆锥体: #include <GL/glut.h> #include <math.h> # ...
随机推荐
- MS SQL Sever数据库还原
一.右键 数据库 二.点击 [还原文件和文件组(E)...],弹出下图的窗口界面 1.在 目标数据库 的输入框填写你的数据库名(注意这是新建一个数据库供还原使用,不能还原到已有的数据库) 三.点击[源 ...
- zookeeper主要使用场景
场景一:有一组服务器向客户端提供某种服务,我们希望客户端每次请求服务端都可以找到服务端集群中某一台服务器,这样服务端就可以向客户端提供客户端所需的服务.对于这种场景,我们的程序中一定有一份这组服务器的 ...
- Swift中可选型的Optional Chaining 和 Nil-Coalesce(Swift2.1)
/* 下面是介绍Optional Chaining 和 Nil-Coalesce */ // Optional Chaining (可选链) if let errorMessage = errorMe ...
- js 浏览器版本检测
整理了一下浏览器检测的js脚本 分享给大家 浏览器检测一般都是在网页打开的时候执行 使用js的闭包来实现页面加载以后执行的脚本 (function(){ //页面加载后执行的脚本 })() ; 检测浏 ...
- 取消IDEA中光标“指哪打哪”模式
很简单,在Settings->Editor里面去掉Allow placement of caret after end of line
- 记录Linux下安装elasticSearch时遇到的一些错误
记录Linux下安装elasticSearch时遇到的一些错误 http://blog.sina.com.cn/s/blog_c90ce4e001032f7w.html (2016-11-02 22: ...
- 使用github创建博客
本文主要介绍以下几个内容: 1.使用githbu创建自己的博客 2.将博客域名映射到自己的域名 3.如果写博客 一.使用github创建自己的博客 具体可参考https://pages.githu ...
- DedeCMS中最重要的四类表
栏目(类别): dede_arctype (dede数据库设计者认为:不管你是存放什么样的数据(软件,商品,电影..)都应该属于某个栏目(类型)) 内容主表:dede_archives (织梦数据库的 ...
- mysql 获取当前日期及格式化
MYSQL 获取当前日期及日期格式获取系统日期: NOW()格式化日期: DATE_FORMAT(date, format)注: date:时间字段format:日期格式 返回系统日期,输出 2009 ...
- Java内部类和外部类的通信探索
1.内部类访问外部类的成员和方法 在内部类中,可以无障碍地访问外部类的所有成员和方法. 在下面的实验代码中,可以看到,内部类sl可以访问外部类的私有成员:sz 和 cur. 同时可以访问私有方法:pr ...