#828 Implemented the SpeckleMesh which extends three's Mesh and corrects the raycasting implementation to work with the positions encoded as pairs of lows and highs

This commit is contained in:
AlexandruPopovici
2022-07-27 21:46:03 +03:00
parent 9e7c80bb4f
commit 3ca2d08e18
10 changed files with 426 additions and 28 deletions
@@ -0,0 +1,382 @@
import {
BackSide,
DoubleSide,
Matrix4,
Mesh,
Ray,
Sphere,
Triangle,
Vector2,
Vector3
} from 'three'
const _inverseMatrix = new Matrix4()
const _ray = new Ray()
const _sphere = new Sphere()
const _vTemp = new Vector3()
const _vA = new Vector3()
const _vB = new Vector3()
const _vC = new Vector3()
const _tempA = new Vector3()
const _tempB = new Vector3()
const _tempC = new Vector3()
const _morphA = new Vector3()
const _morphB = new Vector3()
const _morphC = new Vector3()
const _uvA = new Vector2()
const _uvB = new Vector2()
const _uvC = new Vector2()
const _intersectionPoint = new Vector3()
const _intersectionPointWorld = new Vector3()
export default class SpeckleMesh extends Mesh {
raycast(raycaster, intersects) {
const geometry = this.geometry
const material = this.material
const matrixWorld = this.matrixWorld
if (material === undefined) return
// Checking boundingSphere distance to ray
if (geometry.boundingSphere === null) geometry.computeBoundingSphere()
_sphere.copy(geometry.boundingSphere)
_sphere.applyMatrix4(matrixWorld)
if (raycaster.ray.intersectsSphere(_sphere) === false) return
//
_inverseMatrix.copy(matrixWorld).invert()
_ray.copy(raycaster.ray).applyMatrix4(_inverseMatrix)
// Check boundingBox before continuing
if (geometry.boundingBox !== null) {
if (_ray.intersectsBox(geometry.boundingBox) === false) return
}
let intersection
const index = geometry.index
/** Stored high component if RTE is being used. Regular positions otherwise */
const position = geometry.attributes.position
/** Stored low component if RTE is being used. undefined otherwise */
const positionLow = geometry.attributes['position_low']
const morphPosition = geometry.morphAttributes.position
const morphTargetsRelative = geometry.morphTargetsRelative
const uv = geometry.attributes.uv
const uv2 = geometry.attributes.uv2
const groups = geometry.groups
const drawRange = geometry.drawRange
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 start = Math.max(group.start, drawRange.start)
const end = Math.min(
index.count,
Math.min(group.start + group.count, drawRange.start + drawRange.count)
)
for (let j = start, jl = end; j < jl; j += 3) {
const a = index.getX(j)
const b = index.getX(j + 1)
const c = index.getX(j + 2)
intersection = checkBufferGeometryIntersection(
this,
groupMaterial,
raycaster,
_ray,
positionLow,
position,
morphPosition,
morphTargetsRelative,
uv,
uv2,
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 {
const start = Math.max(0, drawRange.start)
const end = Math.min(index.count, drawRange.start + drawRange.count)
for (let i = start, il = end; i < il; i += 3) {
const a = index.getX(i)
const b = index.getX(i + 1)
const c = index.getX(i + 2)
intersection = checkBufferGeometryIntersection(
this,
material,
raycaster,
_ray,
positionLow,
position,
morphPosition,
morphTargetsRelative,
uv,
uv2,
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 start = Math.max(group.start, drawRange.start)
const end = Math.min(
position.count,
Math.min(group.start + group.count, drawRange.start + drawRange.count)
)
for (let j = start, jl = end; j < jl; j += 3) {
const a = j
const b = j + 1
const c = j + 2
intersection = checkBufferGeometryIntersection(
this,
groupMaterial,
raycaster,
_ray,
positionLow,
position,
morphPosition,
morphTargetsRelative,
uv,
uv2,
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 {
const start = Math.max(0, drawRange.start)
const end = Math.min(position.count, drawRange.start + drawRange.count)
for (let i = start, il = end; i < il; i += 3) {
const a = i
const b = i + 1
const c = i + 2
intersection = checkBufferGeometryIntersection(
this,
material,
raycaster,
_ray,
positionLow,
position,
morphPosition,
morphTargetsRelative,
uv,
uv2,
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 !== DoubleSide, 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,
point: _intersectionPointWorld.clone(),
object,
uv: undefined,
uv2: undefined,
face: undefined
}
}
/** If the geometry is non double->2floats encoded, the `positionHigh` argument will actually
* hold the default `position` attribute values
*/
function checkBufferGeometryIntersection(
object,
material,
raycaster,
ray,
positionLow,
positionHigh,
morphPosition,
morphTargetsRelative,
uv,
uv2,
a,
b,
c
) {
_vA.fromBufferAttribute(positionHigh, a)
_vB.fromBufferAttribute(positionHigh, b)
_vC.fromBufferAttribute(positionHigh, c)
if (positionLow) {
_vA.add(_vTemp.fromBufferAttribute(positionLow, a))
_vB.add(_vTemp.fromBufferAttribute(positionLow, b))
_vC.add(_vTemp.fromBufferAttribute(positionLow, c))
}
const morphInfluences = object.morphTargetInfluences
if (morphPosition && morphInfluences) {
_morphA.set(0, 0, 0)
_morphB.set(0, 0, 0)
_morphC.set(0, 0, 0)
for (let i = 0, il = morphPosition.length; i < il; i++) {
const influence = morphInfluences[i]
const morphAttribute = morphPosition[i]
if (influence === 0) continue
_tempA.fromBufferAttribute(morphAttribute, a)
_tempB.fromBufferAttribute(morphAttribute, b)
_tempC.fromBufferAttribute(morphAttribute, c)
if (morphTargetsRelative) {
_morphA.addScaledVector(_tempA, influence)
_morphB.addScaledVector(_tempB, influence)
_morphC.addScaledVector(_tempC, influence)
} else {
_morphA.addScaledVector(_tempA.sub(_vA), influence)
_morphB.addScaledVector(_tempB.sub(_vB), influence)
_morphC.addScaledVector(_tempC.sub(_vC), influence)
}
}
_vA.add(_morphA)
_vB.add(_morphB)
_vC.add(_morphC)
}
if (object.isSkinnedMesh) {
object.boneTransform(a, _vA)
object.boneTransform(b, _vB)
object.boneTransform(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.getUV(
_intersectionPoint,
_vA,
_vB,
_vC,
_uvA,
_uvB,
_uvC,
new Vector2()
)
}
if (uv2) {
_uvA.fromBufferAttribute(uv2, a)
_uvB.fromBufferAttribute(uv2, b)
_uvC.fromBufferAttribute(uv2, c)
intersection.uv2 = Triangle.getUV(
_intersectionPoint,
_vA,
_vB,
_vC,
_uvA,
_uvB,
_uvC,
new Vector2()
)
}
const face = {
a,
b,
c,
normal: new Vector3(),
materialIndex: 0
}
Triangle.getNormal(_vA, _vB, _vC, face.normal)
intersection.face = face
}
return intersection
}