Files
speckle-server/packages/viewer/src/modules/Intersections.ts
T

313 lines
9.6 KiB
TypeScript

import {
Box3,
Camera,
Object3D,
Plane,
Ray,
Scene,
Vector2,
Vector3,
Vector4
} from 'three'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js'
import {
ExtendedIntersection,
ExtendedMeshIntersection,
SpeckleRaycaster
} from './objects/SpeckleRaycaster.js'
import { ObjectLayers } from '../IViewer.js'
import { World } from './World.js'
export class Intersections {
protected raycaster: SpeckleRaycaster
private boxBuffer: Box3 = new Box3()
private vec0Buffer: Vector4 = new Vector4()
private vec1Buffer: Vector4 = new Vector4()
private boundsBuffer: Box3 = new Box3()
public constructor() {
this.raycaster = new SpeckleRaycaster()
this.raycaster.params.Line = { threshold: 0.01 }
this.raycaster.params.Points = { threshold: 0.01 }
this.raycaster.params.Line2 = { threshold: 1 }
this.raycaster.onObjectIntersectionTest = this.onObjectIntersection.bind(this)
}
protected onObjectIntersection(obj: Object3D) {
if (obj instanceof LineSegments2) {
const box = this.boxBuffer.setFromObject(obj)
const min = this.vec0Buffer.set(box.min.x, box.min.y, box.min.z, 1)
const max = this.vec1Buffer.set(box.max.y, box.max.y, box.max.z, 1)
min
.applyMatrix4(this.raycaster.camera.matrixWorldInverse)
.applyMatrix4(this.raycaster.camera.projectionMatrix)
max
.applyMatrix4(this.raycaster.camera.matrixWorldInverse)
.applyMatrix4(this.raycaster.camera.projectionMatrix)
min
.multiplyScalar(0.5)
.multiplyScalar(1 / min.w)
.addScalar(0.5)
max
.multiplyScalar(0.5)
.multiplyScalar(1 / max.w)
.addScalar(0.5)
const ssDistance = new Vector2()
.set(min.x, min.y)
.distanceTo(new Vector2(max.x, max.y))
const mat: LineMaterial = obj.material
const lineWidth = mat.linewidth
const worldSpace = mat.worldUnits
/** So we empirically adjust the threshold of each line(batch) based on it's
* original line width and how zoomed in the camer is on the line(batch)
*/
if (!worldSpace) {
this.raycaster.params.Line2.threshold =
ssDistance < 1 ? lineWidth * 8 : lineWidth * 5
} else {
this.raycaster.params.Line2.threshold =
ssDistance < 1 ? lineWidth * 2 : lineWidth
}
}
}
public intersect(
scene: Scene,
camera: Camera,
point: Vector2,
castLayers: ObjectLayers.STREAM_CONTENT_MESH,
nearest?: boolean,
bounds?: Box3,
firstOnly?: boolean,
tasOnly?: boolean
): Array<ExtendedMeshIntersection> | null
public intersect(
scene: Scene,
camera: Camera,
point: Vector2,
castLayers?: Array<ObjectLayers>,
nearest?: boolean,
bounds?: Box3,
firstOnly?: boolean,
tasOnly?: boolean
): Array<ExtendedIntersection> | null
public intersect(
scene: Scene,
camera: Camera,
point: Vector2,
castLayers: Array<ObjectLayers> | ObjectLayers | undefined = undefined,
nearest = true,
bounds?: Box3,
firstOnly = false,
tasOnly = false
): Array<ExtendedMeshIntersection> | Array<ExtendedIntersection> | null {
this.raycaster.setFromCamera(point, camera)
this.raycaster.firstHitOnly = firstOnly
this.raycaster.intersectTASOnly = tasOnly
const preserveMask = this.setRaycasterLayers(castLayers)
let result: Array<ExtendedMeshIntersection> | Array<ExtendedIntersection> | null
if (castLayers === ObjectLayers.STREAM_CONTENT_MESH) {
result = this.intersectInternal<ExtendedMeshIntersection>(scene, nearest, bounds)
} else result = this.intersectInternal<ExtendedIntersection>(scene, nearest, bounds)
this.raycaster.layers.mask = preserveMask
return result
}
public intersectRay(
scene: Scene,
camera: Camera,
ray: Ray,
castLayers: ObjectLayers.STREAM_CONTENT_MESH,
nearest?: boolean,
bounds?: Box3,
firstOnly?: boolean,
tasOnly?: boolean
): Array<ExtendedMeshIntersection> | null
public intersectRay(
scene: Scene,
camera: Camera,
ray: Ray,
castLayers?: Array<ObjectLayers>,
nearest?: boolean,
bounds?: Box3,
firstOnly?: boolean,
tasOnly?: boolean
): Array<ExtendedIntersection> | null
public intersectRay(
scene: Scene,
camera: Camera,
ray: Ray,
castLayers: Array<ObjectLayers> | ObjectLayers | undefined = undefined,
nearest = true,
bounds?: Box3,
firstOnly = false,
tasOnly = false
): Array<ExtendedMeshIntersection> | Array<ExtendedIntersection> | null {
this.raycaster.camera = camera
this.raycaster.set(ray.origin, ray.direction)
this.raycaster.firstHitOnly = firstOnly
this.raycaster.intersectTASOnly = tasOnly
const preserveMask = this.setRaycasterLayers(castLayers)
let result: Array<ExtendedMeshIntersection> | Array<ExtendedIntersection> | null
if (castLayers === ObjectLayers.STREAM_CONTENT_MESH) {
result = this.intersectInternal<ExtendedMeshIntersection>(scene, nearest, bounds)
} else result = this.intersectInternal<ExtendedIntersection>(scene, nearest, bounds)
this.raycaster.layers.mask = preserveMask
return result
}
private setRaycasterLayers(
castLayers: Array<ObjectLayers> | ObjectLayers | undefined
): number {
const preserveMask = this.raycaster.layers.mask
if (castLayers !== undefined) {
this.raycaster.layers.disableAll()
if (Array.isArray(castLayers))
castLayers.forEach((layer) => {
this.raycaster.layers.enable(layer)
})
else {
this.raycaster.layers.enable(castLayers)
}
}
return preserveMask
}
private intersectInternal<T extends ExtendedIntersection>(
scene: Scene,
nearest?: boolean,
bounds?: Box3
): T[] | null {
let results: T[] | null = []
const target = scene.getObjectByName('ContentGroup')
if (target) {
// const start = performance.now()
results = this.raycaster.intersectObjects(target.children)
// Logger.warn('Interesct time -> ', performance.now() - start)
}
if (results.length === 0) return null
if (nearest)
results.sort((a, b) => {
return a.distance - b.distance
})
if (bounds) {
/** We slightly increase the tested bounds to account for fp precision issues which
* have proven to arise exactly at the edge of the bounds. Our BVH returns intersection
* points ever so slightly off the actual surface, so for very thin geometries it might
* fall outside of the bounds
*/
this.boundsBuffer.copy(World.expandBoxRelative(bounds))
results = results.filter((result) => {
return (
this.boundsBuffer.containsPoint(result.point) ||
(result.pointOnLine
? this.boundsBuffer.containsPoint(result.pointOnLine)
: false)
)
})
}
return results
}
public static aabbPlanePoints = (plane: Plane, aabb: Box3) => {
const ray = new Ray()
const outPoints = new Array<Vector3>()
// Test edges along X axis, pointing right.
const dir: Vector3 = new Vector3(aabb.max.x - aabb.min.x, 0, 0)
const orig: Vector3 = new Vector3().copy(aabb.min)
ray.set(orig, dir)
let t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.min.x, aabb.max.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.min.x, aabb.min.y, aabb.max.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.min.x, aabb.max.y, aabb.max.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
// Test edges along Y axis, pointing up.
dir.set(0, aabb.max.y - aabb.min.y, 0)
orig.set(aabb.min.x, aabb.min.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.max.x, aabb.min.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.min.x, aabb.min.y, aabb.max.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.max.x, aabb.min.y, aabb.max.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
// Test edges along Z axis, pointing forward.
dir.set(0, 0, aabb.max.z - aabb.min.z)
orig.set(aabb.min.x, aabb.min.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.max.x, aabb.min.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.min.x, aabb.max.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
orig.set(aabb.max.x, aabb.max.y, aabb.min.z)
ray.set(orig, dir)
t = ray.distanceToPlane(plane)
if (t) {
outPoints.push(new Vector3().copy(orig).addScaledVector(dir, t))
}
return outPoints
}
}