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