Files
speckle-server/packages/viewer/src/modules/SectionBox.js
T

335 lines
10 KiB
JavaScript

import * as THREE from 'three'
import SelectionHelper from './SelectionHelper'
/**
* Section box helper for Speckle Viewer
*
*/
// indices to verts in this.boxGeo - box edges
const edges = [
[0,1], [1,3],
[3,2], [2,0],
[4,6], [6,7],
[7,5], [5,4],
[2,7], [0,5],
[1,4], [3,6]
]
export default class SectionBox {
constructor(viewer, _vis){
//defaults to invisible
let vis = _vis || false
this.viewer = viewer
this.display = new THREE.Group()
this.display.visible = vis
this.displayBox = new THREE.Group()
this.displayEdges = new THREE.Group()
this.displayHover = new THREE.Group()
this.display.add(this.displayBox)
this.display.add(this.displayEdges)
this.display.add(this.displayHover)
this.viewer.scene.add(this.display)
// basic display of the section box
this.boxMaterial = new THREE.MeshBasicMaterial({
// transparent:true,
// color: 0xffe842,
// opacity: 0.5
})
// the box itself
this.boxGeo = new THREE.BoxGeometry(2,2,2)
this.boxMesh = new THREE.Mesh(this.boxGeo, this.boxMaterial)
this.boxMesh.visible = false
this.boxMesh.geometry.computeBoundingBox();
this.boxMesh.geometry.computeBoundingSphere();
this.displayBox.add(this.boxMesh)
this.lineMaterial = new THREE.LineDashedMaterial({
color: 0x000000,
linewidth: 4,
})
// show box edges
edges.map(val => {
let pts = [this.boxGeo.vertices[val[0]].clone(),
this.boxGeo.vertices[val[1]].clone()]
let geo = new THREE.BufferGeometry().setFromPoints(pts)
let line = new THREE.Line(geo, this.lineMaterial)
this.displayEdges.add(line)
})
// normal of plane being hovered
this.hoverPlane = new THREE.Vector3()
this.selectionHelper = new SelectionHelper( this.viewer, {subset:this.displayBox, hover:true} )
// pointer position
this.pointer = new THREE.Vector3()
this.dragging = false
// planes face inward
// indices correspond to vertex indices on the boxGeometry
// constant is set to 1 + epsilon to prevent planes from clipping section box display
this.planes = [
{
axis: '+x', // right, x positive
plane:new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 1 ),
indices: [5,4,6,7],
},{
axis: '-x', // left, x negative
plane: new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 1 ),
indices: [0,1,3,2],
},{
axis: '+y', // out, y positive
plane:new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), 1 ),
indices: [2,3,6,7],
},{
axis: '-y', // in, y negative
plane:new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 1 ),
indices: [5,4,1,0],
},{
axis: '+z', // up, z positive
plane:new THREE.Plane( new THREE.Vector3( 0, 0, 1 ), 1 ),
indices: [1,3,6,4],
},{
axis: '-z', // down, z negative
plane:new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 1 ),
indices: [0,2,7,5],
}];
// plane helpers
// this.planeHelpers = this.planes.map( p => this.display.add(new THREE.PlaneHelper( p.plane, 2, 0x000000 ) ));
// adds clipping planes to all materials
// better to add clipping planes to renderer
this.viewer.renderer.localClippingEnabled = true
let objs = this.viewer.sceneManager.objects
objs.forEach( obj => {
obj.material.clippingPlanes = this.planes.map( c => c.plane )
} )
this.hoverMat = new THREE.MeshStandardMaterial( {
transparent: true,
opacity: 0.6,
color: 0xffe842,
// color: 0xE91E63,
metalness: 0.1,
roughness: 0.75,
} );
// hovered event handler
this.selectionHelper.on('hovered', (obj, e) => {
if(obj.length === 0 && !this.dragging) {
this.displayHover.clear()
this.hoverPlane = new THREE.Vector3()
this.viewer.controls.enabled = true
this.viewer.renderer.domElement.style.cursor = 'default'
return;
} else if (this.dragging){
return
}
this.viewer.renderer.domElement.style.cursor = 'pointer';
let index = this.planes.findIndex(p => p.plane.normal.equals(obj[0].face.normal.clone().negate()))
if(index < 0) return // this should never be the case?
let planeObj = this.planes[index]
let plane = planeObj.plane
if(plane.normal.equals(this.hoverPlane)) return
this.hoverPlane = plane.normal.clone()
this.updateHover(planeObj)
})
// Selection Helper seems unecessary for this type of thing
this.viewer.renderer.domElement.addEventListener('pointerup', (e) => {
this.pointer = new THREE.Vector3()
this.tempVerts = []
this.viewer.controls.enabled = true
this.dragging = false
})
// get screen space vector of plane normal
// project mouse displacement vector onto it
// move plane by that much
this.selectionHelper.on('object-drag', (obj, e) => {
// exit if we don't have a valid hoverPlane
if(this.hoverPlane.equals(new THREE.Vector3())) return
// exit if we're clicking on nothing
if(!obj.length && !this.dragging) return
this.viewer.controls.enabled = false
this.viewer.renderer.domElement.style.cursor = 'move';
this.dragging = true
let index = this.planes.findIndex(p => p.plane.normal.equals(this.hoverPlane))
let planeObj = this.planes[index]
let plane = planeObj.plane
if(this.pointer.equals(new THREE.Vector3())) {
this.pointer = new THREE.Vector3(e.x, e.y, 0.0)
}
// screen space normal vector
// bad transformations of camera can corrupt this
let ssNorm = plane.normal.clone()
ssNorm.negate().project(this.viewer.camera)
ssNorm.setComponent(2, 0).normalize()
// mouse displacement
let mD = this.pointer.clone().sub(new THREE.Vector3(e.x, e.y, 0.0))
this.pointer = new THREE.Vector3(e.x, e.y, 0.0)
// quantity of mD on ssNorm
let d = (ssNorm.dot(mD) / ssNorm.lengthSq())
// configurable drag speed
let zoom = this.viewer.camera.getWorldPosition(new THREE.Vector3()).sub(new THREE.Vector3()).length()
zoom *= 0.75
d = d * zoom
// limit plane from crossing it's pair
let hoverOpp = this.hoverPlane.clone().negate()
let indexOpp = this.planes.findIndex(p => p.plane.normal.equals(hoverOpp))
let planeObjOpp = this.planes[indexOpp]
let dist = planeObj.plane.constant + planeObjOpp.plane.constant
let displacement = new THREE.Vector3(d,d,d).multiply(plane.normal)
// are we moving towards the limiting plane?
let dot = displacement.clone().normalize().dot(plane.normal)
// if displacement + padding is greater than limit,
// and we're moving towards the limiting plane
if(dist < (d + 10) && dot > 0) {
d = dist * 0.001
displacement = new THREE.Vector3(d,d,d).multiply(plane.normal)
}
plane.translate(displacement)
this.updateBoxFace(planeObj, displacement)
this.updateHover(planeObj)
})
}
// boxMesh = bbox
setFromBbox(bbox){
console.log("hi")
for(let p of this.planes) {
// reset plane
p.plane.set(p.plane.normal, 1)
let c = 0
// planes point inwards - if negative select max part of bbox
if(p.plane.normal.dot(new THREE.Vector3(1,1,1)) > 0){
c = p.plane.normal.clone().multiply(bbox.min).length()
} else {
c = p.plane.normal.clone().multiply(bbox.max).length()
}
// calculate displacement
let d = p.plane.normal.clone().negate().multiplyScalar(c)
// update boxMesh
this.updateBoxFace(p, d)
// translate plane
p.plane.translate(d)
}
}
updateBoxFace(planeObj, displacement){
this.boxMesh.geometry.vertices.map((v,i) => {
if(!planeObj.indices.includes(i)) return
this.boxMesh.geometry.vertices[i].add(displacement)
})
this.boxMesh.geometry.verticesNeedUpdate = true
this.boxMesh.geometry.computeBoundingBox();
this.boxMesh.geometry.computeBoundingSphere();
this.updateEdges()
}
updateEdges(){
this.displayEdges.clear()
edges.map(val => {
let ptA = this.boxMesh.geometry.vertices[val[0]].clone()
let ptB = this.boxMesh.geometry.vertices[val[1]].clone()
// translation
ptA.add(this.boxMesh.position)
ptB.add(this.boxMesh.position)
this.drawLine([ptA, ptB])
})
}
drawLine(pts){
let geo = new THREE.BufferGeometry().setFromPoints(pts)
let line = new THREE.Line(geo, this.lineMaterial)
this.displayEdges.add(line)
}
updateHover(planeObj){
this.displayHover.clear()
let verts = this.boxMesh.geometry.vertices.filter((v, i) => planeObj.indices.includes(i))
let centroid = verts[0].clone()
.add(verts[1])
.add(verts[2])
.add(verts[3])
centroid.multiplyScalar(0.25)
let dims = verts[0].clone().sub(centroid).multiplyScalar(2).toArray().filter(v=> v !== 0)
let width = Math.abs(dims[0])
let height = Math.abs(dims[1])
let hoverGeo = new THREE.PlaneGeometry(width, height)
// orients hover geometry to box face
switch(planeObj.axis){
case '-x':
hoverGeo.rotateY(Math.PI / 2)
hoverGeo.rotateX(Math.PI / 2)
break
case '+x':
hoverGeo.rotateY(-Math.PI / 2)
hoverGeo.rotateX(-Math.PI / 2)
break
case '-y':
hoverGeo.rotateX(- Math.PI / 2)
break
case '+y':
hoverGeo.rotateX(Math.PI / 2)
break
default:
break
}
// translation
centroid.add(this.boxMesh.position)
hoverGeo.translate(centroid.x, centroid.y, centroid.z)
let hoverMesh = new THREE.Mesh(hoverGeo, this.hoverMat)
this.displayHover.add(hoverMesh)
}
toggleSectionBox(_bool){
let bool = _bool || !this.visible
this.visible = bool
this.display.visible = bool
// what's the tradeoff for having the clipping planes in material vs in the renderer?
// this.viewer.renderer.clippingPlanes = bool ? this.planes.reduce((p,c) => [...p,c.plane],[]) : []
}
}