Visual LabINTERACTIVE LEARNING
OpenGL 与 3D第 4 / 4 章

合并网格的射线检测

在合并后的 Mesh 上恢复原始网格边界和三角面拾取能力。

Web WorkerglTF性能
专题预计 44 分钟查看源文 ↗

merge-raycast

class MergeMesh extends Mesh {
  constructor(geometry = new BufferGeometry(), material = new MeshBasicMaterial(), originalMeshes = []) {
    super()

    this.isMesh = true

    this.type = 'Mesh'

    this.geometry = geometry
    this.material = material
    // 原始的mesh
    this.originalMeshes = originalMeshes ?? []

    this.updateMorphTargets()
  }

  raycast(raycaster, intersects) {
    // console.log('raycast :>> ')
    const geometry = this.geometry
    const material = this.material
    const matrixWorld = this.matrixWorld

    if (material === undefined) return

    // test with bounding sphere in world space

    if (geometry.boundingSphere === null) geometry.computeBoundingSphere()

    _sphere.copy(geometry.boundingSphere)
    _sphere.applyMatrix4(matrixWorld)

    // check distance from ray origin to bounding sphere

    _ray.copy(raycaster.ray).recast(raycaster.near)

    if (_sphere.containsPoint(_ray.origin) === false) {
      if (_ray.intersectSphere(_sphere, _sphereHitAt) === null) return

      if (_ray.origin.distanceToSquared(_sphereHitAt) > (raycaster.far - raycaster.near) ** 2) return
    }
    // console.log('outer sphere')
    // convert ray to local space of mesh

    _inverseMatrix.copy(matrixWorld).invert()
    _ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix)

    // test with bounding box in local space
    //
    if (geometry.boundingBox !== null) {
      if (_ray.intersectsBox(geometry.boundingBox) === false) return
    }
    // 遍历每个合并前的原始mesh
    if (this.originalMeshes.length > 0){
      for (let i = 0, il = this.originalMeshes.length; i < il; i++) {
        const { boundingBox, boundingSphere } = this.originalMeshes[i]
        const { start, count } = this.geometry.groups[i]
        // 射线检测boundingSphere
        if (boundingSphere !== null) {
          _sphere.copy(boundingSphere)
          _sphere.applyMatrix4(matrixWorld)
          _ray.copy(raycaster.ray).recast(raycaster.near)
          if (_sphere.containsPoint(_ray.origin) === false) {
            if (_ray.intersectSphere(_sphere, _sphereHitAt) === null) continue
            if (_ray.origin.distanceToSquared(_sphereHitAt) > (raycaster.far - raycaster.near) ** 2) continue
          }
        }

        // 射线检测boundingBox
        _inverseMatrix.copy(matrixWorld).invert()
        _ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix)
        // test with bounding box in local space
        if (boundingBox !== null) {
          if (_ray.intersectsBox(boundingBox) === false) continue
        }
        // console.log('inner box')
        // 射线检测每个原始mesh的三角面
        this._computeIntersections(raycaster, intersects, _ray, start, count)
      }
    } else {
      this._computeIntersections(raycaster, intersects, _ray)
    }

    // test for intersections with geometry

  }
  /**
   * 主要改写这个方法,实现合并mesh的射线检测,首先检测每个子mesh的射线检测,
   * 然后合并结果,子mesh的射线检测,需要根据顶点的偏移量,来计算射线检测
   * 也是需要计算距离的
   * 1. 其实每个子mesh的计算过程,都是一样的,都是通过射线检测,然后计算距离,然后判断是否在射线检测的范围内
   * 2. 子mesh的射线检测,需要根据顶点的偏移量,来计算射线检测
   */
  _computeIntersections(raycaster, intersects, rayLocalSpace, start = 0, count = Infinity) {
    let intersection
    const geometry = this.geometry
    const material = this.material

    const index = geometry.index
    const position = geometry.attributes.position
    const uv = geometry.attributes.uv
    const uv1 = geometry.attributes.uv1
    const normal = geometry.attributes.normal
    const groups = geometry.groups
    const drawRange = geometry.drawRange

    const startIndex = Math.max(start, drawRange.start)
    const endIndex = Math.min(start + count, drawRange.start + drawRange.count)

    if (index !== null) {
      // indexed buffer geometry

      if (Array.isArray(material)) {
        for (let i = 0, il = groups.length; i < il; i++) {
          const group = groups[i]
          const groupMaterial = material[group.materialIndex]

          const groupStart = Math.max(group.start, startIndex)
          const groupEnd = Math.min(index.count, Math.min(group.start + group.count, endIndex))

          for (let j = groupStart, jl = groupEnd; j < jl; j += 3) {
            const a = index.getX(j)
            const b = index.getX(j + 1)
            const c = index.getX(j + 2)

            intersection = checkGeometryIntersection(this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c)

            if (intersection) {
              intersection.faceIndex = Math.floor(j / 3) // triangle number in indexed buffer semantics
              intersection.face.materialIndex = group.materialIndex
              intersects.push(intersection)
            }
          }
        }
      } else {
        for (let i = startIndex, il = endIndex; i < il; i += 3) {
          const a = index.getX(i)
          const b = index.getX(i + 1)
          const c = index.getX(i + 2)

          intersection = checkGeometryIntersection(this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c)

          if (intersection) {
            intersection.faceIndex = Math.floor(i / 3) // triangle number in indexed buffer semantics
            intersects.push(intersection)
          }
        }
      }
    } else if (position !== undefined) {
      // non-indexed buffer geometry

      if (Array.isArray(material)) {
        for (let i = 0, il = groups.length; i < il; i++) {
          const group = groups[i]
          const groupMaterial = material[group.materialIndex]

          const groupStart = Math.max(group.start, startIndex)
          const groupEnd = Math.min(position.count, Math.min(group.start + group.count, endIndex))

          for (let j = groupStart, jl = groupEnd; j < jl; j += 3) {
            const a = j
            const b = j + 1
            const c = j + 2

            intersection = checkGeometryIntersection(this, groupMaterial, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c)

            if (intersection) {
              intersection.faceIndex = Math.floor(j / 3) // triangle number in non-indexed buffer semantics
              intersection.face.materialIndex = group.materialIndex
              intersects.push(intersection)
            }
          }
        }
      } else {
        for (let i = startIndex, il = endIndex; i < il; i += 3) {
          const a = i
          const b = i + 1
          const c = i + 2

          intersection = checkGeometryIntersection(this, material, raycaster, rayLocalSpace, uv, uv1, normal, a, b, c)

          if (intersection) {
            intersection.faceIndex = Math.floor(i / 3) // triangle number in non-indexed buffer semantics
            intersects.push(intersection)
          }
        }
      }
    }
  }
}

function checkIntersection(object, material, raycaster, ray, pA, pB, pC, point) {
  let intersect

  if (material.side === BackSide) {
    intersect = ray.intersectTriangle(pC, pB, pA, true, point)
  } else {
    intersect = ray.intersectTriangle(pA, pB, pC, material.side === FrontSide, point)
  }

  if (intersect === null) return null

  _intersectionPointWorld.copy(point)
  _intersectionPointWorld.applyMatrix4(object.matrixWorld)

  const distance = raycaster.ray.origin.distanceTo(_intersectionPointWorld)

  if (distance < raycaster.near || distance > raycaster.far) return null

  return {
    distance: distance,
    point: _intersectionPointWorld.clone(),
    object: object,
  }
}

function checkGeometryIntersection(object, material, raycaster, ray, uv, uv1, normal, a, b, c) {
  object.getVertexPosition(a, _vA)
  object.getVertexPosition(b, _vB)
  object.getVertexPosition(c, _vC)

  const intersection = checkIntersection(object, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint)

  if (intersection) {
    if (uv) {
      _uvA.fromBufferAttribute(uv, a)
      _uvB.fromBufferAttribute(uv, b)
      _uvC.fromBufferAttribute(uv, c)

      intersection.uv = Triangle.getInterpolation(_intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2())
    }

    if (uv1) {
      _uvA.fromBufferAttribute(uv1, a)
      _uvB.fromBufferAttribute(uv1, b)
      _uvC.fromBufferAttribute(uv1, c)

      intersection.uv1 = Triangle.getInterpolation(_intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2())
    }

    if (normal) {
      _normalA.fromBufferAttribute(normal, a)
      _normalB.fromBufferAttribute(normal, b)
      _normalC.fromBufferAttribute(normal, c)

      intersection.normal = Triangle.getInterpolation(_intersectionPoint, _vA, _vB, _vC, _normalA, _normalB, _normalC, new Vector3())

      if (intersection.normal.dot(ray.direction) > 0) {
        intersection.normal.multiplyScalar(-1)
      }
    }

    const face = {
      a: a,
      b: b,
      c: c,
      normal: new Vector3(),
      materialIndex: 0,
    }

    Triangle.getNormal(_vA, _vB, _vC, face.normal)

    intersection.face = face
  }

  return intersection
}

export { MergeMesh }