From 829dbd6fc128a2de22ea68044fd561b97c9a5319 Mon Sep 17 00:00:00 2001 From: luoyifan Date: Fri, 6 Jun 2025 20:24:39 +0800 Subject: [PATCH] =?UTF-8?q?EsDragControl=20=E5=BB=B6=E8=BF=9F=E6=8A=93?= =?UTF-8?q?=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/core/controls/DragControl.ts | 326 +++++++++++++++++++++++++++++++++++ src/core/controls/DragControls.js | 255 --------------------------- src/core/controls/EsDragControl2.ts | 281 ------------------------------ src/core/controls/EsDragControls.ts | 163 ------------------ src/core/controls/SelectInspect.ts | 25 +-- src/core/engine/Viewport.ts | 8 +- src/modules/gstore/GstoreRenderer.ts | 29 ++-- 8 files changed, 356 insertions(+), 735 deletions(-) create mode 100644 src/core/controls/DragControl.ts delete mode 100644 src/core/controls/DragControls.js delete mode 100644 src/core/controls/EsDragControl2.ts delete mode 100644 src/core/controls/EsDragControls.ts diff --git a/README.md b/README.md index d168ae6..a5fc001 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ src/ │ │ └── ... │ ├── controls/ # 各种实体基础类 │ │ ├── SelectionControls.ts -│ │ ├── EsDragControls.ts +│ │ ├── DragControl.ts │ │ └── MouseMoveControls.ts │ ├── Model3DViewer.vue │ ├── Model2DEditor.vue @@ -157,4 +157,4 @@ src/ └── views/ # 页面视图(Vue 页面) ├── Editor.vue # 主编辑器页面 └── Viewer.vue # 查看器页面 -``` \ No newline at end of file +``` diff --git a/src/core/controls/DragControl.ts b/src/core/controls/DragControl.ts new file mode 100644 index 0000000..21654e5 --- /dev/null +++ b/src/core/controls/DragControl.ts @@ -0,0 +1,326 @@ +import * as THREE from 'three' +import type Viewport from '@/core/engine/Viewport.ts' +import type IControls from '@/core/controls/IControls.ts' +import { getClosestObject } from '@/core/ModelUtils.ts' +import EventBus from '@/runtime/EventBus.ts' + +/** + * ThreeJS 拖拽管理器(仅限 X/Z 平面) + */ +export default class DragControl implements IControls { + private viewport: Viewport + private _is_enabled: boolean = true + private domElement: HTMLElement + public isDragging: boolean = false + private isPointerDown: boolean = false + private dragStartMouse: THREE.Vector2 = new THREE.Vector2() + private checkStateInterval: number | null = null + private dragShadowsGroup: THREE.Group | null = null + private dragDelayTimeout: number | null = null + + init(viewport: Viewport): void { + this.viewport = viewport + + const domElement = this.viewport.renderer.domElement + domElement.addEventListener('pointerdown', this.onPointerDown) + domElement.addEventListener('pointermove', this.onPointerMove) + domElement.addEventListener('pointerup', this.onPointerUp) + domElement.addEventListener('pointerleave', this.onPointerLeave) + + domElement.style.cursor = 'auto' + this.domElement = domElement + } + + + /** + * 卸载资源 + */ + dispose(): void { + if (this.domElement) { + this.domElement.removeEventListener('pointermove', this.onPointerMove) + this.domElement.removeEventListener('pointerdown', this.onPointerDown) + this.domElement.removeEventListener('pointerup', this.onPointerUp) + this.domElement.removeEventListener('pointerleave', this.onPointerLeave) + + this.cleanupDrag() + this.domElement.style.cursor = 'auto' + this.viewport = null + } + + } + + /** + * pointerdown 事件处理 + */ + private onPointerDown = (event: PointerEvent): void => { + if (!this.enabled) return + + const mouse = this.getMousePosition(event.clientX, event.clientY) + const intersected = this.getIntersectedDraggableObject(mouse) + if (!intersected) return + this.viewport.controls.enabled = false + + // 设置定时器:0.1秒后才抓取拖拽 + this.dragDelayTimeout = window.setTimeout(() => { + + const mouse = this.getMousePosition(event.clientX, event.clientY) + const intersected = this.getIntersectedDraggableObject(mouse) + if (!intersected) return + + this.isPointerDown = true + this.dragStartMouse.set(intersected.position.x, intersected.position.z) + + let selectedObjects = [intersected] + if (this.viewport.state.multiSelectedObjects.length > 0) { + if (this.viewport.state.multiSelectedObjects.includes(intersected)) { + // drag multi-selected objects + selectedObjects = this.viewport.state.multiSelectedObjects + } + } + + this.checkStateInterval = setInterval(() => { + if (isNaN(CurrentMouseInfo.x) || isNaN(CurrentMouseInfo.z) || !this.isPointerDown) { + this.cancelDrag() + } + }, 100) + + this.createShadows(selectedObjects) + this.domElement.style.cursor = 'grabbing' + + }, 100) // 0.1秒延迟抓取 + + } + + /** + * pointermove 事件处理 + */ + private onPointerMove = (event: PointerEvent): void => { + if (!this.enabled || !this.domElement) return + + if (!isNaN(this.dragStartMouse.x) && !isNaN(this.dragStartMouse.y) && this.dragShadowsGroup) { + this.isDragging = true + this.domElement.style.cursor = 'grabbing' + + // 更新阴影位置(锁定 Y 轴) + this.updateShadows(new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z)) + + } else { + + const mouse = this.getMousePosition(event.clientX, event.clientY) + const intersected = this.getIntersectedDraggableObject(mouse) + if (intersected) { + this.domElement.style.cursor = 'grab' + } else { + this.domElement.style.cursor = 'auto' + } + } + } + /** + * pointerup 事件处理 + */ + private onPointerUp = (event: PointerEvent): void => { + if (this.isDragging) { + const startPos = this.dragStartMouse + const targetPos = new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z) + + if (startPos && targetPos) { + this.dragComplete(startPos, targetPos) + } + } + + this.cleanupDrag() + } + + /** + * 清理拖拽状态 + */ + private cleanupDrag(): void { + if (this.domElement) { + this.domElement.style.cursor = 'auto' + } + + if (this.viewport) { + this.viewport.controls.enabled = true + } + + this.isDragging = false + this.isPointerDown = false + this.dragStartMouse.set(NaN, NaN) + + this.removeShadows() + + if (this.dragDelayTimeout !== null) { + clearTimeout(this.dragDelayTimeout) + this.dragDelayTimeout = null + } + + if (this.checkStateInterval) { + clearInterval(this.checkStateInterval) + this.checkStateInterval = null + } + } + + /** + * 获取当前鼠标坐标(归一化设备坐标) + */ + private getMousePosition(clientX: number, clientY: number): THREE.Vector2 { + const rect = this.domElement.getBoundingClientRect() + return new THREE.Vector2( + ((clientX - rect.left) / rect.width) * 2 - 1, + ((clientY - rect.top) / rect.height) * -2 + 1 + ) + } + + /** + * 射线检测,返回第一个可拖拽对象 + */ + private getIntersectedDraggableObject(mouse: THREE.Vector2): THREE.Object3D | null { + const raycaster = new THREE.Raycaster() + raycaster.setFromCamera(mouse, this.viewport.camera) + + const draggableObjects = this.viewport.entityManager._draggableObjects || [] + const intersects = raycaster.intersectObjects(draggableObjects, true) + + if (intersects.length > 0) { + return getClosestObject(intersects[0].object) + } + } + + private static readonly SHADOW_MATERIAL = new THREE.MeshBasicMaterial({ + color: 0x222222, + transparent: true, + opacity: 0.5, + depthWrite: false, + side: THREE.DoubleSide + }) + + /** + * 创建拖拽阴影 + */ + private createShadows(objects: THREE.Object3D[]): void { + this.removeShadows() + + console.log('createShadows', objects.map(obj => obj.name)) + + this.dragShadowsGroup = new THREE.Group() + // 初始位置为 (0, 0, 0) + this.dragShadowsGroup.position.set(0, 0, 0) + for (const obj of objects) { + if (!obj.userData.entityId) { + console.error('Object does not have entityId:', obj.name) + continue // 跳过没有 entityId 的对象 + } + + // 克隆对象的几何体和材质 + // const shadowBox = obj.clone() + + const box = new THREE.Box3().setFromObject(obj) + const size = new THREE.Vector3() + box.getSize(size) + const geometry = new THREE.PlaneGeometry(size.x, size.z) + const shadowBox = new THREE.Mesh(geometry, DragControl.SHADOW_MATERIAL) + shadowBox.position.copy(obj.position) + shadowBox.rotation.x = -Math.PI / 2 + + shadowBox.userData = { + isShadow: true, + entityId: obj.userData.entityId, + originPosition: obj.position.clone() // 保存原始位置 + } + this.dragShadowsGroup.add(shadowBox) + } + this.viewport.scene.add(this.dragShadowsGroup) + } + + /** + * 移除阴影 + */ + private removeShadows(): void { + if (this.dragShadowsGroup) { + console.log('removeShadows') + this.viewport.scene.remove(this.dragShadowsGroup) + this.dragShadowsGroup = null + } + } + + /** + * 更新阴影位置(仅 X/Z 平面) + */ + private updateShadows(newPosition: THREE.Vector2): void { + if (!this.dragShadowsGroup) return + + // 计算新位置与拖拽开始位置的偏移量 + const offsetX = newPosition.x - this.dragStartMouse.x + const offsetZ = newPosition.y - this.dragStartMouse.y + + for (let i = 0; i < this.dragShadowsGroup.children.length; i++) { + const shadow = this.dragShadowsGroup.children[i] as THREE.Mesh + if (!shadow.userData.originPosition) { + console.error(`Shadow ${shadow.name} does not have originPosition`) + continue + } + + const newPosX = shadow.userData.originPosition.x + offsetX + const newPosZ = shadow.userData.originPosition.z + offsetZ + shadow.position.set(newPosX, shadow.userData.originPosition.y, newPosZ) // 锁定 Y 轴高度 + // console.log(`Updating shadow ${shadow.name} position with offset:`, offsetX, offsetZ) + } + } + + private dragComplete = (startPos: THREE.Vector2, targetPos: THREE.Vector2): void => { + // console.log(`Drag completed from ${startPos.toArray()} to ${targetPos.toArray()}`) + this.viewport.stateManager.beginStateUpdate() + for (const object of this.dragShadowsGroup.children) { + const entityId = object.userData.entityId + if (entityId) { + const entity = this.viewport.stateManager.findItemById(entityId) + if (entity) { + // 更新实体位置 + entity.tf[0][0] = object.position.x + entity.tf[0][2] = object.position.z + + } else { + system.showErrorDialog('not found entityId:' + entityId) + } + } else { + system.showErrorDialog('not found entity') + } + } + this.viewport.stateManager.endStateUpdate() + EventBus.dispatch('multiselectedObjectChanged', { + multiSelectedObjects: this.viewport.state.multiSelectedObjects + }) + EventBus.dispatch('selectedObjectPropertyChanged', {}) + + this.cleanupDrag() + } + + /** + * pointerleave 事件处理 + */ + private onPointerLeave = (_event: PointerEvent): void => { + this.cancelDrag() + } + + /** + * 取消当前拖拽状态 + */ + cancelDrag(): void { + this.cleanupDrag() + } + + + /** + * 设置启用/禁用 + */ + public set enabled(value: boolean) { + this._is_enabled = value + if (!value) { + this.cleanupDrag() + } + } + + public get enabled(): boolean { + return this._is_enabled + } +} diff --git a/src/core/controls/DragControls.js b/src/core/controls/DragControls.js deleted file mode 100644 index 7e777df..0000000 --- a/src/core/controls/DragControls.js +++ /dev/null @@ -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 } diff --git a/src/core/controls/EsDragControl2.ts b/src/core/controls/EsDragControl2.ts deleted file mode 100644 index 0bce29a..0000000 --- a/src/core/controls/EsDragControl2.ts +++ /dev/null @@ -1,281 +0,0 @@ -import * as THREE from 'three' -import type Viewport from '@/core/engine/Viewport.ts' -import type IControls from '@/core/controls/IControls.ts' -import { Curve } from 'three' -import { getClosestObject } from '@/core/ModelUtils.ts' -import EventBus from '@/runtime/EventBus.ts' - -/** - * DragControl2 - ThreeJS 拖拽管理器(仅限 X/Z 平面) - */ -export default class DragControl2 implements IControls { - private viewport: Viewport - private _is_enabled: boolean = true - private domElement: HTMLElement - public isDragging: boolean = false - private isPointerDown: boolean = false - private dragStartMouse: THREE.Vector2 = new THREE.Vector2() - - private dragShadows: THREE.Group | null = null - - init(viewport: Viewport): void { - this.viewport = viewport - - const domElement = this.viewport.renderer.domElement - domElement.addEventListener('pointermove', this.onPointerMove) - domElement.addEventListener('pointerdown', this.onPointerDown) - domElement.addEventListener('pointerup', this.onPointerUp) - domElement.addEventListener('pointerleave', this.onPointerLeave) - - domElement.style.cursor = 'auto' - this.domElement = domElement - } - - - /** - * 设置启用/禁用 - */ - public set enabled(value: boolean) { - this._is_enabled = value - if (!value) { - this.cleanupDrag() - } - } - - public get enabled(): boolean { - return this._is_enabled - } - - /** - * 卸载资源 - */ - dispose(): void { - if (this.domElement) { - this.domElement.removeEventListener('pointermove', this.onPointerMove) - this.domElement.removeEventListener('pointerdown', this.onPointerDown) - this.domElement.removeEventListener('pointerup', this.onPointerUp) - this.domElement.removeEventListener('pointerleave', this.onPointerLeave) - - this.cleanupDrag() - this.domElement.style.cursor = 'auto' - this.viewport = null - } - } - - /** - * 清理拖拽状态 - */ - private cleanupDrag(): void { - if (this.domElement) { - this.domElement.style.cursor = 'auto' - this.isDragging = false - this.isPointerDown = false - this.dragStartMouse.set(NaN, NaN) - this.removeShadows() - } - } - - /** - * 获取当前鼠标坐标(归一化设备坐标) - */ - private getMousePosition(clientX: number, clientY: number): THREE.Vector2 { - const rect = this.domElement.getBoundingClientRect() - return new THREE.Vector2( - ((clientX - rect.left) / rect.width) * 2 - 1, - ((clientY - rect.top) / rect.height) * -2 + 1 - ) - } - - /** - * 射线检测,返回第一个可拖拽对象 - */ - private getIntersectedDraggableObject(mouse: THREE.Vector2): THREE.Object3D | null { - const raycaster = new THREE.Raycaster() - raycaster.setFromCamera(mouse, this.viewport.camera) - - const draggableObjects = this.viewport.entityManager._draggableObjects || [] - const intersects = raycaster.intersectObjects(draggableObjects, true) - - if (intersects.length > 0) { - return getClosestObject(intersects[0].object) - } - } - - /** - * 创建拖拽阴影 - */ - private createShadows(objects: THREE.Object3D[]): void { - this.removeShadows() - - console.log('createShadows', objects.map(obj => obj.name)) - - this.dragShadows = new THREE.Group() - for (const obj of objects) { - if (!obj.userData.entityId) { - console.error('Object does not have entityId:', obj.name) - continue // 跳过没有 entityId 的对象 - } - - const shadow = obj.clone() - - shadow.userData = { - isShadow: true, - entityId: obj.userData.entityId, - originPosition: obj.position.clone() // 保存原始位置 - } - this.dragShadows.add(shadow) - } - this.viewport.scene.add(this.dragShadows) - } - - /** - * 移除阴影 - */ - private removeShadows(): void { - if (this.dragShadows) { - this.viewport.scene.remove(this.dragShadows) - this.dragShadows = null - } - } - - /** - * 更新阴影位置(仅 X/Z 平面) - */ - private updateShadows(newPosition: THREE.Vector2): void { - if (!this.dragShadows) return - - // 计算新位置与拖拽开始位置的偏移量 - const offsetX = newPosition.x - this.dragStartMouse.x - const offsetZ = newPosition.y - this.dragStartMouse.y - - this.dragShadows.children.forEach((shadow, index) => { - const newPosX = shadow.userData.originPosition.x + offsetX - const newPosZ = shadow.userData.originPosition.z + offsetZ - - shadow.position.set(newPosX, shadow.userData.originPosition.y, newPosZ) // 锁定 Y 轴高度 - console.log(`Updated shadow position for ${shadow.name}:`, shadow.position) - }) - } - - private dragComplete = (startPos: THREE.Vector2, targetPos: THREE.Vector2): void => { - // console.log(`Drag completed from ${startPos.toArray()} to ${targetPos.toArray()}`) - this.viewport.stateManager.beginStateUpdate() - for (const object of this.dragShadows.children) { - const entityId = object.userData.entityId - if (entityId) { - const entity = this.viewport.stateManager.findItemById(entityId) - if (entity) { - // 更新实体位置 - entity.tf[0][0] = object.position.x - entity.tf[0][2] = object.position.z - - } else { - system.showErrorDialog('not found entityId:' + entityId) - } - } else { - system.showErrorDialog('not found entity') - } - } - this.viewport.stateManager.endStateUpdate() - EventBus.dispatch('multiselectedObjectChanged', { - multiSelectedObjects: this.viewport.state.multiSelectedObjects - }) - EventBus.dispatch('selectedObjectPropertyChanged', {}) - - this.cleanupDrag() - } - - /** - * pointermove 事件处理 - */ - private onPointerMove = (event: PointerEvent): void => { - if (!this.enabled || !this.domElement) return - - if (!isNaN(this.dragStartMouse.x) && !isNaN(this.dragStartMouse.y) && this.dragShadows) { - this.isDragging = true - // 更新鼠标样式 - this.domElement.style.cursor = 'grabbing' - - // 更新阴影位置(锁定 Y 轴) - this.updateShadows(new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z)) - - } else { - - const mouse = this.getMousePosition(event.clientX, event.clientY) - const intersected = this.getIntersectedDraggableObject(mouse) - if (intersected) { - this.domElement.style.cursor = 'grab' - } else { - this.domElement.style.cursor = 'auto' - } - } - } - - /** - * pointerdown 事件处理 - */ - private onPointerDown = (event: PointerEvent): void => { - if (!this.enabled) return - - const mouse = this.getMousePosition(event.clientX, event.clientY) - const intersected = this.getIntersectedDraggableObject(mouse) - - if (!intersected) return - - this.isPointerDown = true - this.dragStartMouse.set(intersected.position.x, intersected.position.z) - this.viewport.controls.enabled = false - - let selectedObjects = [intersected] - if (this.viewport.state.multiSelectedObjects.length > 0) { - if (this.viewport.state.multiSelectedObjects.includes(intersected)) { - // drag multi-selected objects - selectedObjects = this.viewport.state.multiSelectedObjects - } - } - - this.createShadows(selectedObjects) - } - - - /** - * pointerup 事件处理 - */ - private onPointerUp = (event: PointerEvent): void => { - if (!this.enabled) return - - this.dragStartMouse.set(NaN, NaN) - this.isPointerDown = false - // console.log('enable controls') - this.viewport.controls.enabled = true - - if (this.isDragging) { - const startPos = this.dragStartMouse - const targetPos = new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z) - - if (startPos && targetPos) { - this.dragComplete(startPos, targetPos) - } - } - } - - /** - * pointerleave 事件处理 - */ - private onPointerLeave = (_event: PointerEvent): void => { - if (!this.enabled) return - this.cleanupDrag() - } - - /** - * 取消当前拖拽状态 - */ - cancelDrag(): void { - if (!this.domElement) return - if (this.isDragging || this.isPointerDown) { - this.cleanupDrag() - this.domElement.style.cursor = 'auto' - this.viewport.controls.enabled = true - } - } -} diff --git a/src/core/controls/EsDragControls.ts b/src/core/controls/EsDragControls.ts deleted file mode 100644 index 145097a..0000000 --- a/src/core/controls/EsDragControls.ts +++ /dev/null @@ -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() - } -} diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index d2d85d2..628159b 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -120,8 +120,8 @@ export default class SelectInspect implements IControls { } } + // 清除之前的红色包围盒线框 clearRedSelectionBoxes() { - // 清除之前的红色包围盒线框 if (this.redSelectionGroup.children.length > 0) { for (const child of this.redSelectionGroup.children) { this.redSelectionGroup.remove(child) @@ -132,16 +132,13 @@ export default class SelectInspect implements IControls { this.viewport.scene.add(this.redSelectionGroup) } - /** - * 创建红选包围盒 - */ + // 创建红选包围盒 createRedSelectionBox(object: THREE.Object3D) { // 如果对象没有 entityId,则不创建包围盒线框 if (!object.userData.entityId) { return } const box = new THREE.Box3().setFromObject(object) - box.expandByScalar(Constract.RED_EXPAND_AMOUNT) // 假设 Constract.RED_EXPAND_AMOUNT 已定义 const min = box.min const max = box.max @@ -173,9 +170,7 @@ export default class SelectInspect implements IControls { this.redSelectionGroup.add(selectionBox) } - /** - * 更新选中对象的包围盒线框 - */ + // 更新选中对象的包围盒线框 updateSelectionBox(selectedObject: THREE.Object3D) { this.clearSelectionBox() @@ -214,13 +209,6 @@ export default class SelectInspect implements IControls { const selectionBox = new Line2(lineGeom, this.yellowMaterial) selectionBox.computeLineDistances() - - // 获取包围盒中心并设置位置 - const center = new THREE.Vector3() - box.getCenter(center) - // selectionBox.position.copy(center) - selectionBox.computeLineDistances() - this.selectionBox = selectionBox console.log('selectedItem', this.viewport.state.selectedItem) @@ -242,6 +230,7 @@ export default class SelectInspect implements IControls { this.clearRedSelectionBoxes() } + // 清除当前选中对象的包围盒线框 clearSelectionBox() { if (this.selectionBox) { this.viewport.scene.remove(this.selectionBox) @@ -319,7 +308,7 @@ export default class SelectInspect implements IControls { disposeRect() { if (this.rectangle !== null) { // 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects - this.multipleSelectedObjects() + this.calcRectangleObjectToState() this.viewport.scene.remove(this.rectangle) this.rectangle.geometry.dispose() this.rectangle = null @@ -332,7 +321,7 @@ export default class SelectInspect implements IControls { this.disposeRect() const clickTime = this.clickTime this.clickTime = null - if (Date.now() - clickTime < 200) { + if (Date.now() - clickTime < 100) { // 如果是点击事件,触发选中逻辑 const objects: THREE.Object3D[] = this.viewport.entityManager.getObjectByCanvasMouse(event) if (objects.length > 0 && objects[0]?.userData?.entityId && objects[0]?.userData?.createType !== 'line') { @@ -371,7 +360,7 @@ export default class SelectInspect implements IControls { } } - private multipleSelectedObjects() { + private calcRectangleObjectToState() { if (!this.rectangle || !this.recStartPos) return // 获取矩形的包围盒 diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index b71292e..0ebb8db 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -14,15 +14,13 @@ import { getAllItemTypes } from '@/model/itemType/ItemTypeDefine.ts' import SceneHelp from './SceneHelp' import SelectInspect from '../controls/SelectInspect' import MouseMoveInspect from '../controls/MouseMoveInspect' -import EsDragControls from '../controls/EsDragControls' import EntityManager from '../manager/EntityManager' import InteractionManager from '@/core/manager/InteractionManager' import { calcPositionUseSnap } from '@/core/ModelUtils' import StateManager from '@/core/manager/StateManager.ts' import EventBus from '@/runtime/EventBus.ts' import Constract from '@/core/Constract.ts' -import type { IMeta } from '@/core/base/IMeta.ts' -import DragControl2 from '@/core/controls/EsDragControl2.ts' +import DragControl from '@/core/controls/DragControl.ts' import type { PropertySetter } from '@/core/base/PropertyTypes.ts' /** @@ -41,7 +39,7 @@ export default class Viewport { scene: SceneHelp selectInspect = new SelectInspect() mouseMoveInspect = new MouseMoveInspect() - dragControl = new DragControl2() + dragControl = new DragControl() tools: IControls[] = [ markRaw(this.selectInspect), @@ -581,7 +579,7 @@ export interface ViewportState { multiSelectedObjects: THREE.Object3D[] multiSelectedItems: ItemJson[] multiSelectedEntityIds: string[] - multiSelectedObjectMetas: IMeta[] + multiSelectedObjectMetas: any[] view3DMode: string // Constract.Mode2D | Constract.Mode3D diff --git a/src/modules/gstore/GstoreRenderer.ts b/src/modules/gstore/GstoreRenderer.ts index 4a13d6d..cb5157e 100644 --- a/src/modules/gstore/GstoreRenderer.ts +++ b/src/modules/gstore/GstoreRenderer.ts @@ -31,14 +31,20 @@ export default class GstoreRenderer extends BaseRenderer { override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, objects: THREE.Object3D[]) { super.afterCreateOrUpdatePoint(item, option, objects) - const point = objects[0] - point.position.y = this.defulePositionY - // point.scale.set(item.dt.storeWidth, this.defaultScale.y, item.dt.storeDepth) - point.rotation.set( + const group = objects[0] + group.position.y = this.defulePositionY + group.scale.set(item.dt.storeWidth, this.defaultScale.y, item.dt.storeDepth) + group.rotation.set( THREE.MathUtils.degToRad(item.tf[1][0]), THREE.MathUtils.degToRad(item.tf[1][1]), THREE.MathUtils.degToRad(item.tf[1][2]) ) + + // const planeMesh = group.children[0] as THREE.Mesh + // planeMesh.geometry.dispose() + // + // const newGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth) + // planeMesh.geometry = newGeometry } createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { @@ -56,13 +62,14 @@ export default class GstoreRenderer extends BaseRenderer { createPoint(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { // 创建平面几何体 if (!item.dt.storeWidth || !item.dt.storeDepth) { + system.showErrorDialog('地堆货位缺少 storeWidth 或 storeDepth 属性') return [] } const group = new THREE.Group() group.name = GstoreRenderer.POINT_NAME // 绘制背景矩形框 - const planeGeometry = new THREE.PlaneGeometry(item.dt.storeWidth, item.dt.storeDepth) + const planeGeometry = new THREE.PlaneGeometry(1, 1) planeGeometry.rotateX(Math.PI / 2) const planeMaterial = new THREE.MeshBasicMaterial({ color: '#dee8ee', @@ -99,13 +106,13 @@ export default class GstoreRenderer extends BaseRenderer { // 设置位置 group.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) - const points = [group] - this.fillObjectUserDataFromItem(item, ...points) - this.afterCreateOrUpdatePoint(item, option, points) - this.tempViewport.entityManager.appendObject(item.id, points) - this.appendToScene(...points) + const result = [group] + this.fillObjectUserDataFromItem(item, ...result) + this.afterCreateOrUpdatePoint(item, option, result) + this.tempViewport.entityManager.appendObject(item.id, result) + this.appendToScene(...result) - return points + return result } dispose() {