一、均值法
我们将所有的顶点相加,然后除以顶点数,这样就能得到一个球心的位置了,然后将所有顶点位置减去球心位置,取最大值,得到半径
/**
* 计算包围盒
*/
BufferGeometry.prototype.computeBoundingSphere = function () {
const length = this._positions.length
const vertexNumber = length / 3
let x_total = 0, y_total = 0, z_total = 0
for (let i = 0; i < length; i += 3) {
x_total += this._positions[i]
y_total += this._positions[i + 1]
z_total += this._positions[i + 2]
}
const centerX = x_total / vertexNumber
const centerY = y_total / vertexNumber
const centerZ = z_total / vertexNumber
let radius = 0
//遍历,获取半径最大值
for (let i = 0; i < length; i += 3) {
const rangeX = this._positions[i] - centerX
const rangeY = this._positions[i + 1] - centerY
const rangeZ = this._positions[i + 2] - centerZ
//计算到中心点距离
const distance = Math.sqrt(Math.max(rangeX * rangeX + rangeY * rangeX + rangeZ * rangeZ, 0))
radius = Math.max(distance, radius)
}
return {
radius: radius,
center: Vector3.fromValues(centerX, centerY, centerZ)
}
}
二 、Ritter方法
首先我们分别找到这个模型在x,y,z正负六个方向上的最远距离的6个点。然后我们分别计算出这三对点之间的长度,也就是x轴向上两个点之间的长度,y轴向上两个点之间的长度,z轴向上两个点之间的长度。我们选取长度最长的那一个作为包围球的直径,以这个长度的两个点的中点作为包围球的球心。
通过上面的方法,我们能近似的求出这个包围球,但是并不能保证模型中的每一个顶点都在这个包围球里面,所以我们还需要对此进行修正。
我们遍历所有的顶点,判断顶点是否在球体里面,如果在里面,则忽略它,如果不在里面,我们按照下面的算法来对球体进行修正,以使的新的球体能够包含这个点。
我们假设当前的球心为O,半径为r。现在我们发现在这个球体之外有一个点P。所以,我们需要对这个包围球体进行修正,以便于将这个点包围在球体里面。为了最紧凑的包围住这个点,我们将点P与球心O连线,交圆与T点。这时,你就会发现,TP就是我们要求的包围球的新直径了,那么球心也就是他们之间的中点了。
求出T的计算比较庞大,所以我们计算新的半径使用下面的方法:
由于P点和O点都是已知的,所以求他们之间的距离比较容易。也就是说新的半径为: (r + OP) * 0.5 有了新的半径之后,我们需要做的就是平移球心点,平移的向量大小刚好就是SP的长度,方向是从O点指向P点,其中S点为PR的中点 所以有了上面的算法,我们依次的遍历所有的点,我们就能够确定一个近似的包围球了。 算法如下:
/**
* 根据Ritter计算包围球
* @returns
*/
BufferGeometry.prototype.computeBoundingSphereRitter = function () {
const length = this._positions.length
//计算,x,y,z方向上最远的点
let xMax = -1000000000, yMax = -1000000000, zMax = -1000000000, xMin = 1000000000, yMin = 1000000000, zMin = 1000000000
let xMax_Index = -1, yMax_Index = -1, zMax_Index = -1, xMin_index = -1, yMin_index = -1, zMin_index = -1
for (let i = 0; i < length; i += 3) {
const x = this._positions[i]
const y = this._positions[i + 1]
const z = this._positions[i + 2]
if (x > xMax) {
xMax = x
xMax_Index = i
}
if (x < xMin) {
xMin = x
xMin_index = i
}
if (y > yMax) {
yMax = y
yMax_Index = i + 1
}
if (y < yMin) {
yMin = x
yMin_index = i + 1
}
if (z > zMax) {
zMax = z
zMax_Index = i + 2
}
if (z < zMin) {
zMin = x
zMin_index = i + 2
}
}
const minXPoint = [this._positions[xMin_index], this._positions[xMin_index + 1], this._positions[xMin_index + 2]]
const minYPoint = [this._positions[yMin_index - 1], this._positions[yMin_index], this._positions[yMin_index + 1]]
const minZPoint = [this._positions[zMin_index - 2], this._positions[zMin_index - 1], this._positions[zMin_index]]
const maxXPoint = [this._positions[xMax_Index], this._positions[xMax_Index + 1], this._positions[xMax_Index + 2]]
const maxYPoint = [this._positions[yMax_Index - 1], this._positions[yMax_Index], this._positions[yMax_Index + 1]]
const maxZPoint = [this._positions[zMax_Index - 2], this._positions[zMax_Index - 1], this._positions[zMax_Index]]
const distanceX = getDistance(maxXPoint, minXPoint)
const distanceY = getDistance(maxYPoint, minYPoint)
const distanceZ = getDistance(maxZPoint, minZPoint)
let maxPoint = maxXPoint
let minPoint = minXPoint
if (distanceX < distanceY) {
maxPoint = maxYPoint
minPoint = minYPoint
if (distanceY < distanceZ) {
maxPoint = maxZPoint
minPoint = minZPoint
}
} else {
if (distanceX < distanceZ) {
maxPoint = maxZPoint
minPoint = minZPoint
}
}
//计算出近似包围球
let center = [(maxPoint[0] + minPoint[0]) * 0.5, (maxPoint[1] + minPoint[1]) * 0.5, (maxPoint[2] + minPoint[2]) * 0.5]
let radius = Math.max(distanceX, distanceY, distanceZ) * 0.5
//遍历,计算每一个点到当前的距离
for (let i = 0; i < length; i += 3) {
const currentPoint = [this._positions[i], this._positions[i + 1], this._positions[i + 2]]
const distance = getDistance(currentPoint, center)
//矫正
if (distance > radius) {
//计算新的半径
const newRadius = (radius + distance) * 0.5
//平移球心
//计算球心到当前点向量
const vecCenterCurP = Vector3.fromValues(currentPoint[0] - center[0], currentPoint[1] - center[1], currentPoint[2] - center[2])
//归一化算方向
const dir = Vector3.create()
Vector3.normalize(dir, vecCenterCurP)
//球心加向量乘半径即为交点R
const RVector = Vector3.create()
Vector3.scale(RVector, dir, radius)
const RPosition = [center[0] + RVector[0], center[1] + RVector[1], center[2] + RVector[2]]
//计算RP
const RP = [currentPoint[0] - RPosition[0], currentPoint[1] - RPosition[1], currentPoint[2] - RPosition[2]]
const RS = [RP[0] * 0.5, RP[1] * 0.5, RP[2] * 0.5]
//更新球心位置
const newCenter = [center[0] + RS[0], center[1] + RS[1], center[2] + RS[2]]
center = newCenter
radius = newRadius
}
}
return { center, radius }
}
三、结果对比
图中包围球为一个瓦片的包围球,其中黄色为Ritter方法计算的结果,
侧面效果