#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
+1 -1
View File
@@ -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'
)
@@ -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++) {
@@ -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 }
@@ -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))
}
@@ -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 -> <float low; float high> encoding.
*/
public static DoubleToHighLowVector(input: Vector3, low: Vector3, high: Vector3) {
let doubleValue = input.x
@@ -1,7 +1,7 @@
export const speckleBasicVert = /* glsl */ `
#include <common>
#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 <skinning_vertex>
// #include <project_vertex> 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.);
@@ -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 <skinning_vertex>
//#include <project_vertex> 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.);
@@ -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 <displacementmap_vertex>
//#include <project_vertex> // 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.);
@@ -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 <displacementmap_vertex>
//#include <project_vertex> // 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.);
@@ -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
}