8 changed files with 356 additions and 735 deletions
@ -1,255 +0,0 @@ |
|||
import { EventDispatcher, Matrix4, Plane, Raycaster, Vector2, Vector3 } from 'three' |
|||
|
|||
import { calcPositionUseSnap, getClosestObject } from '@/core/ModelUtils.js' |
|||
|
|||
const _plane = new Plane() // 用于拖拽操作的平面
|
|||
const _raycaster = new Raycaster() // 射线检测器,用于拾取物体
|
|||
|
|||
const _pointer = new Vector2() // 屏幕坐标指针位置 (归一化设备坐标)
|
|||
const _offset = new Vector3() // 拖动时相对于点击点的偏移量
|
|||
const _intersection = new Vector3() // 与平面相交的点
|
|||
const _worldPosition = new Vector3() // 世界坐标位置
|
|||
const _inverseMatrix = new Matrix4() // 用于将位置转换到局部空间
|
|||
|
|||
/** |
|||
* DragControls 控制器类 |
|||
* 提供基于鼠标或触摸的拖拽交互功能,并支持 hover 和 clickblank 等事件 |
|||
*/ |
|||
class DragControls extends EventDispatcher { |
|||
/** |
|||
* 构造函数 |
|||
* @param _objects 可拖拽的对象数组 |
|||
* @param _camera 当前使用的相机 |
|||
* @param _domElement 绑定事件的目标 DOM 元素(通常是 canvas) |
|||
*/ |
|||
constructor(_objects, _camera, _domElement) { |
|||
super() |
|||
|
|||
// 禁止触摸滚动行为
|
|||
_domElement.style.touchAction = 'none' |
|||
|
|||
let _selected = null // 当前选中(正在拖动)的对象
|
|||
let _hovered = null // 当前悬停的对象
|
|||
|
|||
const _intersections = [] // 存储射线检测结果的数组
|
|||
|
|||
let isMove = false // 标记是否发生了移动
|
|||
let isMouseDownClicked = false // 标记是否按下了鼠标
|
|||
const scope = this // 保存当前上下文
|
|||
|
|||
/** |
|||
* 激活事件监听器 |
|||
*/ |
|||
function activate() { |
|||
_domElement.addEventListener('pointermove', onPointerMove) |
|||
_domElement.addEventListener('pointerdown', onPointerDown) |
|||
_domElement.addEventListener('pointerup', onPointerCancel) |
|||
_domElement.addEventListener('pointerleave', onPointerCancel) |
|||
} |
|||
|
|||
/** |
|||
* 去激活事件监听器 |
|||
*/ |
|||
function deactivate() { |
|||
_domElement.removeEventListener('pointermove', onPointerMove) |
|||
_domElement.removeEventListener('pointerdown', onPointerDown) |
|||
_domElement.removeEventListener('pointerup', onPointerCancel) |
|||
_domElement.removeEventListener('pointerleave', onPointerCancel) |
|||
|
|||
_domElement.style.cursor = '' |
|||
} |
|||
|
|||
/** |
|||
* 销毁控制器并释放资源 |
|||
*/ |
|||
function dispose() { |
|||
deactivate() |
|||
} |
|||
|
|||
/** |
|||
* 设置可拖拽的对象列表 |
|||
* @param {Array} objects - 新的对象数组 |
|||
*/ |
|||
function setObjects(objects) { |
|||
_objects = objects |
|||
} |
|||
|
|||
/** |
|||
* 获取当前可拖拽的对象列表 |
|||
* @returns {Array} |
|||
*/ |
|||
function getObjects() { |
|||
return _objects |
|||
} |
|||
|
|||
/** |
|||
* 获取内部的 Raycaster 实例 |
|||
* @returns {Raycaster} |
|||
*/ |
|||
function getRaycaster() { |
|||
return _raycaster |
|||
} |
|||
|
|||
/** |
|||
* 鼠标/指针移动事件处理函数 |
|||
* 处理悬停、拖拽等交互逻辑 |
|||
*/ |
|||
function onPointerMove(event) { |
|||
if (!scope.enabled || !scope.enabledMove) return |
|||
|
|||
if (isMouseDownClicked) { |
|||
_domElement.style.cursor = 'move' |
|||
} |
|||
|
|||
isMove = true |
|||
updatePointer(event) |
|||
_raycaster.setFromCamera(_pointer, _camera) |
|||
|
|||
// 如果有选中的对象,则更新其位置
|
|||
if (_selected) { |
|||
if (_raycaster.ray.intersectPlane(_plane, _intersection)) { |
|||
const pos = _intersection.sub(_offset).applyMatrix4(_inverseMatrix) |
|||
const newIntersection = calcPositionUseSnap(event, pos) |
|||
_selected.position.copy(newIntersection) |
|||
} |
|||
scope.dispatchEvent({ type: 'drag', object: _selected }) |
|||
return |
|||
} |
|||
|
|||
// 鼠标/笔悬停检测
|
|||
if (event.pointerType === 'mouse' || event.pointerType === 'pen') { |
|||
|
|||
_intersections.length = 0 |
|||
|
|||
_raycaster.setFromCamera(_pointer, _camera) |
|||
_raycaster.intersectObjects(_objects, true, _intersections) |
|||
|
|||
if (_intersections.length > 0) { |
|||
|
|||
const object = _intersections[0].object |
|||
|
|||
_plane.setFromNormalAndCoplanarPoint( |
|||
_camera.getWorldDirection(_plane.normal), |
|||
_worldPosition.setFromMatrixPosition(object.matrixWorld) |
|||
) |
|||
|
|||
if (_hovered !== object && _hovered !== null) { |
|||
scope.dispatchEvent({ type: 'hoveroff', object: _hovered }) |
|||
_domElement.style.cursor = 'auto' |
|||
_hovered = null |
|||
} |
|||
|
|||
if (_hovered !== object) { |
|||
scope.dispatchEvent({ type: 'hoveron', object: object }) |
|||
_domElement.style.cursor = 'pointer' |
|||
_hovered = object |
|||
} |
|||
|
|||
} else { |
|||
|
|||
if (_hovered !== null) { |
|||
scope.dispatchEvent({ type: 'hoveroff', object: _hovered }) |
|||
_domElement.style.cursor = 'auto' |
|||
_hovered = null |
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 鼠标按下事件处理函数 |
|||
* 检测是否点击了可拖拽对象,并准备开始拖拽 |
|||
*/ |
|||
function onPointerDown(event) { |
|||
if (scope.enabled === false) return |
|||
|
|||
updatePointer(event) |
|||
|
|||
_intersections.length = 0 |
|||
|
|||
_raycaster.setFromCamera(_pointer, _camera) |
|||
let objects = _objects |
|||
|
|||
_raycaster.intersectObjects(objects, true, _intersections) |
|||
|
|||
if (_intersections.length > 0) { |
|||
// 判断是否启用组拖动模式
|
|||
_selected = (scope.transformGroup === true) ? _objects[0] : _intersections[0].object |
|||
|
|||
if (scope.enabledMove) { |
|||
// 设置拖拽平面
|
|||
_plane.setFromNormalAndCoplanarPoint( |
|||
_camera.getWorldDirection(_plane.normal), |
|||
_worldPosition.setFromMatrixPosition(_selected.matrixWorld) |
|||
) |
|||
if (_raycaster.ray.intersectPlane(_plane, _intersection)) { |
|||
// 计算偏移量
|
|||
_inverseMatrix.copy(_selected.parent.matrixWorld).invert() |
|||
_offset.copy(_intersection).sub( |
|||
_worldPosition.setFromMatrixPosition(_selected.matrixWorld) |
|||
) |
|||
} |
|||
|
|||
isMouseDownClicked = true |
|||
} |
|||
|
|||
// 触发 dragstart 事件
|
|||
scope.dispatchEvent({ type: 'dragstart', object: _selected, e: event }) |
|||
} |
|||
|
|||
isMove = false |
|||
} |
|||
|
|||
/** |
|||
* 鼠标释放或离开事件处理函数 |
|||
* 结束拖拽操作或触发点击空白区域事件 |
|||
*/ |
|||
function onPointerCancel(event) { |
|||
if (scope.enabled === false) return |
|||
|
|||
if (_selected) { |
|||
// 结束拖拽
|
|||
scope.dispatchEvent({ type: 'dragend', object: _selected, e: event }) |
|||
_selected = null |
|||
} else if (!isMove) { |
|||
// 如果没有发生移动,则认为是点击空白处
|
|||
scope.dispatchEvent({ type: 'clickblank', e: event }) |
|||
} |
|||
|
|||
// 恢复光标状态
|
|||
_domElement.style.cursor = _hovered ? 'pointer' : 'auto' |
|||
isMouseDownClicked = false |
|||
} |
|||
|
|||
/** |
|||
* 更新指针位置 |
|||
* 将屏幕坐标转换为 NDC 设备坐标 (-1 ~ 1) |
|||
*/ |
|||
function updatePointer(event) { |
|||
const rect = _domElement.getBoundingClientRect() |
|||
|
|||
_pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1 |
|||
_pointer.y = -(event.clientY - rect.top) / rect.height * 2 + 1 |
|||
} |
|||
|
|||
// 初始化:激活事件监听器
|
|||
activate() |
|||
|
|||
// 暴露 API 方法和属性
|
|||
this.enabled = true // 是否启用控制器
|
|||
this.enabledMove = true // 是否允许移动操作
|
|||
this.transformGroup = false // 是否以组形式变换多个对象
|
|||
|
|||
this.activate = activate |
|||
this.deactivate = deactivate |
|||
this.dispose = dispose |
|||
this.setObjects = setObjects |
|||
this.getObjects = getObjects |
|||
this.getRaycaster = getRaycaster |
|||
} |
|||
} |
|||
|
|||
export { DragControls } |
|||
@ -1,163 +0,0 @@ |
|||
import * as THREE from 'three' |
|||
import { DragControls } from './DragControls.js' |
|||
import type Viewport from '@/core/engine/Viewport.ts' |
|||
import EventBus from '@/runtime/EventBus' |
|||
import { getInteraction } from '@/core/manager/ModuleManager.ts' |
|||
import type BaseInteraction from '@/core/base/BaseInteraction.ts' |
|||
|
|||
// dragControls 绑定函数
|
|||
let dragStartFn, dragFn, dragEndFn, clickblankFn |
|||
|
|||
export default class EsDragControls { |
|||
_dragObjects: THREE.Object3D[] = [] // 拖拽对象
|
|||
dragControls: any |
|||
private onDownPosition: { x: number; y: number } = { x: -1, y: -1 } |
|||
|
|||
viewport: Viewport |
|||
currentInteraction: BaseInteraction |
|||
isDragging = false |
|||
|
|||
constructor(viewport) { |
|||
this.viewport = viewport |
|||
|
|||
// 物体拖拽控制器
|
|||
this.dragControls = new DragControls(this._dragObjects, viewport.camera, viewport.renderer.domElement) |
|||
this.dragControls.deactivate() // 默认禁用
|
|||
dragStartFn = this.dragControlsStart.bind(this) |
|||
this.dragControls.addEventListener('dragstart', dragStartFn) |
|||
dragFn = this.drag.bind(this) |
|||
this.dragControls.addEventListener('drag', dragFn) |
|||
dragEndFn = this.dragControlsEnd.bind(this) |
|||
this.dragControls.addEventListener('dragend', dragEndFn) |
|||
// 点击可拖拽物体之外
|
|||
clickblankFn = this.clickblank.bind(this) |
|||
this.dragControls.addEventListener('clickblank', clickblankFn) |
|||
} |
|||
|
|||
set domElement(element: HTMLElement) { |
|||
this.dragControls.setDomElement(element) |
|||
} |
|||
|
|||
setDragObjects(objects: THREE.Object3D[], type: 'eq' | 'push' | 'remove' = 'eq') { |
|||
// 当前拖拽对象为空时加入对象需激活控制器
|
|||
if (this._dragObjects.length === 0) { |
|||
if (objects.length > 0) { |
|||
this.dragControls.activate() |
|||
} |
|||
|
|||
this._dragObjects = objects |
|||
} else { |
|||
// 当前拖拽对象不为空时
|
|||
if (type === 'eq') { |
|||
// 是清空拖拽对象的设置,则禁用控制器
|
|||
if (objects.length === 0) { |
|||
this.dragControls.deactivate() |
|||
} |
|||
|
|||
this._dragObjects = objects |
|||
} else if (type === 'push') { |
|||
this._dragObjects.push(...objects) |
|||
} else if (type === 'remove') { |
|||
this._dragObjects = this._dragObjects.filter((item) => !objects.includes(item)) |
|||
} |
|||
} |
|||
|
|||
this.dragControls.setObjects(this._dragObjects) |
|||
} |
|||
|
|||
// 拖拽开始
|
|||
dragControlsStart(e) { |
|||
// 右键拖拽不响应
|
|||
if (e.e.button === 2 || !e.object?.visible) return |
|||
|
|||
const type = e.object.userData?.t |
|||
const entityId = e.object.userData?.entityId |
|||
|
|||
if (!type || !entityId) return |
|||
|
|||
this.currentInteraction = getInteraction(e.object.userData?.t) |
|||
if (!this.currentInteraction) { |
|||
return |
|||
} |
|||
|
|||
// 有效拖拽对象
|
|||
const enable = this.currentInteraction.dragPointStart(this.viewport, { |
|||
object: e.object, |
|||
entityId: entityId |
|||
}) |
|||
if (!enable) return |
|||
|
|||
e.e.preventDefault() |
|||
|
|||
// 拖拽时禁用其他控制器
|
|||
this.viewport.controls.enabled = false |
|||
|
|||
this.isDragging = true |
|||
|
|||
// 记录拖拽按下的位置和对象
|
|||
this.onDownPosition = { x: e.e.clientX, y: e.e.clientY } |
|||
|
|||
// const itemType: ItemTypeDefineOption = getItemTypeByName(e.object.userData.type)
|
|||
// if (itemType?.clazz) {
|
|||
// itemType.clazz.dragPointStart(this.viewport, e.object)
|
|||
// }
|
|||
// switch (e.object.userData.type) {
|
|||
// case Constract.MeasureMarker:
|
|||
// this.viewport.measure.dragPointStart(e.object)
|
|||
// break
|
|||
// }
|
|||
} |
|||
|
|||
// 拖拽中
|
|||
drag(e) { |
|||
this.currentInteraction?.dragPointMove(this.viewport, e) |
|||
} |
|||
|
|||
// 拖拽结束
|
|||
dragControlsEnd(e) { |
|||
// 右键拖拽不响应
|
|||
if (e.e.button === 2 || !e.object.visible) return |
|||
if (!e.object.userData?.t) return |
|||
|
|||
// 单选点击
|
|||
if (this.onDownPosition.x === e.e.clientX && this.onDownPosition.y === e.e.clientY) { |
|||
if (e.object.userData.onClick) { |
|||
e.object.userData.onClick(e) |
|||
} |
|||
} |
|||
|
|||
const ret = this.currentInteraction?.dragPointComplete(this.viewport, e) |
|||
if (!ret) return |
|||
|
|||
EventBus.dispatch('selectedObjectPropertyChanged', {}) |
|||
|
|||
// 拖拽结束启用其他控制器
|
|||
this.viewport.controls.enabled = true |
|||
this.isDragging = false |
|||
|
|||
// if (e.object.userData?.type) {
|
|||
// const itemType: ItemTypeDefineOption = getItemTypeByName(e.object.userData.type)
|
|||
// if (itemType?.clazz) {
|
|||
// itemType.clazz.dragPointComplete(this.viewport)
|
|||
// }
|
|||
// }
|
|||
// switch (e.object.userData.type) {
|
|||
// case Constract.MeasureMarker:
|
|||
// this.viewport.measure.dragPointComplete()
|
|||
// break
|
|||
// }
|
|||
} |
|||
|
|||
// 点击可拖拽物体之外
|
|||
clickblank(e) { |
|||
if (e.e.button === 2) return |
|||
} |
|||
|
|||
dispose() { |
|||
this._dragObjects = [] |
|||
|
|||
this.dragControls.removeEventListener('dragstart', dragStartFn) |
|||
this.dragControls.removeEventListener('dragend', dragEndFn) |
|||
this.dragControls.dispose() |
|||
} |
|||
} |
|||
Loading…
Reference in new issue