2023-03-17--------------------包围球计算的两种方式

291 阅读1分钟

一、均值法

我们将所有的顶点相加,然后除以顶点数,这样就能得到一个球心的位置了,然后将所有顶点位置减去球心位置,取最大值,得到半径

/**
 * 计算包围盒
 */
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轴向上两个点之间的长度。我们选取长度最长的那一个作为包围球的直径,以这个长度的两个点的中点作为包围球的球心。

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

我们遍历所有的顶点,判断顶点是否在球体里面,如果在里面,则忽略它,如果不在里面,我们按照下面的算法来对球体进行修正,以使的新的球体能够包含这个点。 image.png

我们假设当前的球心为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方法计算的结果, image.png

侧面效果 image.png