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

463 lines
14 KiB
JavaScript

import * as THREE from 'three'
import SelectionHelper from './legacy/SelectionHelper'
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'
import { Box3 } from 'three'
import { ViewerEvent } from '../IViewer'
export default class SectionBox {
constructor(viewer) {
this.viewer = viewer
this.viewer.speckleRenderer.renderer.localClippingEnabled = true
this.dragging = false
this.display = new THREE.Group()
this.display.name = 'SectionBox'
this.viewer.speckleRenderer.scene.add(this.display)
// box
this.boxGeometry = this._generateSimpleCube(5, 5, 5)
this.material = new THREE.MeshStandardMaterial({
color: 0x00ffff,
opacity: 0,
wireframe: false,
side: THREE.DoubleSide
})
this.cube = new THREE.Mesh(this.boxGeometry, this.material)
this.cube.visible = false
this.display.add(this.cube)
this.boxHelper = new THREE.BoxHelper(this.cube, 0x0a66ff)
this.boxHelper.material.opacity = 0.4
this.display.add(this.boxHelper)
// we're attaching the gizmo mover to this sphere in the box centre
const sphere = new THREE.SphereGeometry(0.01, 10, 10)
this.sphere = new THREE.Mesh(
sphere,
new THREE.MeshStandardMaterial({ color: 0x00ffff })
)
this.sphere.visible = false
this.display.add(this.sphere)
// plane
this.plane = new THREE.PlaneGeometry(1, 1)
this.hoverPlane = new THREE.Mesh(
this.plane,
new THREE.MeshStandardMaterial({
transparent: true,
side: THREE.DoubleSide,
opacity: 0.1,
wireframe: false,
color: 0x0a66ff,
metalness: 0.1,
roughness: 0.75
})
)
this.hoverPlane.visible = false
this.display.add(this.hoverPlane)
this.dragging = false
this._setupControls()
this.sidesSimple = {
256: { verts: [1, 2, 5, 6], axis: 'x' },
152: { verts: [1, 2, 5, 6], axis: 'x' },
407: { verts: [0, 3, 4, 7], axis: 'x' },
703: { verts: [0, 3, 4, 7], axis: 'x' },
327: { verts: [2, 3, 6, 7], axis: 'y' },
726: { verts: [2, 3, 6, 7], axis: 'y' },
450: { verts: [0, 1, 4, 5], axis: 'y' },
'051': { verts: [0, 1, 4, 5], axis: 'y' },
312: { verts: [0, 1, 3, 2], axis: 'z' },
'013': { verts: [0, 1, 3, 2], axis: 'z' },
546: { verts: [4, 5, 7, 6], axis: 'z' },
647: { verts: [4, 5, 7, 6], axis: 'z' }
}
this._generateOrUpdatePlanes()
this.currentRange = null
this.prevPosition = null
this.attachedToBox = true
this.selectionHelper = new SelectionHelper(this.viewer, {
subset: [this.cube],
hover: false,
checkForSectionBoxInclusion: false
})
this.selectionHelper.on(ViewerEvent.ObjectClicked, this._clickHandler.bind(this))
this.selectionHelper.on('hovered', () => {
// TODO: cannot get this to work reliably
// if( !this.attachedToBox ) return
// if( objs.length === 0 ) {
// this.controls.visible = false
// this.viewer.needsRender = true
// }
// else if( objs.length !== 0 ) {
// this.controls.visible = true
// this.viewer.needsRender = true
// }
})
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.viewer.mouseOverRenderer) {
this._attachControlsToBox()
}
})
this._attachControlsToBox()
this.viewer.on(
'projection-change',
function () {
this._setupControls()
this._attachControlsToBox()
}.bind(this)
)
}
_setupControls() {
this.controls?.dispose()
this.controls?.detach()
this.controls = new TransformControls(
this.viewer.cameraHandler.activeCam.camera,
this.viewer.speckleRenderer.renderer.domElement
)
this.controls.setSize(0.75)
this.display.add(this.controls)
this.controls.addEventListener('change', this._draggingChangeHandler.bind(this))
this.controls.addEventListener('dragging-changed', (event) => {
if (!this.display.visible) return
const val = !!event.value
if (val) {
this.dragging = val
//@Dim: Not sure what this needs to do in the new viewer
//@Alex: this prevents(?) involuntary selection happening on mobile
// this.viewer.interactions.preventSelection = val
this.viewer.cameraHandler.enabled = !val
} else {
setTimeout(() => {
this.dragging = val
//@Dim: Not sure what this needs to do in the new viewer
//@Alex: this prevents(?) involuntary selection happening on mobile
// this.viewer.interactions.preventSelection = val
this.viewer.cameraHandler.enabled = !val
}, 100)
}
})
this.viewer.needsRender = true
}
_draggingChangeHandler() {
if (!this.display.visible) return
this.boxHelper.update()
this._generateOrUpdatePlanes()
// Dragging a side / plane
if (this.dragging && this.currentRange) {
if (this.prevPosition === null)
this.prevPosition = this.hoverPlane.position.clone()
this.prevPosition.sub(this.hoverPlane.position)
this.prevPosition.negate()
const boxArr = this.boxGeometry.attributes.position.array
for (let i = 0; i < this.currentRange.length; i++) {
const index = this.currentRange[i]
boxArr[3 * index] += this.prevPosition.x
boxArr[3 * index + 1] += this.prevPosition.y
boxArr[3 * index + 2] += this.prevPosition.z
}
this.prevPosition = this.hoverPlane.position.clone()
this.boxGeometry.attributes.position.needsUpdate = true
this.boxGeometry.computeVertexNormals()
this.boxGeometry.computeBoundingBox()
this.boxGeometry.computeBoundingSphere()
}
// Dragging the whole section box
if (this.dragging && !this.currentRange) {
if (this.prevPosition === null) this.prevPosition = this.sphere.position.clone()
this.prevPosition.sub(this.sphere.position)
this.prevPosition.negate()
for (let i = 0; i < this.boxGeometry.attributes.position.array.length; i += 3) {
this.boxGeometry.attributes.position.array[i] += this.prevPosition.x
this.boxGeometry.attributes.position.array[i + 1] += this.prevPosition.y
this.boxGeometry.attributes.position.array[i + 2] += this.prevPosition.z
}
this.boxGeometry.attributes.position.needsUpdate = true
this.boxGeometry.computeVertexNormals()
this.boxGeometry.computeBoundingBox()
this.boxGeometry.computeBoundingSphere()
this.prevPosition = this.sphere.position.clone()
}
this.viewer.needsRender = true
}
_clickHandler(args) {
if (this.viewer.cameraHandler.orbiting || this.dragging) return
if (args.length === 0 && !this.dragging) {
this._attachControlsToBox()
this.boxHelper.material.opacity = 0.5
this.attachedToBox = true
return
}
this.attachedToBox = false
this.boxHelper.material.opacity = 0.3
this.hoverPlane.visible = true
const side = this.sidesSimple[`${args[0].face.a}${args[0].face.b}${args[0].face.c}`]
this.controls.showX = side.axis === 'x'
this.controls.showY = side.axis === 'y'
this.controls.showZ = side.axis === 'z'
this.currentRange = side.verts
const boxArr = this.boxGeometry.attributes.position
let index = 0
const planeArr = this.plane.attributes.position.array
const centre = new THREE.Vector3()
const tempArr = []
for (let i = 0; i < planeArr.length; i++) {
if (i % 3 === 0) {
tempArr.push(boxArr.getX(this.currentRange[index]))
} else if (i % 3 === 1) {
tempArr.push(boxArr.getY(this.currentRange[index]))
} else if (i % 3 === 2) {
tempArr.push(boxArr.getZ(this.currentRange[index]))
centre.add(new THREE.Vector3(tempArr[i - 2], tempArr[i - 1], tempArr[i]))
index++
}
}
centre.multiplyScalar(0.25)
this.hoverPlane.position.copy(centre.applyMatrix4(this.cube.matrixWorld))
this.prevPosition = this.hoverPlane.position.clone()
index = 0
for (let i = 0; i < planeArr.length; i++) {
if (i % 3 === 0) {
planeArr[i] = boxArr.getX(this.currentRange[index]) - centre.x
} else if (i % 3 === 1) {
planeArr[i] = boxArr.getY(this.currentRange[index]) - centre.y
} else if (i % 3 === 2) {
planeArr[i] = boxArr.getZ(this.currentRange[index]) - centre.z
index++
}
}
this.plane.applyMatrix4(this.cube.matrixWorld)
this.plane.attributes.position.needsUpdate = true
this.plane.computeBoundingSphere()
this.plane.computeBoundingBox()
this.controls.detach()
this.controls.attach(this.hoverPlane)
this.controls.updateMatrixWorld()
}
_generateSimpleCube(width = 0.5, depth = 0.5, height = 0.5) {
const vertices = [
[-1 * width, -1 * depth, -1 * height],
[1 * width, -1 * depth, -1 * height],
[1 * width, 1 * depth, -1 * height],
[-1 * width, 1 * depth, -1 * height],
[-1 * width, -1 * depth, 1 * height],
[1 * width, -1 * depth, 1 * height],
[1 * width, 1 * depth, 1 * height],
[-1 * width, 1 * depth, 1 * height]
]
const indexes = [
0, 1, 3, 3, 1, 2, 1, 5, 2, 2, 5, 6, 5, 4, 6, 6, 4, 7, 4, 0, 7, 7, 0, 3, 3, 2, 7,
7, 2, 6, 4, 5, 0, 0, 5, 1
]
const positions = []
for (const vert of vertices) {
positions.push(...vert)
}
const g = new THREE.BufferGeometry()
g.setAttribute(
'position',
new THREE.BufferAttribute(new Float32Array(positions), 3)
)
g.setIndex(indexes)
g.computeVertexNormals()
return g
}
_generateOrUpdatePlanes() {
this.planes = this.planes || [
new THREE.Plane(),
new THREE.Plane(),
new THREE.Plane(),
new THREE.Plane(),
new THREE.Plane(),
new THREE.Plane()
]
let index = 0
const boxArr = this.boxGeometry.attributes.position
const indexes = [
0, 1, 3, 3, 1, 2, 1, 5, 2, 2, 5, 6, 5, 4, 6, 6, 4, 7, 4, 0, 7, 7, 0, 3, 3, 2, 7,
7, 2, 6, 4, 5, 0, 0, 5, 1
]
for (let i = 0; i < indexes.length; i += 6) {
const a = new THREE.Vector3(
boxArr.getX(indexes[i]),
boxArr.getY(indexes[i]),
boxArr.getZ(indexes[i])
)
const b = new THREE.Vector3(
boxArr.getX(indexes[i + 1]),
boxArr.getY(indexes[i + 1]),
boxArr.getZ(indexes[i + 1])
)
const c = new THREE.Vector3(
boxArr.getX(indexes[i + 2]),
boxArr.getY(indexes[i + 2]),
boxArr.getZ(indexes[i + 2])
)
const plane = this.planes[index]
plane.setFromCoplanarPoints(a, b, c)
index++
}
}
_attachControlsToBox() {
this.controls.detach()
const centre = new THREE.Vector3()
const boxArr = this.boxGeometry.attributes.position.array
for (let i = 0; i < boxArr.length; i += 3) {
centre.add(new THREE.Vector3(boxArr[i], boxArr[i + 1], boxArr[i + 2]))
}
centre.multiplyScalar(1 / 8)
this.sphere.position.copy(centre)
this.cube.geometry.computeBoundingSphere()
this.cube.geometry.computeBoundingBox()
this.controls.attach(this.sphere)
this.currentRange = null
this.prevPosition = null
this.hoverPlane.visible = false
this.controls.showX = true
this.controls.showY = true
this.controls.showZ = true
}
// setBoxFromObjects(objectIds: string[], offset = 0.05) {
// WorldTree.getInstance().walk() => Solved
// this.setBox(...)
// }
setBox(targetBox, offset = 0.05) {
let box
if (targetBox) box = targetBox // targetbox = { min: {x, y, z}, max: {x, y, z} }
else {
// // @Alex: part of the old behaviour: if a selected object is present, we set the box to it
// // if no selection is present, we set the box to whole scene. TBD re API, etc.
// /* //@Dim: Not sure what this needs to do in the new viewer
// if (this.viewer.interactions.selectedObjects.children.length !== 0) {
// box = new THREE.Box3().setFromObject(this.viewer.interactions.selectedObjects)
// } else*/ if (this.viewer.speckleRenderer.allObjects.children.length !== 0) {
// box = new THREE.Box3().setFromObject(this.viewer.speckleRenderer.allObjects)
// } else {
box = new Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1))
}
if (box.min.x === Infinity) {
box = new Box3(new THREE.Vector3(-1, -1, -1), new THREE.Vector3(1, 1, 1))
}
const x1 = box.min.x - (box.max.x - box.min.x) * offset
const y1 = box.min.y - (box.max.y - box.min.y) * offset
const z1 = box.min.z - (box.max.z - box.min.z) * offset
const x2 = box.max.x + (box.max.x - box.min.x) * offset
const y2 = box.max.y + (box.max.y - box.min.y) * offset
const z2 = box.max.z + (box.max.z - box.min.z) * offset
const newVertices = [
x1,
y1,
z1,
x2,
y1,
z1,
x2,
y2,
z1,
x1,
y2,
z1,
x1,
y1,
z2,
x2,
y1,
z2,
x2,
y2,
z2,
x1,
y2,
z2
]
const boxVerts = this.boxGeometry.attributes.position.array
for (let i = 0; i < newVertices.length; i++) {
boxVerts[i] = newVertices[i]
}
this.boxGeometry.attributes.position.needsUpdate = true
this.boxGeometry.computeVertexNormals()
this.boxGeometry.computeBoundingBox()
this.boxGeometry.computeBoundingSphere()
this._generateOrUpdatePlanes()
this._attachControlsToBox()
this.boxHelper.update()
this.viewer.needsRender = true
}
toggle() {
// This will not get set internally anymore
// this.setBox()
this.display.visible = !this.display.visible
this.viewer.speckleRenderer.renderer.localClippingEnabled = this.display.visible
this.viewer.needsRender = true
}
off() {
this.display.visible = false
this.viewer.speckleRenderer.renderer.localClippingEnabled = false
this.viewer.needsRender = true
}
on() {
this.display.visible = true
this.viewer.speckleRenderer.renderer.localClippingEnabled = true
this.viewer.needsRender = true
}
displayOff() {
this.display.visible = false
}
displayOn() {
this.display.visible = true
}
getCurrentBox() {
if (!this.display.visible) return null
const box = new THREE.Box3().setFromBufferAttribute(
this.boxGeometry.attributes.position
)
return box
}
}