Fixed the underlying issue. When the camera moves inside the bounds of a TAS batch object and we can no longer get a min dist from that batch object, we just use the fallback emipiric value. Also added an option to display TAS BVH for debuging purposes

This commit is contained in:
AlexandruPopovici
2024-07-08 13:44:59 +03:00
parent f37dce3539
commit e23d8cdfa0
5 changed files with 69 additions and 71 deletions
+2 -2
View File
@@ -105,7 +105,7 @@ const getStream = () => {
// prettier-ignore
// 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D'
// Revit sample house (good for bim-like stuff with many display meshes)
// 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
// 'https://latest.speckle.dev/streams/c1faab5c62/commits/ab1a1ab2b6'
// 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8'
// 'https://latest.speckle.dev/streams/58b5648c4d/commits/60371ecb2d'
@@ -390,7 +390,7 @@ const getStream = () => {
// 'https://app.speckle.systems/projects/20f72acc58/models/2cf8a736f8'
// Rhino sRGB vertex colors
// 'https://app.speckle.systems/projects/47bbaf594f/models/ef78e94f72'
'https://app.speckle.systems/projects/47bbaf594f/models/de52414725f8937b1f0e2f550ef9ca52'
// 'https://app.speckle.systems/projects/47bbaf594f/models/de52414725f8937b1f0e2f550ef9ca52'
)
}
@@ -604,9 +604,12 @@ export default class SpeckleRenderer {
private addBatch(batch: Batch, parent: Object3D) {
const batchRenderable = batch.renderObject
parent.add(batch.renderObject)
if (batchRenderable instanceof SpeckleMesh) {
parent.add(batchRenderable.TAS.bvhHelper)
parent.add(batchRenderable)
if (
batchRenderable instanceof SpeckleMesh ||
batchRenderable instanceof SpeckleInstancedMesh
) {
if (batchRenderable.TAS.bvhHelper) parent.add(batchRenderable.TAS.bvhHelper)
}
if (batch.geometryType === GeometryType.MESH) {
batchRenderable.traverse((obj: Object3D) => {
@@ -623,11 +626,6 @@ export default class SpeckleRenderer {
)
}
})
const meshRenderable = batchRenderable as SpeckleMesh | SpeckleInstancedMesh
meshRenderable.TAS.boxHelpers.forEach((helper: Box3Helper) => {
this.scene.add(helper)
})
}
this.viewer.World.expandWorld(batch.bounds)
}
@@ -368,18 +368,32 @@ export class CameraController extends Extension implements SpeckleCamera {
}
public updateCameraPlanes(targetVolume?: Box3, offsetScale: number = 1) {
const renderer = this.viewer.getRenderer()
if (!renderer.renderingCamera) return
if (!targetVolume) targetVolume = this.viewer.getRenderer().sceneBox
let nearPlane = this.computeNearCameraPlaneEmpiric(targetVolume, offsetScale)
if (this._options.nearPlaneCalculation === NearPlaneCalculation.ACCURATE)
this.updateNearCameraPlaneAccurate(targetVolume, offsetScale)
else if (this._options.nearPlaneCalculation === NearPlaneCalculation.EMPIRIC)
this.updateNearCameraPlaneEmpiric(targetVolume, offsetScale)
nearPlane = this.computeNearCameraPlaneAccurate(
targetVolume,
offsetScale,
nearPlane
)
if (nearPlane) {
renderer.renderingCamera.near = nearPlane
renderer.renderingCamera.updateProjectionMatrix()
}
this.updateFarCameraPlane()
}
protected updateNearCameraPlaneEmpiric(targetVolume?: Box3, offsetScale: number = 1) {
protected computeNearCameraPlaneEmpiric(
targetVolume?: Box3,
offsetScale: number = 1
): number | undefined {
if (!targetVolume) return
if (targetVolume.isEmpty()) {
Logger.error('Cannot set camera planes for empty volume')
Logger.warn('Cannot set camera planes for empty volume')
return
}
@@ -393,22 +407,17 @@ export class CameraController extends Extension implements SpeckleCamera {
const fitWidthDistance = fitHeightDistance / camAspect
const distance = offsetScale * Math.max(fitHeightDistance, fitWidthDistance)
this._renderingCamera.near =
this._renderingCamera === this.perspectiveCamera ? distance / 100 : 0.001
this._renderingCamera.updateProjectionMatrix()
return this.perspectiveCamera ? distance / 100 : 0.001
}
protected updateNearCameraPlaneAccurate(
protected computeNearCameraPlaneAccurate(
targetVolume?: Box3,
offsetScale: number = 1
) {
const renderer = this.viewer.getRenderer()
if (!renderer.renderingCamera) return
const minDist = this.getClosestGeometryDistance()
offsetScale: number = 1,
fallback?: number
): number | undefined {
const minDist = this.getClosestGeometryDistance(fallback)
if (minDist === Number.POSITIVE_INFINITY) {
this.updateNearCameraPlaneEmpiric(targetVolume, offsetScale)
return
return this.computeNearCameraPlaneEmpiric(targetVolume, offsetScale)
}
const camFov =
@@ -422,9 +431,8 @@ export class CameraController extends Extension implements SpeckleCamera {
Math.pow(Math.tan(((camFov / 180) * Math.PI) / 2), 2) *
(Math.pow(camAspect, 2) + 1)
)
renderer.renderingCamera.near = nearPlane
renderer.renderingCamera.updateProjectionMatrix()
// console.log(minDist, nearPlane)
return nearPlane
}
protected updateFarCameraPlane() {
@@ -455,7 +463,7 @@ export class CameraController extends Extension implements SpeckleCamera {
renderer.renderingCamera.updateProjectionMatrix()
}
protected getClosestGeometryDistance(): number {
protected getClosestGeometryDistance(fallback?: number): number {
const cameraPosition = this._renderingCamera.position
const cameraTarget = this.getTarget()
const cameraDir = new Vector3().subVectors(cameraTarget, cameraPosition).normalize()
@@ -467,7 +475,8 @@ export class CameraController extends Extension implements SpeckleCamera {
for (let b = 0; b < batches.length; b++) {
const result = batches[b].mesh.TAS.closestPointToPointHalfplane(
cameraPosition,
cameraDir
cameraDir,
fallback
)
if (!result) continue
minDist = Math.min(minDist, result.distance)
@@ -2,10 +2,8 @@ import Logger from 'js-logger'
import {
BackSide,
Box3,
Box3Helper,
BufferAttribute,
BufferGeometry,
Color,
DataTexture,
DoubleSide,
FloatType,
@@ -25,7 +23,6 @@ import {
} from 'three'
import { BatchObject } from '../batching/BatchObject'
import Materials from '../materials/Materials'
import { ObjectLayers } from '../../IViewer'
import { TopLevelAccelerationStructure } from './TopLevelAccelerationStructure'
import { SpeckleRaycaster } from './SpeckleRaycaster'
@@ -76,9 +73,6 @@ export default class SpeckleMesh extends Mesh {
public transformsTextureUniform: DataTexture
public transformsArrayUniforms: Matrix4[] | null = null
private debugBatchBox = false
private boxHelper!: Box3Helper
public get TAS(): TopLevelAccelerationStructure {
return this.tas
}
@@ -244,11 +238,6 @@ export default class SpeckleMesh extends Mesh {
this.geometry.boundingBox.copy(this.tas.bounds)
if (!this.geometry.boundingSphere) this.geometry.boundingSphere = new Sphere()
this.geometry.boundingBox.getBoundingSphere(this.geometry.boundingSphere)
if (!this.boxHelper && this.debugBatchBox) {
this.boxHelper = new Box3Helper(this.tas.bounds, new Color(0xff0000))
this.boxHelper.layers.set(ObjectLayers.PROPS)
if (this.parent) this.parent.add(this.boxHelper)
}
}
}
@@ -1,17 +1,12 @@
import {
Box3,
Box3Helper,
BufferAttribute,
BufferGeometry,
Color,
Float32BufferAttribute,
FrontSide,
Material,
Matrix4,
Mesh,
Ray,
Side,
Uint32BufferAttribute,
Vector3
} from 'three'
import { MeshBVHVisualizer } from 'three-mesh-bvh'
@@ -42,7 +37,7 @@ import { AccelerationStructure } from './AccelerationStructure'
fine as it is. If we really really really need that 100% accuracy, we'll just make it relative to the origin
*/
export class TopLevelAccelerationStructure {
private static debugBoxes = false
private debugBVH = false
private static cubeIndices = [
// front
0, 1, 2, 2, 3, 0,
@@ -62,7 +57,6 @@ export class TopLevelAccelerationStructure {
public batchObjects: BatchObject[] = []
public bounds: Box3 = new Box3(new Vector3(0, 0, 0), new Vector3(0, 0, 0))
public boxHelpers: Box3Helper[] = []
public accelerationStructure: AccelerationStructure
public bvhHelper: MeshBVHVisualizer
@@ -91,12 +85,6 @@ export class TopLevelAccelerationStructure {
vertOffset / 3 + TopLevelAccelerationStructure.CUBE_VERTS
vertOffset += TopLevelAccelerationStructure.CUBE_VERTS * 3
if (TopLevelAccelerationStructure.debugBoxes) {
const helper = new Box3Helper(boxBounds, new Color(0xff0000))
helper.layers.set(ObjectLayers.PROPS)
this.boxHelpers.push(helper)
}
}
this.accelerationStructure = new AccelerationStructure(
AccelerationStructure.buildBVH(indices, vertices)
@@ -105,21 +93,16 @@ export class TopLevelAccelerationStructure {
this.accelerationStructure.outputTransform = new Matrix4()
this.accelerationStructure.inputOriginTransform = new Matrix4()
this.accelerationStructure.outputOriginTransfom = new Matrix4()
const geom = new BufferGeometry()
geom.setIndex(new Uint32BufferAttribute(new Uint32Array(indices), 1))
geom.setAttribute(
'position',
new Float32BufferAttribute(new Float32Array(vertices), 3)
)
geom.computeBoundingBox()
const mesh = new Mesh(geom)
mesh.layers.set(ObjectLayers.OVERLAY)
mesh.geometry.boundsTree = this.accelerationStructure.bvh
this.bvhHelper = new MeshBVHVisualizer(mesh)
this.bvhHelper.layers.set(ObjectLayers.OVERLAY)
this.bvhHelper.children[0].layers.set(ObjectLayers.OVERLAY)
this.bvhHelper.update()
if (this.debugBVH) {
const mesh = new Mesh(this.accelerationStructure.geometry)
mesh.layers.set(ObjectLayers.OVERLAY)
mesh.geometry.boundsTree = this.accelerationStructure.bvh
this.bvhHelper = new MeshBVHVisualizer(mesh)
this.bvhHelper.layers.set(ObjectLayers.OVERLAY)
this.bvhHelper.children[0].layers.set(ObjectLayers.OVERLAY)
this.bvhHelper.update()
}
}
private updateVertArray(box: Box3, offset: number, outPositions: number[]) {
@@ -165,8 +148,6 @@ export class TopLevelAccelerationStructure {
const basBox =
this.batchObjects[k].accelerationStructure.getBoundingBox(boxBuffer)
this.updateVertArray(basBox, start * 3, positions)
if (TopLevelAccelerationStructure.debugBoxes) this.boxHelpers[k].box.copy(basBox)
}
this.accelerationStructure.bvh.refit()
}
@@ -349,6 +330,7 @@ export class TopLevelAccelerationStructure {
public closestPointToPointHalfplane(
point: Vector3,
planeNormal: Vector3,
fallback?: number,
target: HitPointInfo = {
point: new Vector3(),
distance: 0,
@@ -380,6 +362,26 @@ export class TopLevelAccelerationStructure {
intersectsBounds: (_box: Box3, _isLeaf, score: number) => {
return score < closestDistanceSq && score < maxThresholdSq
},
intersectsRange: (triangleOffset: number) => {
/** The index buffer for the bvh's geometry will *never* be undefined as it uses indexed geometry */
const indexBufferAttribute: BufferAttribute = this.accelerationStructure
.geometry.index as BufferAttribute
const vertIndex = indexBufferAttribute.array[triangleOffset * 3]
const batchObjectIndex = Math.trunc(
vertIndex / TopLevelAccelerationStructure.CUBE_VERTS
)
const batchObject: BatchObject = this.batchObjects[batchObjectIndex]
/** Because we cannot get a proper min distance to geometry when *Inside* the bounds of a batch object's bounds,
* we just use the provided fallback value as min dist. Single meshes made of dijoint sets are particularly susceptible
* to incorrect min dist calculation, unless we go inside their BAS. But we want to avoid that all cost speed reasons
*/
const ret = batchObject.aabb.containsPoint(point)
if (ret && fallback !== undefined) {
closestDistanceSq = fallback * fallback
}
/** We do not interrupt traversal, there might be other closer valid geometry available */
return false
},
intersectsTriangle: (tri, triIndex) => {
tri.closestPointToPoint(point, temp)