|
|
|
@ -1,38 +1,45 @@ |
|
|
|
import { |
|
|
|
EventDispatcher, |
|
|
|
Matrix4, |
|
|
|
Plane, |
|
|
|
Raycaster, |
|
|
|
Vector2, |
|
|
|
Vector3 |
|
|
|
} from 'three' |
|
|
|
import { calcPositionUseSnap } 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() |
|
|
|
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' // disable touch scroll
|
|
|
|
|
|
|
|
let _selected = null, _hovered = null |
|
|
|
// 禁止触摸滚动行为
|
|
|
|
_domElement.style.touchAction = 'none' |
|
|
|
|
|
|
|
const _intersections = [] |
|
|
|
let _selected = null // 当前选中(正在拖动)的对象
|
|
|
|
let _hovered = null // 当前悬停的对象
|
|
|
|
|
|
|
|
//
|
|
|
|
const _intersections = [] // 存储射线检测结果的数组
|
|
|
|
|
|
|
|
let isMove = false |
|
|
|
let isMouseDownClicked = false |
|
|
|
const scope = this |
|
|
|
let isMove = false // 标记是否发生了移动
|
|
|
|
let isMouseDownClicked = false // 标记是否按下了鼠标
|
|
|
|
const scope = this // 保存当前上下文
|
|
|
|
|
|
|
|
/** |
|
|
|
* 激活事件监听器 |
|
|
|
*/ |
|
|
|
function activate() { |
|
|
|
_domElement.addEventListener('pointermove', onPointerMove) |
|
|
|
_domElement.addEventListener('pointerdown', onPointerDown) |
|
|
|
@ -40,6 +47,9 @@ class DragControls extends EventDispatcher { |
|
|
|
_domElement.addEventListener('pointerleave', onPointerCancel) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 去激活事件监听器 |
|
|
|
*/ |
|
|
|
function deactivate() { |
|
|
|
_domElement.removeEventListener('pointermove', onPointerMove) |
|
|
|
_domElement.removeEventListener('pointerdown', onPointerDown) |
|
|
|
@ -49,22 +59,41 @@ class DragControls extends EventDispatcher { |
|
|
|
_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 |
|
|
|
|
|
|
|
@ -76,6 +105,7 @@ class DragControls extends EventDispatcher { |
|
|
|
updatePointer(event) |
|
|
|
_raycaster.setFromCamera(_pointer, _camera) |
|
|
|
|
|
|
|
// 如果有选中的对象,则更新其位置
|
|
|
|
if (_selected) { |
|
|
|
if (_raycaster.ray.intersectPlane(_plane, _intersection)) { |
|
|
|
const pos = _intersection.sub(_offset).applyMatrix4(_inverseMatrix) |
|
|
|
@ -86,7 +116,7 @@ class DragControls extends EventDispatcher { |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
// hover support
|
|
|
|
// 鼠标/笔悬停检测
|
|
|
|
if (event.pointerType === 'mouse' || event.pointerType === 'pen') { |
|
|
|
|
|
|
|
_intersections.length = 0 |
|
|
|
@ -98,24 +128,21 @@ class DragControls extends EventDispatcher { |
|
|
|
|
|
|
|
const object = _intersections[0].object |
|
|
|
|
|
|
|
_plane.setFromNormalAndCoplanarPoint(_camera.getWorldDirection(_plane.normal), _worldPosition.setFromMatrixPosition(object.matrixWorld)) |
|
|
|
_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 { |
|
|
|
@ -132,6 +159,10 @@ class DragControls extends EventDispatcher { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 鼠标按下事件处理函数 |
|
|
|
* 检测是否点击了可拖拽对象,并准备开始拖拽 |
|
|
|
*/ |
|
|
|
function onPointerDown(event) { |
|
|
|
if (scope.enabled === false) return |
|
|
|
|
|
|
|
@ -145,41 +176,58 @@ class DragControls extends EventDispatcher { |
|
|
|
_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)) |
|
|
|
// 设置拖拽平面
|
|
|
|
_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)) |
|
|
|
_offset.copy(_intersection).sub( |
|
|
|
_worldPosition.setFromMatrixPosition(_selected.matrixWorld) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
// setTimeout(() => {
|
|
|
|
// _domElement.style.cursor = 'move'
|
|
|
|
// }, 20)
|
|
|
|
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() |
|
|
|
|
|
|
|
@ -187,13 +235,13 @@ class DragControls extends EventDispatcher { |
|
|
|
_pointer.y = -(event.clientY - rect.top) / rect.height * 2 + 1 |
|
|
|
} |
|
|
|
|
|
|
|
// 初始化:激活事件监听器
|
|
|
|
activate() |
|
|
|
|
|
|
|
// API
|
|
|
|
|
|
|
|
this.enabled = true |
|
|
|
this.enabledMove = true |
|
|
|
this.transformGroup = false |
|
|
|
// 暴露 API 方法和属性
|
|
|
|
this.enabled = true // 是否启用控制器
|
|
|
|
this.enabledMove = true // 是否允许移动操作
|
|
|
|
this.transformGroup = false // 是否以组形式变换多个对象
|
|
|
|
|
|
|
|
this.activate = activate |
|
|
|
this.deactivate = deactivate |
|
|
|
@ -201,9 +249,7 @@ class DragControls extends EventDispatcher { |
|
|
|
this.setObjects = setObjects |
|
|
|
this.getObjects = getObjects |
|
|
|
this.getRaycaster = getRaycaster |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
export { DragControls } |
|
|
|
|