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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user