OpenGL 与 3D第 4 / 4 章
合并网格的射线检测
在合并后的 Mesh 上恢复原始网格边界和三角面拾取能力。
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 }