diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 07abf510e..30a64b2cd 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -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' ) } diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 4656cf08d..a7d2d4647 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -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) } diff --git a/packages/viewer/src/modules/extensions/CameraController.ts b/packages/viewer/src/modules/extensions/CameraController.ts index 4e09e96cc..e375f4939 100644 --- a/packages/viewer/src/modules/extensions/CameraController.ts +++ b/packages/viewer/src/modules/extensions/CameraController.ts @@ -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) diff --git a/packages/viewer/src/modules/objects/SpeckleMesh.ts b/packages/viewer/src/modules/objects/SpeckleMesh.ts index cf9231ff7..455b902e7 100644 --- a/packages/viewer/src/modules/objects/SpeckleMesh.ts +++ b/packages/viewer/src/modules/objects/SpeckleMesh.ts @@ -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) - } } } diff --git a/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts b/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts index ea94a041c..d424cf8f5 100644 --- a/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts +++ b/packages/viewer/src/modules/objects/TopLevelAccelerationStructure.ts @@ -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)