From 3ca2d08e18ccaf1cfe484e2e98f5dbc7e2ca196f Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Wed, 27 Jul 2022 21:46:03 +0300 Subject: [PATCH] #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 --- packages/viewer-sandbox/src/main.ts | 2 +- .../viewer/src/modules/batching/Batcher.ts | 12 +- .../viewer/src/modules/batching/LineBatch.ts | 6 +- .../viewer/src/modules/batching/MeshBatch.ts | 7 +- .../viewer/src/modules/converter/Geometry.ts | 9 +- .../materials/shaders/speckle-basic-vert.ts | 9 +- .../materials/shaders/speckle-lambert-vert.ts | 9 +- .../shaders/speckle-standard-colored-vert.ts | 9 +- .../shaders/speckle-standard-vert.ts | 9 +- .../viewer/src/modules/objects/SpeckleMesh.ts | 382 ++++++++++++++++++ 10 files changed, 426 insertions(+), 28 deletions(-) create mode 100644 packages/viewer/src/modules/objects/SpeckleMesh.ts diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index 306effeb7..0750ac923 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -40,5 +40,5 @@ sandbox.makeSceneUI() sandbox.makeFilteringUI() // Load demo object sandbox.loadUrl( - 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce?c=%5B44.9036,-24.475,37.28273,46.97876,-2.90317,-1.54942,0,1%5D' + 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' ) diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts index 167438635..db13a23df 100644 --- a/packages/viewer/src/modules/batching/Batcher.ts +++ b/packages/viewer/src/modules/batching/Batcher.ts @@ -9,7 +9,7 @@ import { NodeRenderView } from '../tree/NodeRenderView' import { Batch, BatchUpdateRange, GeometryType } from './Batch' import PointBatch from './PointBatch' import { FilterMaterialType } from '../FilteringManager' -import { Material, WebGLRenderer } from 'three' +import { WebGLRenderer } from 'three' import { FilterMaterial } from '../FilteringManager' export default class Batcher { @@ -170,8 +170,14 @@ export default class Batcher { const batchIds = [...Array.from(new Set(rvs.map((value) => value.batchId)))] for (const k in this.batches) { if (!batchIds.includes(k)) { - ;(this.batches[k].renderObject as unknown as { material: Material }).material = - this.materials.getGhostMaterial(this.batches[k].renderViews[0]) + this.batches[k].setDrawRanges({ + offset: 0, + count: Infinity, + material: this.materials.getFilterMaterial( + this.batches[k].renderViews[0], + FilterMaterialType.GHOST + ) + }) } else { const drawRanges = [] for (let i = 0; i < this.batches[k].renderViews.length; i++) { diff --git a/packages/viewer/src/modules/batching/LineBatch.ts b/packages/viewer/src/modules/batching/LineBatch.ts index c797df68a..5a449af17 100644 --- a/packages/viewer/src/modules/batching/LineBatch.ts +++ b/packages/viewer/src/modules/batching/LineBatch.ts @@ -24,7 +24,7 @@ export default class LineBatch implements Batch { public batchMaterial: SpeckleLineMaterial private mesh: LineSegments2 | Line public colorBuffer: InstancedInterleavedBuffer - public static vectorBuffer: Vector4 = new Vector4() + private static readonly vector4Buffer: Vector4 = new Vector4() public constructor(id: string, renderViews: NodeRenderView[]) { this.id = id @@ -87,11 +87,11 @@ export default class LineBatch implements Batch { ranges[i].offset * this.colorBuffer.stride + ranges[i].count * this.colorBuffer.stride - LineBatch.vectorBuffer.set(color.r, color.g, color.b, 1) + LineBatch.vector4Buffer.set(color.r, color.g, color.b, 1) this.updateColorBuffer( start, ranges[i].count === Infinity ? this.colorBuffer.array.length : len, - LineBatch.vectorBuffer + LineBatch.vector4Buffer ) } this.colorBuffer.updateRange = { offset: 0, count: data.length } diff --git a/packages/viewer/src/modules/batching/MeshBatch.ts b/packages/viewer/src/modules/batching/MeshBatch.ts index 824c117b1..e503462ac 100644 --- a/packages/viewer/src/modules/batching/MeshBatch.ts +++ b/packages/viewer/src/modules/batching/MeshBatch.ts @@ -4,7 +4,6 @@ import { DynamicDrawUsage, Float32BufferAttribute, Material, - Mesh, Object3D, Uint16BufferAttribute, Uint32BufferAttribute, @@ -12,6 +11,7 @@ import { } from 'three' import { Geometry } from '../converter/Geometry' import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredMaterial' +import SpeckleMesh from '../objects/SpeckleMesh' import { NodeRenderView } from '../tree/NodeRenderView' import { World } from '../World' import { Batch, BatchUpdateRange, HideAllBatchUpdateRange } from './Batch' @@ -21,7 +21,7 @@ export default class MeshBatch implements Batch { public renderViews: NodeRenderView[] private geometry: BufferGeometry public batchMaterial: Material - public mesh: Mesh + public mesh: SpeckleMesh private gradientIndexBuffer: BufferAttribute public constructor(id: string, renderViews: NodeRenderView[]) { @@ -257,7 +257,7 @@ export default class MeshBatch implements Batch { position, this.batchMaterial.vertexColors ? color : null ) - this.mesh = new Mesh(this.geometry, this.batchMaterial) + this.mesh = new SpeckleMesh(this.geometry, this.batchMaterial) this.mesh.uuid = this.id } @@ -285,6 +285,7 @@ export default class MeshBatch implements Batch { } if (position) { + /** When RTE enabled, we'll be storing the high component of the encoding here */ this.geometry.setAttribute('position', new Float32BufferAttribute(position, 3)) } diff --git a/packages/viewer/src/modules/converter/Geometry.ts b/packages/viewer/src/modules/converter/Geometry.ts index 74d05851c..2dbbe561d 100644 --- a/packages/viewer/src/modules/converter/Geometry.ts +++ b/packages/viewer/src/modules/converter/Geometry.ts @@ -33,13 +33,10 @@ export class Geometry { ) { if (geometry.type === 'BufferGeometry') { const position_low = new Float32Array(doublePositions.length) - const position_high = new Float32Array(doublePositions.length) + /** We'll store the high component of the encoding inside three's default `position` attribute */ + const position_high = geometry.attributes.position.array as Float32Array Geometry.DoubleToHighLowBuffer(doublePositions, position_low, position_high) geometry.setAttribute('position_low', new Float32BufferAttribute(position_low, 3)) - geometry.setAttribute( - 'position_high', - new Float32BufferAttribute(position_high, 3) - ) } else if ( geometry.type === 'LineGeometry' || geometry.type === 'LineSegmentsGeometry' @@ -191,7 +188,7 @@ export class Geometry { } /** Please see https://speckle.systems/blog/improving-speckles-rte-implementation/ for additional details - * regarding double -> float low; float high encoding. + * regarding double -> encoding. */ public static DoubleToHighLowVector(input: Vector3, low: Vector3, high: Vector3) { let doubleValue = input.x diff --git a/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts index 77e3986af..6a161ca03 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts @@ -1,7 +1,7 @@ export const speckleBasicVert = /* glsl */ ` #include #ifdef USE_RTE - attribute vec3 position_high; + // The high component is stored as the default 'position' attribute buffer attribute vec3 position_low; uniform vec3 uViewer_high; uniform vec3 uViewer_low; @@ -32,10 +32,13 @@ void main() { #include // #include COMMENTED CHUNK #ifdef USE_RTE - /** Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl */ + /* + Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl + Note here, we're storing the high part of the position encoding inside three's default 'position' attribute buffer so we avoid redundancy + */ vec3 t1 = position_low.xyz - uViewer_low; vec3 e = t1 - position_low.xyz; - vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position_high.xyz - uViewer_high; + vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position.xyz - uViewer_high; vec3 highDifference = t1 + t2; vec3 lowDifference = t2 - (highDifference - t1); vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.); diff --git a/packages/viewer/src/modules/materials/shaders/speckle-lambert-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-lambert-vert.ts index abe31a037..10205d03e 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-lambert-vert.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-lambert-vert.ts @@ -1,7 +1,7 @@ export const speckleLambertVert = /* glsl */ ` #define LAMBERT #ifdef USE_RTE - attribute vec3 position_high; + // The high component is stored as the default 'position' attribute buffer attribute vec3 position_low; uniform vec3 uViewer_high; uniform vec3 uViewer_low; @@ -41,10 +41,13 @@ void main() { #include //#include COMMENTED CHUNK #ifdef USE_RTE - /** Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl */ + /* + Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl + Note here, we're storing the high part of the position encoding inside three's default 'position' attribute buffer so we avoid redundancy + */ vec3 t1 = position_low.xyz - uViewer_low; vec3 e = t1 - position_low.xyz; - vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position_high.xyz - uViewer_high; + vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position.xyz - uViewer_high; vec3 highDifference = t1 + t2; vec3 lowDifference = t2 - (highDifference - t1); vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.); diff --git a/packages/viewer/src/modules/materials/shaders/speckle-standard-colored-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-standard-colored-vert.ts index e6ab98ac2..ad7c54688 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-standard-colored-vert.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-standard-colored-vert.ts @@ -1,7 +1,7 @@ export const speckleStandardColoredVert = /* glsl */ ` #define STANDARD #ifdef USE_RTE - attribute vec3 position_high; + // The high component is stored as the default 'position' attribute buffer attribute vec3 position_low; uniform vec3 uViewer_high; uniform vec3 uViewer_low; @@ -50,10 +50,13 @@ void main() { #include //#include // EDITED CHUNK #ifdef USE_RTE - /** Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl */ + /* + Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl + Note here, we're storing the high part of the position encoding inside three's default 'position' attribute buffer so we avoid redundancy + */ vec3 t1 = position_low.xyz - uViewer_low; vec3 e = t1 - position_low.xyz; - vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position_high.xyz - uViewer_high; + vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position.xyz - uViewer_high; vec3 highDifference = t1 + t2; vec3 lowDifference = t2 - (highDifference - t1); vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.); diff --git a/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts index 9e67f9646..b62c0faca 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-standard-vert.ts @@ -1,7 +1,7 @@ export const speckleStandardVert = /* glsl */ ` #define STANDARD #ifdef USE_RTE - attribute vec3 position_high; + // The high component is stored as the default 'position' attribute buffer attribute vec3 position_low; uniform vec3 uViewer_high; uniform vec3 uViewer_low; @@ -49,10 +49,13 @@ void main() { #include //#include // EDITED CHUNK #ifdef USE_RTE - /** Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl */ + /* + Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl + Note here, we're storing the high part of the position encoding inside three's default 'position' attribute buffer so we avoid redundancy + */ vec3 t1 = position_low.xyz - uViewer_low; vec3 e = t1 - position_low.xyz; - vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position_high.xyz - uViewer_high; + vec3 t2 = ((-uViewer_low - e) + (position_low.xyz - (t1 - e))) + position.xyz - uViewer_high; vec3 highDifference = t1 + t2; vec3 lowDifference = t2 - (highDifference - t1); vec4 mvPosition = vec4(highDifference.xyz + lowDifference.xyz , 1.); diff --git a/packages/viewer/src/modules/objects/SpeckleMesh.ts b/packages/viewer/src/modules/objects/SpeckleMesh.ts new file mode 100644 index 000000000..196b021b0 --- /dev/null +++ b/packages/viewer/src/modules/objects/SpeckleMesh.ts @@ -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 +}