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

163 lines
5.1 KiB
JavaScript

import * as THREE from 'three'
import EventEmitter from './EventEmitter'
/**
* Selects and deselects user added objects in the scene. Emits the array of all intersected objects on click.
* optional param to configure SelectionHelper
* _options = {
* subset: THREE.Group
* hover: boolean.
* sectionBox: if present, will test for inclusion
* }
*/
export default class SelectionHelper extends EventEmitter {
constructor(parent, _options) {
super()
this.viewer = parent
this.raycaster = new THREE.Raycaster()
this.raycaster.params.Line.threshold = 0.1
this.raycaster.params.Line2 = {}
this.raycaster.params.Line2.threshold = 0
// optional param allows for raycasting against a subset of objects
// this.subset = typeof _options !== 'undefined' && typeof _options.subset !== 'undefined' ? _options.subset : null;
this.subset =
typeof _options !== 'undefined' && typeof _options.subset !== 'undefined'
? _options.subset
: null
this.pointerDown = false
this.checkForSectionBoxInclusion = true
if (typeof _options !== 'undefined' && _options.checkForSectionBoxInclusion) {
this.sectionBox = _options.checkForSectionBoxInclusion
}
// Handle mouseclicks
let mdTime
this.viewer.renderer.domElement.addEventListener('pointerdown', (e) => {
e.preventDefault()
mdTime = new Date().getTime()
})
this.viewer.renderer.domElement.addEventListener('pointerup', (e) => {
e.preventDefault()
if (this.viewer.cameraHandler.orbiting) return
const delta = new Date().getTime() - mdTime
this.pointerDown = false
if (delta > 250) return
const selectionObjects = this.getClickedObjects(e)
this.emit('object-clicked', selectionObjects)
})
// Doubleclicks on touch devices
// http://jsfiddle.net/brettwp/J4djY/
this.tapTimeout
this.lastTap = 0
this.touchLocation
this.viewer.renderer.domElement.addEventListener('touchstart', (e) => {
this.touchLocation = e.targetTouches[0]
})
this.viewer.renderer.domElement.addEventListener('touchend', (e) => {
// Ignore the first `touchend` when pinch-zooming (so we don't consider double-tap)
if (e.targetTouches.length > 0) {
return
}
const currentTime = new Date().getTime()
const tapLength = currentTime - this.lastTap
clearTimeout(this.tapTimeout)
if (tapLength < 500 && tapLength > 0) {
const selectionObjects = this.getClickedObjects(this.touchLocation)
this.emit('object-doubleclicked', selectionObjects)
} else {
this.tapTimeout = setTimeout(function () {
clearTimeout(this.tapTimeout)
}, 500)
}
this.lastTap = currentTime
})
this.viewer.renderer.domElement.addEventListener('dblclick', (e) => {
const selectionObjects = this.getClickedObjects(e)
this.emit('object-doubleclicked', selectionObjects)
})
// Handle multiple object selection
this.multiSelect = false
document.addEventListener('keydown', (e) => {
if (e.isComposing || e.keyCode === 229) return
if (e.key === 'Shift') this.multiSelect = true
if (e.key === 'Escape') this.unselect()
})
document.addEventListener('keyup', (e) => {
if (e.isComposing || e.keyCode === 229) return
if (e.key === 'Shift') this.multiSelect = false
})
this.originalSelectionObjects = []
}
unselect() {
this.originalSelectionObjects = []
}
getClickedObjects(e) {
const normalizedPosition = this._getNormalisedClickPosition(e)
this.raycaster.setFromCamera(
normalizedPosition,
this.viewer.cameraHandler.activeCam.camera
)
const targetObjects = this.subset
? this.subset
: this.viewer.sceneManager.filteredObjects
let intersectedObjects = this.raycaster.intersectObjects(targetObjects)
// filters objects in section box mode
if (this.viewer.sectionBox.display.visible && this.checkForSectionBoxInclusion) {
const box = new THREE.Box3().setFromObject(this.viewer.sectionBox.cube)
intersectedObjects = intersectedObjects.filter((obj) => {
return box.containsPoint(obj.point)
})
}
return intersectedObjects
}
// get all children of a subset passed as a THREE.Group
_getGroupChildren(group) {
let children = []
if (group.children.length === 0) return [group]
group.children.forEach(
(c) => (children = [...children, ...this._getGroupChildren(c)])
)
return children
}
_getNormalisedClickPosition(e) {
// Reference: https://threejsfundamentals.org/threejs/lessons/threejs-picking.html
const canvas = this.viewer.renderer.domElement
const rect = this.viewer.renderer.domElement.getBoundingClientRect()
const pos = {
x: ((e.clientX - rect.left) * canvas.width) / rect.width,
y: ((e.clientY - rect.top) * canvas.height) / rect.height
}
return {
x: (pos.x / canvas.width) * 2 - 1,
y: (pos.y / canvas.height) * -2 + 1
}
}
dispose() {
super.dispose()
this.unselect()
this.originalSelectionObjects = null
}
}