refactor(viewer): cleanup
This commit is contained in:
@@ -964,7 +964,7 @@ __webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export */ "default": () => /* binding */ InteractionHandler
|
||||
/* harmony export */ });
|
||||
/* harmony import */ var three__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! three */ "./node_modules/three/build/three.module.js");
|
||||
/* harmony import */ var _SectionBox2__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SectionBox2 */ "./src/modules/SectionBox2.js");
|
||||
/* harmony import */ var _SectionBox__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./SectionBox */ "./src/modules/SectionBox.js");
|
||||
/* harmony import */ var _SelectionHelper__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./SelectionHelper */ "./src/modules/SelectionHelper.js");
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
@@ -981,7 +981,7 @@ var InteractionHandler = /*#__PURE__*/function () {
|
||||
_classCallCheck(this, InteractionHandler);
|
||||
|
||||
this.viewer = viewer;
|
||||
this.sectionBox = new _SectionBox2__WEBPACK_IMPORTED_MODULE_1__.default(this.viewer);
|
||||
this.sectionBox = new _SectionBox__WEBPACK_IMPORTED_MODULE_1__.default(this.viewer);
|
||||
this.sectionBox.toggle(); // switch off
|
||||
|
||||
this.preventSelection = false;
|
||||
@@ -1012,16 +1012,16 @@ var InteractionHandler = /*#__PURE__*/function () {
|
||||
}
|
||||
}, {
|
||||
key: "_handleSelect",
|
||||
value: function _handleSelect(obj) {
|
||||
value: function _handleSelect(objs) {
|
||||
if (this.preventSelection) return;
|
||||
|
||||
if (obj.length === 0) {
|
||||
if (objs.length === 0) {
|
||||
this.deselectObjects();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.selectionHelper.multiSelect) this.deselectObjects();
|
||||
var mesh = new three__WEBPACK_IMPORTED_MODULE_0__.Mesh(obj[0].object.geometry, this.selectionMaterial);
|
||||
var mesh = new three__WEBPACK_IMPORTED_MODULE_0__.Mesh(objs[0].object.geometry, this.selectionMaterial);
|
||||
var box = new three__WEBPACK_IMPORTED_MODULE_0__.BoxHelper(mesh, 0x23F3BD);
|
||||
box.material = this.selectionEdgesMaterial;
|
||||
this.selectedObjects.add(mesh);
|
||||
@@ -1042,9 +1042,11 @@ var InteractionHandler = /*#__PURE__*/function () {
|
||||
if (this.sectionBox.display.visible) {
|
||||
if (this.selectedObjects.children.length === 0) {
|
||||
this.sectionBox.setBox(this.viewer.sceneManager.getSceneBoundingBox());
|
||||
this.zoomExtents();
|
||||
} else {
|
||||
var box = new three__WEBPACK_IMPORTED_MODULE_0__.Box3().setFromObject(this.selectedObjects);
|
||||
this.sectionBox.setBox(box);
|
||||
this.zoomToBox(box);
|
||||
}
|
||||
} else {
|
||||
this.preventSelection = false;
|
||||
@@ -1073,6 +1075,11 @@ var InteractionHandler = /*#__PURE__*/function () {
|
||||
}, {
|
||||
key: "zoomExtents",
|
||||
value: function zoomExtents() {
|
||||
if (this.sectionBox.display.visible) {
|
||||
this.zoomToObject(this.sectionBox.boxMesh);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.viewer.sceneManager.objects.length === 0) {
|
||||
var _box = new three__WEBPACK_IMPORTED_MODULE_0__.Box3(new three__WEBPACK_IMPORTED_MODULE_0__.Vector3(-1, -1, -1), new three__WEBPACK_IMPORTED_MODULE_0__.Vector3(1, 1, 1));
|
||||
|
||||
@@ -1572,6 +1579,7 @@ var SceneObjectManager = /*#__PURE__*/function () {
|
||||
key: "_postLoadFunction",
|
||||
value: function _postLoadFunction() {
|
||||
this.viewer.interactions.zoomExtents();
|
||||
this.viewer.interactions.hideSectionBox();
|
||||
this.viewer.reflectionsNeedUpdate = true;
|
||||
}
|
||||
}, {
|
||||
@@ -1635,10 +1643,10 @@ var SceneObjectManager = /*#__PURE__*/function () {
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./src/modules/SectionBox2.js":
|
||||
/*!************************************!*\
|
||||
!*** ./src/modules/SectionBox2.js ***!
|
||||
\************************************/
|
||||
/***/ "./src/modules/SectionBox.js":
|
||||
/*!***********************************!*\
|
||||
!*** ./src/modules/SectionBox.js ***!
|
||||
\***********************************/
|
||||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||||
|
||||
"use strict";
|
||||
@@ -1854,7 +1862,7 @@ var SectionBox = /*#__PURE__*/function () {
|
||||
}, {
|
||||
key: "setBox",
|
||||
value: function setBox(box) {
|
||||
box = box.clone().expandByScalar(1.1);
|
||||
box = box.clone().expandByScalar(0.5);
|
||||
var dimensions = new three__WEBPACK_IMPORTED_MODULE_0__.Vector3().subVectors(box.max, box.min);
|
||||
var boxGeo = new three__WEBPACK_IMPORTED_MODULE_0__.BoxGeometry(dimensions.x, dimensions.y, dimensions.z);
|
||||
var matrix = new three__WEBPACK_IMPORTED_MODULE_0__.Matrix4().setPosition(dimensions.addVectors(box.min, box.max).multiplyScalar(0.5));
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -32,8 +32,8 @@
|
||||
<p>Click an object to select it. Double click it to focus on it. Press `esc` to clear the selection. Press `shift-s` to toggle a section plane. Press `s` while the section plane is active to toggle its control mode. Double click anywhere outside an object to zoom extents to the entire scene.</p>
|
||||
</div>
|
||||
<div class="twelve columns">
|
||||
<button onclick="v.sceneManager.zoomExtents()">Zoom Extents</button>
|
||||
<button onclick="v.postprocessing = !v.postprocessing">Postprocessing Toggle</button>
|
||||
<button onclick="v.interactions.zoomExtents()">Zoom Extents</button>
|
||||
<button onclick="v.interactions.toggleSectionBox()">Toggle Section Box</button>
|
||||
</div>
|
||||
<div class="twelve columns">
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
<p>Click an object to select it. Double click it to focus on it. Press `esc` to clear the selection. Press `shift-s` to toggle a section plane. Press `s` while the section plane is active to toggle its control mode. Double click anywhere outside an object to zoom extents to the entire scene.</p>
|
||||
</div>
|
||||
<div class="twelve columns">
|
||||
<button onclick="v.sceneManager.zoomExtents()">Zoom Extents</button>
|
||||
<button onclick="v.postprocessing = !v.postprocessing">Postprocessing Toggle</button>
|
||||
<button onclick="v.interactions.zoomExtents()">Zoom Extents</button>
|
||||
<button onclick="v.interactions.toggleSectionBox()">Toggle Section Box</button>
|
||||
</div>
|
||||
<div class="twelve columns">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as THREE from 'three'
|
||||
import SectionBox2 from './SectionBox2'
|
||||
import SectionBox from './SectionBox'
|
||||
import SelectionHelper from './SelectionHelper'
|
||||
|
||||
export default class InteractionHandler {
|
||||
@@ -7,7 +7,7 @@ export default class InteractionHandler {
|
||||
constructor( viewer ) {
|
||||
this.viewer = viewer
|
||||
|
||||
this.sectionBox = new SectionBox2( this.viewer )
|
||||
this.sectionBox = new SectionBox( this.viewer )
|
||||
this.sectionBox.toggle() // switch off
|
||||
|
||||
this.preventSelection = false
|
||||
@@ -34,17 +34,17 @@ export default class InteractionHandler {
|
||||
this.viewer.needsRender = true
|
||||
}
|
||||
|
||||
_handleSelect( obj ) {
|
||||
_handleSelect( objs ) {
|
||||
if ( this.preventSelection ) return
|
||||
|
||||
if ( obj.length === 0 ) {
|
||||
if ( objs.length === 0 ) {
|
||||
this.deselectObjects()
|
||||
return
|
||||
}
|
||||
|
||||
if ( !this.selectionHelper.multiSelect ) this.deselectObjects()
|
||||
|
||||
let mesh = new THREE.Mesh( obj[0].object.geometry, this.selectionMaterial )
|
||||
let mesh = new THREE.Mesh( objs[0].object.geometry, this.selectionMaterial )
|
||||
let box = new THREE.BoxHelper( mesh, 0x23F3BD )
|
||||
box.material = this.selectionEdgesMaterial
|
||||
this.selectedObjects.add( mesh )
|
||||
@@ -91,6 +91,10 @@ export default class InteractionHandler {
|
||||
}
|
||||
|
||||
zoomExtents() {
|
||||
if ( this.sectionBox.display.visible ) {
|
||||
this.zoomToObject( this.sectionBox.boxMesh )
|
||||
return
|
||||
}
|
||||
if ( this.viewer.sceneManager.objects.length === 0 ) {
|
||||
let box = new THREE.Box3( new THREE.Vector3( -1,-1,-1 ), new THREE.Vector3( 1,1,1 ) )
|
||||
this.zoomToBox( box )
|
||||
|
||||
@@ -176,6 +176,7 @@ export default class SceneObjectManager {
|
||||
|
||||
_postLoadFunction() {
|
||||
this.viewer.interactions.zoomExtents()
|
||||
this.viewer.interactions.hideSectionBox()
|
||||
this.viewer.reflectionsNeedUpdate = true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,345 +1,244 @@
|
||||
import * as THREE from 'three'
|
||||
// import { DragControls } from 'three/examples/jsm/controls/DragControls.js'
|
||||
|
||||
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 ]
|
||||
]
|
||||
import { TransformControls } from './external/TransformControls.js'
|
||||
|
||||
export default class SectionBox {
|
||||
constructor( viewer, _vis ) {
|
||||
//defaults to invisible
|
||||
let vis = _vis || false
|
||||
|
||||
constructor( viewer, bbox ) {
|
||||
this.viewer = viewer
|
||||
|
||||
this.orbiting = false
|
||||
this.dragging = false
|
||||
this.display = new THREE.Group()
|
||||
this.viewer.controls.addEventListener( 'wake', () => { this.orbiting = true } )
|
||||
this.viewer.controls.addEventListener( 'controlend', () => { this.orbiting = false } )
|
||||
|
||||
this.display = new THREE.Group()
|
||||
this.display.visible = vis
|
||||
this.box = bbox || this.viewer.sceneManager.getSceneBoundingBox()
|
||||
const dimensions = new THREE.Vector3().subVectors( this.box.max, this.box.min )
|
||||
this.boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
|
||||
this.displayBox = new THREE.Group()
|
||||
this.displayEdges = new THREE.Group()
|
||||
this.displayHover = new THREE.Group()
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( this.box.min, this.box.max ).multiplyScalar( 0.5 ) )
|
||||
this.boxGeo.applyMatrix4( matrix )
|
||||
this.boxMesh = new THREE.Mesh( this.boxGeo, new THREE.MeshBasicMaterial() )
|
||||
|
||||
this.display.add( this.displayBox )
|
||||
this.display.add( this.displayEdges )
|
||||
this.display.add( this.displayHover )
|
||||
this.boxHelper = new THREE.BoxHelper( this.boxMesh, 0x0A66FF )
|
||||
|
||||
this.viewer.scene.add( this.display )
|
||||
|
||||
// basic display of the section box
|
||||
this.boxMaterial = new THREE.MeshStandardMaterial( {
|
||||
const plane = new THREE.PlaneGeometry( 1, 1 )
|
||||
this.hoverPlane = new THREE.Mesh( plane, new THREE.MeshStandardMaterial( {
|
||||
transparent: true,
|
||||
opacity: 0.1,
|
||||
side: THREE.DoubleSide,
|
||||
color: 0x0A66FF,
|
||||
metalness: 0.01,
|
||||
roughness: 0.75,
|
||||
} )
|
||||
|
||||
// the box itself
|
||||
this.boxGeo = new THREE.BoxGeometry( 2,2,2 )
|
||||
this.boxMesh = new THREE.Mesh( this.boxGeo, this.boxMaterial )
|
||||
|
||||
this.boxMesh.geometry.computeBoundingBox()
|
||||
this.boxMesh.geometry.computeBoundingSphere()
|
||||
this.displayBox.add( this.boxMesh )
|
||||
|
||||
// const edges = new THREE.EdgesGeometry( this.boxGeo )
|
||||
// this.line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0xDF66FF } ) )
|
||||
|
||||
|
||||
// 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 ), 0 ),
|
||||
indices: [ 5,4,6,7 ],
|
||||
},{
|
||||
axis: '-x', // left, x negative
|
||||
plane: new THREE.Plane( new THREE.Vector3( -1, 0, 0 ), 0 ),
|
||||
indices: [ 0,1,3,2 ],
|
||||
},{
|
||||
axis: '+y', // out, y positive
|
||||
plane:new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), 0 ),
|
||||
indices: [ 2,3,6,7 ],
|
||||
},{
|
||||
axis: '-y', // in, y negative
|
||||
plane:new THREE.Plane( new THREE.Vector3( 0, -1, 0 ), 0 ),
|
||||
indices: [ 5,4,1,0 ],
|
||||
},{
|
||||
axis: '+z', // up, z positive
|
||||
plane:new THREE.Plane( new THREE.Vector3( 0, 0, 1 ), 0 ),
|
||||
indices: [ 1,3,6,4 ],
|
||||
},{
|
||||
axis: '-z', // down, z negative
|
||||
plane:new THREE.Plane( new THREE.Vector3( 0, 0, -1 ), 0 ),
|
||||
indices: [ 0,2,7,5 ],
|
||||
} ]
|
||||
|
||||
// this.planes.forEach( p => {
|
||||
// const helper = new THREE.PlaneHelper( p.plane, 1, 0xffff00 )
|
||||
// this.display.add( helper )
|
||||
// } )
|
||||
|
||||
this.viewer.sceneManager.objects.forEach( obj => {
|
||||
obj.material.clippingPlanes = this.planes.map( c => c.plane )
|
||||
} )
|
||||
|
||||
this.hoverMat = new THREE.MeshStandardMaterial( {
|
||||
transparent: true,
|
||||
opacity: 0.1,
|
||||
opacity: 0.05,
|
||||
color: 0x0A66FF,
|
||||
metalness: 0.1,
|
||||
roughness: 0.75,
|
||||
} )
|
||||
} ) )
|
||||
|
||||
// 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
|
||||
} )
|
||||
this.display.add( this.boxHelper )
|
||||
this.display.add( this.hoverPlane )
|
||||
|
||||
let cuttingPlane = null
|
||||
let hoverPlane = null
|
||||
this.viewer.scene.add( this.display )
|
||||
|
||||
// hovered event handler
|
||||
this.selectionHelper.on( 'hovered', ( obj, e ) => {
|
||||
if ( this.orbiting ) return
|
||||
this.boxMesh.userData.planes = []
|
||||
this.boxMesh.userData.indices = []
|
||||
this.planes = []
|
||||
|
||||
// Gen box and planes
|
||||
this._generatePlanes()
|
||||
|
||||
// Box face selection controls
|
||||
this.selectionHelper = new SelectionHelper( this.viewer, { subset: this.boxMesh, hover: true } )
|
||||
let targetFaceIndex = -1
|
||||
|
||||
this.selectionHelper.on( 'hovered', ( obj ) => {
|
||||
if ( obj.length === 0 && !this.dragging ) {
|
||||
this.hoverPlane.visible = false
|
||||
this.controls.visible = true
|
||||
this.planeControls.detach()
|
||||
this.viewer.controls.enabled = true
|
||||
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 ) {
|
||||
this.viewer.interactions.preventSelection = false
|
||||
this.viewer.needsRender = true
|
||||
targetFaceIndex = -1
|
||||
return
|
||||
}
|
||||
if ( this.orbiting || this.dragging ) return
|
||||
|
||||
this.viewer.controls.enabled = false
|
||||
this.viewer.renderer.domElement.style.cursor = 'pointer'
|
||||
this.controls.visible = false
|
||||
this.hoverPlane.visible = true
|
||||
|
||||
switch ( obj[0].faceIndex ) {
|
||||
case 0: case 1:
|
||||
cuttingPlane = this.planes[0]
|
||||
hoverPlane = this.planes[1]
|
||||
break
|
||||
case 2: case 3:
|
||||
cuttingPlane = this.planes[1]
|
||||
hoverPlane = this.planes[0]
|
||||
break
|
||||
case 4: case 5:
|
||||
cuttingPlane = this.planes[2]
|
||||
hoverPlane = this.planes[3]
|
||||
break
|
||||
case 6: case 7:
|
||||
cuttingPlane = this.planes[3]
|
||||
hoverPlane = this.planes[2]
|
||||
break
|
||||
case 8: case 9:
|
||||
cuttingPlane = this.planes[4]
|
||||
hoverPlane = this.planes[5]
|
||||
break
|
||||
case 10: case 11:
|
||||
cuttingPlane = this.planes[5]
|
||||
hoverPlane = this.planes[4]
|
||||
break
|
||||
let centre = new THREE.Vector3()
|
||||
for ( let i = 0; i < 4; i++ ) {
|
||||
centre.add( this.boxGeo.vertices[ obj[0].object.userData.indices[ obj[0].faceIndex ][i] ].clone().applyMatrix4( this.boxMesh.matrixWorld ) )
|
||||
}
|
||||
centre.multiplyScalar( 0.25 )
|
||||
this.hoverPlane.position.copy( centre )
|
||||
|
||||
for ( let i = 0; i < 4; i++ ) {
|
||||
let vertex = this.boxGeo.vertices[ obj[0].object.userData.indices[ obj[0].faceIndex ][i] ].clone().applyMatrix4( this.boxMesh.matrixWorld )
|
||||
this.hoverPlane.geometry.vertices[i].set( vertex.x - centre.x, vertex.y - centre.y , vertex.z - centre.z )
|
||||
}
|
||||
|
||||
// this.hoverPlane = plane.normal.clone()
|
||||
this.updateHover( hoverPlane )
|
||||
this.hoverPlane.geometry.verticesNeedUpdate = true
|
||||
|
||||
let normal = obj[0].face.normal
|
||||
this.planeControls.showX = normal.x !== 0
|
||||
this.planeControls.showY = normal.y !== 0
|
||||
this.planeControls.showZ = normal.z !== 0
|
||||
|
||||
this.planeControls.attach( this.hoverPlane )
|
||||
|
||||
if ( obj[0].faceIndex !== targetFaceIndex ) {
|
||||
this.viewer.needsRender = true
|
||||
targetFaceIndex = obj[0].faceIndex
|
||||
}
|
||||
} )
|
||||
|
||||
// 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 ) => {
|
||||
if ( this.orbiting ) return
|
||||
if ( !this.display.visible ) return
|
||||
// Whole box controls
|
||||
this._globalControlsTarget = new THREE.Mesh( new THREE.SphereGeometry( 0.0001 ), new THREE.MeshBasicMaterial( ) )
|
||||
this._globalControlsTarget.position.copy( this.boxGeo.vertices[ 5 ].clone().multiplyScalar( 1.1 ) )
|
||||
this.display.add( this._globalControlsTarget )
|
||||
|
||||
// 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.controls = new TransformControls( this.viewer.camera, this.viewer.renderer.domElement )
|
||||
this.controls.setSize( 0.5 )
|
||||
this.controls.attach( this._globalControlsTarget )
|
||||
this.display.add( this.controls )
|
||||
|
||||
this.viewer.controls.enabled = false
|
||||
this.viewer.renderer.domElement.style.cursor = 'move'
|
||||
// Section plane controls
|
||||
this.planeControls = new TransformControls( this.viewer.camera, this.viewer.renderer.domElement, true )
|
||||
this.display.add( this.planeControls )
|
||||
|
||||
this.dragging = true
|
||||
this.prevGizmoPos = this._globalControlsTarget.position.clone()
|
||||
this.controls.addEventListener( 'change', ( ) => {
|
||||
this.prevGizmoPos.sub( this._globalControlsTarget.position )
|
||||
this.boxMesh.translateX( -this.prevGizmoPos.x )
|
||||
this.boxMesh.translateY( -this.prevGizmoPos.y )
|
||||
this.boxMesh.translateZ( -this.prevGizmoPos.z )
|
||||
|
||||
let plane = hoverPlane.plane
|
||||
this.prevGizmoPos = this._globalControlsTarget.position.clone()
|
||||
this.setPlanesFromBox( new THREE.Box3().setFromObject( this.boxMesh ) )
|
||||
this.boxHelper.update()
|
||||
this.viewer.needsRender = true
|
||||
} )
|
||||
|
||||
if ( this.pointer.equals( new THREE.Vector3() ) ) {
|
||||
this.pointer = new THREE.Vector3( e.x, e.y, 0.0 )
|
||||
this.controls.addEventListener( 'dragging-changed', ( event ) => {
|
||||
this.viewer.controls.enabled = !event.value
|
||||
this.viewer.interactions.preventSelection = !event.value
|
||||
if ( !event.value )
|
||||
this.viewer.interactions.zoomToObject( this.boxMesh )
|
||||
} )
|
||||
|
||||
let prevPlaneGizmoPos = null
|
||||
this.planeControls.addEventListener( 'change', ( ) => {
|
||||
if ( !this.dragging ) return
|
||||
if ( targetFaceIndex === -1 ) return
|
||||
if ( prevPlaneGizmoPos === null ) prevPlaneGizmoPos = this.hoverPlane.position.clone()
|
||||
prevPlaneGizmoPos.sub( this.hoverPlane.position )
|
||||
let plane = this.boxMesh.userData.planes[ targetFaceIndex ]
|
||||
|
||||
prevPlaneGizmoPos.negate()
|
||||
plane.translate( prevPlaneGizmoPos )
|
||||
let indices = this.boxMesh.userData.indices[ targetFaceIndex ]
|
||||
for ( let i = 0; i < 4; i++ ) {
|
||||
let index = indices[i]
|
||||
this.boxGeo.vertices[index].add( prevPlaneGizmoPos )
|
||||
}
|
||||
this.boxGeo.verticesNeedUpdate = true
|
||||
this.boxMesh.geometry.computeBoundingBox()
|
||||
this.boxMesh.geometry.computeBoundingSphere()
|
||||
|
||||
// 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()
|
||||
let gizmoPos = this.boxGeo.vertices[ 5 ].clone()
|
||||
gizmoPos.multiplyScalar( 1.1 )
|
||||
gizmoPos.applyMatrix4( this.boxMesh.matrixWorld )
|
||||
this._globalControlsTarget.position.copy( gizmoPos )
|
||||
this.prevGizmoPos = gizmoPos
|
||||
|
||||
// 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 )
|
||||
prevPlaneGizmoPos = this.hoverPlane.position.clone()
|
||||
this.boxHelper.update()
|
||||
this.viewer.needsRender = true
|
||||
} )
|
||||
|
||||
// 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 planeObjOpp = cuttingPlane
|
||||
let dist = hoverPlane.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 ( dist < 0.1 && dot < 0 ) return
|
||||
|
||||
|
||||
cuttingPlane.plane.translate( displacement )
|
||||
this.updateBoxFace( hoverPlane, displacement )
|
||||
this.updateHover( hoverPlane )
|
||||
this.planeControls.addEventListener( 'dragging-changed', ( event ) => {
|
||||
this.viewer.controls.enabled = !event.value
|
||||
this.viewer.interactions.preventSelection = !event.value
|
||||
this.dragging = !!event.value
|
||||
if ( !this.dragging ) {
|
||||
prevPlaneGizmoPos = null
|
||||
this.viewer.interactions.zoomToObject( this.boxMesh )
|
||||
targetFaceIndex = -1
|
||||
|
||||
}
|
||||
this.viewer.needsRender = true
|
||||
} )
|
||||
}
|
||||
|
||||
// boxMesh = bbox
|
||||
setFromBbox( bbox, offset ){
|
||||
bbox = bbox.clone()
|
||||
_generatePlanes() {
|
||||
for ( let i = 0; i < this.boxGeo.faces.length; i += 2 ) {
|
||||
let face = this.boxGeo.faces[i]
|
||||
let pairFace = this.boxGeo.faces[i+1]
|
||||
let plane = new THREE.Plane()
|
||||
// inverting points so plane
|
||||
plane.setFromCoplanarPoints( this.boxGeo.vertices[face.c], this.boxGeo.vertices[face.b], this.boxGeo.vertices[face.a] )
|
||||
// adding it twice for ease of use
|
||||
this.boxMesh.userData.planes.push( plane )
|
||||
this.boxMesh.userData.planes.push( plane )
|
||||
|
||||
// add a little padding to the box
|
||||
const size = bbox.getSize( new THREE.Vector3() )
|
||||
if ( offset )
|
||||
bbox.expandByVector( size.multiplyScalar( offset ) )
|
||||
this.boxMesh.userData.indices.push( [ face.a, face.b, face.c, pairFace.b ] )
|
||||
this.boxMesh.userData.indices.push( [ face.a, face.b, face.c, pairFace.b ] )
|
||||
|
||||
const dimensions = new THREE.Vector3().subVectors( bbox.max, bbox.min )
|
||||
const boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( bbox.min, bbox.max ).multiplyScalar( 0.5 ) )
|
||||
this.planes.push( plane )
|
||||
}
|
||||
}
|
||||
|
||||
setPlanesFromBox( box ) {
|
||||
const dimensions = new THREE.Vector3().subVectors( box.max, box.min )
|
||||
let boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( box.min, box.max ).multiplyScalar( 0.5 ) )
|
||||
boxGeo.applyMatrix4( matrix )
|
||||
|
||||
let k = 0
|
||||
for ( let i = 0; i < boxGeo.faces.length; i += 2 ) {
|
||||
let plane = this.planes[k]
|
||||
for ( let i = 0; i < this.boxGeo.faces.length; i += 2 ) {
|
||||
let face = boxGeo.faces[i]
|
||||
let plane = this.boxMesh.userData.planes[i]
|
||||
plane.setFromCoplanarPoints( boxGeo.vertices[face.c], boxGeo.vertices[face.b], boxGeo.vertices[face.a] ) // invert pts
|
||||
}
|
||||
}
|
||||
|
||||
plane.plane.setFromCoplanarPoints( boxGeo.vertices[face.c], boxGeo.vertices[face.b], boxGeo.vertices[face.a] )
|
||||
k++
|
||||
}
|
||||
|
||||
// update box geometry
|
||||
for ( let i = 0; i< boxGeo.vertices.length; i++ ) {
|
||||
let vert = boxGeo.vertices[i]
|
||||
this.boxMesh.geometry.vertices[i].set( vert.x, vert.y, vert.z )
|
||||
setBox( box ) {
|
||||
box = box.clone().expandByScalar( 0.5 )
|
||||
const dimensions = new THREE.Vector3().subVectors( box.max, box.min )
|
||||
let boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( box.min, box.max ).multiplyScalar( 0.5 ) )
|
||||
boxGeo.applyMatrix4( matrix )
|
||||
|
||||
for ( let i = 0; i < this.boxGeo.vertices.length; i++ ) {
|
||||
this.boxGeo.vertices[i].copy( boxGeo.vertices[i] )
|
||||
}
|
||||
|
||||
this._globalControlsTarget.position.copy( this.boxGeo.vertices[ 5 ].clone().multiplyScalar( 1.1 ) )
|
||||
this.prevGizmoPos = this._globalControlsTarget.position.clone()
|
||||
this.boxMesh.position.copy( new THREE.Vector3() )
|
||||
this.boxMesh.geometry.verticesNeedUpdate = true
|
||||
this.boxMesh.geometry.computeBoundingBox()
|
||||
this.boxMesh.geometry.computeBoundingSphere()
|
||||
|
||||
const edges = new THREE.EdgesGeometry( this.boxMesh.geometry )
|
||||
const line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0xffffff } ) )
|
||||
this.displayEdges.add( line )
|
||||
this.boxHelper.update()
|
||||
this.setPlanesFromBox( box )
|
||||
this.viewer.needsRender = true
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
toggle() {
|
||||
if ( this.display.visible ) {
|
||||
this.viewer.renderer.localClippingEnabled = false
|
||||
this.display.visible = false
|
||||
} else {
|
||||
this.viewer.renderer.localClippingEnabled = true
|
||||
this.display.visible = true
|
||||
}
|
||||
|
||||
// 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 )
|
||||
this.viewer.needsRender = true
|
||||
}
|
||||
|
||||
toggleSectionBox( _bool ){
|
||||
let bool = _bool || !this.visible
|
||||
this.visible = bool
|
||||
this.display.visible = bool
|
||||
this.viewer.needsRender = true
|
||||
dispose() {
|
||||
this.selectionHelper.dispose()
|
||||
this.controls.dispose()
|
||||
this.planeControls.dispose()
|
||||
this.display.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,245 +0,0 @@
|
||||
import * as THREE from 'three'
|
||||
import SelectionHelper from './SelectionHelper'
|
||||
import { TransformControls } from './external/TransformControls.js'
|
||||
|
||||
export default class SectionBox {
|
||||
|
||||
constructor( viewer, bbox ) {
|
||||
this.viewer = viewer
|
||||
|
||||
this.orbiting = false
|
||||
this.dragging = false
|
||||
this.display = new THREE.Group()
|
||||
this.viewer.controls.addEventListener( 'wake', () => { this.orbiting = true } )
|
||||
this.viewer.controls.addEventListener( 'controlend', () => { this.orbiting = false } )
|
||||
|
||||
this.box = bbox || this.viewer.sceneManager.getSceneBoundingBox()
|
||||
const dimensions = new THREE.Vector3().subVectors( this.box.max, this.box.min )
|
||||
this.boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( this.box.min, this.box.max ).multiplyScalar( 0.5 ) )
|
||||
this.boxGeo.applyMatrix4( matrix )
|
||||
this.boxMesh = new THREE.Mesh( this.boxGeo, new THREE.MeshBasicMaterial() )
|
||||
|
||||
this.boxHelper = new THREE.BoxHelper( this.boxMesh, 0x0A66FF )
|
||||
|
||||
const plane = new THREE.PlaneGeometry( 1, 1 )
|
||||
this.hoverPlane = new THREE.Mesh( plane, new THREE.MeshStandardMaterial( {
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
opacity: 0.05,
|
||||
color: 0x0A66FF,
|
||||
metalness: 0.1,
|
||||
roughness: 0.75,
|
||||
} ) )
|
||||
|
||||
this.display.add( this.boxHelper )
|
||||
this.display.add( this.hoverPlane )
|
||||
|
||||
this.viewer.scene.add( this.display )
|
||||
|
||||
this.boxMesh.userData.planes = []
|
||||
this.boxMesh.userData.indices = []
|
||||
this.planes = []
|
||||
|
||||
// Gen box and planes
|
||||
this._generatePlanes()
|
||||
|
||||
// Box face selection controls
|
||||
this.selectionHelper = new SelectionHelper( this.viewer, { subset: this.boxMesh, hover: true } )
|
||||
let targetFaceIndex = -1
|
||||
|
||||
this.selectionHelper.on( 'hovered', ( obj ) => {
|
||||
if ( obj.length === 0 && !this.dragging ) {
|
||||
this.hoverPlane.visible = false
|
||||
this.controls.visible = true
|
||||
this.planeControls.detach()
|
||||
this.viewer.controls.enabled = true
|
||||
this.viewer.interactions.preventSelection = false
|
||||
this.viewer.needsRender = true
|
||||
targetFaceIndex = -1
|
||||
return
|
||||
}
|
||||
if ( this.orbiting || this.dragging ) return
|
||||
|
||||
this.controls.visible = false
|
||||
this.hoverPlane.visible = true
|
||||
|
||||
let centre = new THREE.Vector3()
|
||||
for ( let i = 0; i < 4; i++ ) {
|
||||
centre.add( this.boxGeo.vertices[ obj[0].object.userData.indices[ obj[0].faceIndex ][i] ].clone().applyMatrix4( this.boxMesh.matrixWorld ) )
|
||||
}
|
||||
centre.multiplyScalar( 0.25 )
|
||||
this.hoverPlane.position.copy( centre )
|
||||
|
||||
for ( let i = 0; i < 4; i++ ) {
|
||||
let vertex = this.boxGeo.vertices[ obj[0].object.userData.indices[ obj[0].faceIndex ][i] ].clone().applyMatrix4( this.boxMesh.matrixWorld )
|
||||
this.hoverPlane.geometry.vertices[i].set( vertex.x - centre.x, vertex.y - centre.y , vertex.z - centre.z )
|
||||
}
|
||||
|
||||
this.hoverPlane.geometry.verticesNeedUpdate = true
|
||||
|
||||
let normal = obj[0].face.normal
|
||||
this.planeControls.showX = normal.x !== 0
|
||||
this.planeControls.showY = normal.y !== 0
|
||||
this.planeControls.showZ = normal.z !== 0
|
||||
|
||||
this.planeControls.attach( this.hoverPlane )
|
||||
|
||||
if ( obj[0].faceIndex !== targetFaceIndex ) {
|
||||
this.viewer.needsRender = true
|
||||
targetFaceIndex = obj[0].faceIndex
|
||||
}
|
||||
} )
|
||||
|
||||
// Whole box controls
|
||||
this._globalControlsTarget = new THREE.Mesh( new THREE.SphereGeometry( 0.0001 ), new THREE.MeshBasicMaterial( ) )
|
||||
this._globalControlsTarget.position.copy( this.boxGeo.vertices[ 5 ].clone().multiplyScalar( 1.1 ) )
|
||||
this.display.add( this._globalControlsTarget )
|
||||
|
||||
this.controls = new TransformControls( this.viewer.camera, this.viewer.renderer.domElement )
|
||||
this.controls.setSize( 0.5 )
|
||||
this.controls.attach( this._globalControlsTarget )
|
||||
this.display.add( this.controls )
|
||||
|
||||
// Section plane controls
|
||||
this.planeControls = new TransformControls( this.viewer.camera, this.viewer.renderer.domElement, true )
|
||||
this.display.add( this.planeControls )
|
||||
|
||||
this.prevGizmoPos = this._globalControlsTarget.position.clone()
|
||||
this.controls.addEventListener( 'change', ( ) => {
|
||||
this.prevGizmoPos.sub( this._globalControlsTarget.position )
|
||||
this.boxMesh.translateX( -this.prevGizmoPos.x )
|
||||
this.boxMesh.translateY( -this.prevGizmoPos.y )
|
||||
this.boxMesh.translateZ( -this.prevGizmoPos.z )
|
||||
|
||||
this.prevGizmoPos = this._globalControlsTarget.position.clone()
|
||||
this.setPlanesFromBox( new THREE.Box3().setFromObject( this.boxMesh ) )
|
||||
this.boxHelper.update()
|
||||
this.viewer.needsRender = true
|
||||
} )
|
||||
|
||||
this.controls.addEventListener( 'dragging-changed', ( event ) => {
|
||||
this.viewer.controls.enabled = !event.value
|
||||
this.viewer.interactions.preventSelection = !event.value
|
||||
if ( !event.value )
|
||||
this.viewer.interactions.zoomToObject( this.boxMesh )
|
||||
} )
|
||||
|
||||
let prevPlaneGizmoPos = null
|
||||
this.planeControls.addEventListener( 'change', ( ) => {
|
||||
if ( !this.dragging ) return
|
||||
if ( targetFaceIndex === -1 ) return
|
||||
if ( prevPlaneGizmoPos === null ) prevPlaneGizmoPos = this.hoverPlane.position.clone()
|
||||
prevPlaneGizmoPos.sub( this.hoverPlane.position )
|
||||
let plane = this.boxMesh.userData.planes[ targetFaceIndex ]
|
||||
|
||||
prevPlaneGizmoPos.negate()
|
||||
plane.translate( prevPlaneGizmoPos )
|
||||
let indices = this.boxMesh.userData.indices[ targetFaceIndex ]
|
||||
for ( let i = 0; i < 4; i++ ) {
|
||||
let index = indices[i]
|
||||
this.boxGeo.vertices[index].add( prevPlaneGizmoPos )
|
||||
}
|
||||
this.boxGeo.verticesNeedUpdate = true
|
||||
this.boxMesh.geometry.computeBoundingBox()
|
||||
this.boxMesh.geometry.computeBoundingSphere()
|
||||
|
||||
let gizmoPos = this.boxGeo.vertices[ 5 ].clone()
|
||||
gizmoPos.multiplyScalar( 1.1 )
|
||||
gizmoPos.applyMatrix4( this.boxMesh.matrixWorld )
|
||||
this._globalControlsTarget.position.copy( gizmoPos )
|
||||
this.prevGizmoPos = gizmoPos
|
||||
|
||||
prevPlaneGizmoPos = this.hoverPlane.position.clone()
|
||||
this.boxHelper.update()
|
||||
this.viewer.needsRender = true
|
||||
} )
|
||||
|
||||
this.planeControls.addEventListener( 'dragging-changed', ( event ) => {
|
||||
this.viewer.controls.enabled = !event.value
|
||||
this.viewer.interactions.preventSelection = !event.value
|
||||
this.dragging = !!event.value
|
||||
if ( !this.dragging ) {
|
||||
prevPlaneGizmoPos = null
|
||||
this.viewer.interactions.zoomToObject( this.boxMesh )
|
||||
targetFaceIndex = -1
|
||||
|
||||
}
|
||||
this.viewer.needsRender = true
|
||||
} )
|
||||
}
|
||||
|
||||
_generatePlanes() {
|
||||
for ( let i = 0; i < this.boxGeo.faces.length; i += 2 ) {
|
||||
let face = this.boxGeo.faces[i]
|
||||
let pairFace = this.boxGeo.faces[i+1]
|
||||
let plane = new THREE.Plane()
|
||||
// inverting points so plane
|
||||
plane.setFromCoplanarPoints( this.boxGeo.vertices[face.c], this.boxGeo.vertices[face.b], this.boxGeo.vertices[face.a] )
|
||||
// adding it twice for ease of use
|
||||
this.boxMesh.userData.planes.push( plane )
|
||||
this.boxMesh.userData.planes.push( plane )
|
||||
|
||||
this.boxMesh.userData.indices.push( [ face.a, face.b, face.c, pairFace.b ] )
|
||||
this.boxMesh.userData.indices.push( [ face.a, face.b, face.c, pairFace.b ] )
|
||||
|
||||
this.planes.push( plane )
|
||||
}
|
||||
}
|
||||
|
||||
setPlanesFromBox( box ) {
|
||||
const dimensions = new THREE.Vector3().subVectors( box.max, box.min )
|
||||
let boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( box.min, box.max ).multiplyScalar( 0.5 ) )
|
||||
boxGeo.applyMatrix4( matrix )
|
||||
|
||||
for ( let i = 0; i < this.boxGeo.faces.length; i += 2 ) {
|
||||
let face = boxGeo.faces[i]
|
||||
let plane = this.boxMesh.userData.planes[i]
|
||||
plane.setFromCoplanarPoints( boxGeo.vertices[face.c], boxGeo.vertices[face.b], boxGeo.vertices[face.a] ) // invert pts
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setBox( box ) {
|
||||
box = box.clone().expandByScalar( 1.1 )
|
||||
const dimensions = new THREE.Vector3().subVectors( box.max, box.min )
|
||||
let boxGeo = new THREE.BoxGeometry( dimensions.x, dimensions.y, dimensions.z )
|
||||
|
||||
const matrix = new THREE.Matrix4().setPosition( dimensions.addVectors( box.min, box.max ).multiplyScalar( 0.5 ) )
|
||||
boxGeo.applyMatrix4( matrix )
|
||||
|
||||
for ( let i = 0; i < this.boxGeo.vertices.length; i++ ) {
|
||||
this.boxGeo.vertices[i].copy( boxGeo.vertices[i] )
|
||||
}
|
||||
|
||||
this._globalControlsTarget.position.copy( this.boxGeo.vertices[ 5 ].clone().multiplyScalar( 1.1 ) )
|
||||
this.prevGizmoPos = this._globalControlsTarget.position.clone()
|
||||
this.boxMesh.position.copy( new THREE.Vector3() )
|
||||
this.boxMesh.geometry.verticesNeedUpdate = true
|
||||
this.boxMesh.geometry.computeBoundingBox()
|
||||
this.boxMesh.geometry.computeBoundingSphere()
|
||||
this.boxHelper.update()
|
||||
this.setPlanesFromBox( box )
|
||||
this.viewer.needsRender = true
|
||||
}
|
||||
|
||||
toggle() {
|
||||
if ( this.display.visible ) {
|
||||
this.viewer.renderer.localClippingEnabled = false
|
||||
this.display.visible = false
|
||||
} else {
|
||||
this.viewer.renderer.localClippingEnabled = true
|
||||
this.display.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.selectionHelper.dispose()
|
||||
this.controls.dispose()
|
||||
this.planeControls.dispose()
|
||||
this.display.clear()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user