Files
speckle-server/packages/viewer/src/modules/SectionBox.js
T
2021-02-06 19:33:39 -05:00

355 lines
11 KiB
JavaScript

import * as THREE from 'three'
import SelectionHelper from './SelectionHelper'
/**
* Class that implements a section box
* _bbox is optional parameter that sets initial size
*
* Manipulating the section box (scale, rotate, translate)
* Is all done by maniuplating the vertices
*
*/
// indices to verts in this.boxGeo
// these allow for drawing of 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]
]
// constants to make code more concise
const X = new THREE.Vector3(1,0,0)
const Y = new THREE.Vector3(0,1,0)
const Z = new THREE.Vector3(0,0,1)
export default class SectionBox {
constructor(viewer){
this.viewer = viewer
this.display = new THREE.Group()
this.display.name = 'section-box'
this.displayBox = new THREE.Group()
this.displayEdges = new THREE.Group()
this.displayHover = new THREE.Group()
// this.displayStencils = new THREE.Group()
this.display.add(this.displayBox)
this.display.add(this.displayEdges)
this.display.add(this.displayHover)
// this.display.add(this.displayStencils)
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 // surprised raycasting still works when 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
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 ) ));
this.viewer.renderer.localClippingEnabled = true
// adds clipping planes to all materials
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))
// 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
let displacement = new THREE.Vector3(d,d,d).multiply(plane.normal).multiplyScalar(zoom)
plane.translate(displacement)
this.updateBoxFace(planeObj, displacement)
this.pointer = new THREE.Vector3(e.x, e.y, 0.0)
this.updateHover(planeObj)
})
}
setFromBbox(bbox){
// console.log(this.boxMesh)
let bboxCurr = this.boxMesh.geometry.boundingBox.clone()
if(bboxCurr.containsBox(bbox)) return
// let bboxExpand = new THREE.Box3(bboxCurr.max.sub(bbox.max),
// bboxCurr.min.sub(bbox.min))
for(let p of this.planes) {
let c = 0
// planes point in - 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)
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)
}
// https://github.com/mrdoob/three.js/blob/master/examples/webgl_clipping_stencil.html
createPlaneStencilGroup( geometry, plane, renderOrder ) {
const group = new THREE.Group();
const baseMat = new THREE.MeshBasicMaterial();
baseMat.depthWrite = false;
baseMat.depthTest = false;
baseMat.colorWrite = false;
baseMat.stencilWrite = true;
baseMat.stencilFunc = THREE.AlwaysStencilFunc;
// back faces
const mat0 = baseMat.clone();
mat0.side = THREE.BackSide;
mat0.clippingPlanes = [ plane ];
mat0.stencilFail = THREE.IncrementWrapStencilOp;
mat0.stencilZFail = THREE.IncrementWrapStencilOp;
mat0.stencilZPass = THREE.IncrementWrapStencilOp;
const mesh0 = new THREE.Mesh( geometry, mat0 );
mesh0.renderOrder = renderOrder;
group.add( mesh0 );
// front faces
const mat1 = baseMat.clone();
mat1.side = THREE.FrontSide;
mat1.clippingPlanes = [ plane ];
mat1.stencilFail = THREE.DecrementWrapStencilOp;
mat1.stencilZFail = THREE.DecrementWrapStencilOp;
mat1.stencilZPass = THREE.DecrementWrapStencilOp;
const mesh1 = new THREE.Mesh( geometry, mat1 );
mesh1.renderOrder = renderOrder;
group.add( mesh1 );
return group;
}
}