18 changed files with 318 additions and 984 deletions
@ -1,319 +0,0 @@ |
|||||
import * as THREE from 'three' |
|
||||
import type Viewport from '@/core/engine/Viewport.ts' |
|
||||
import { getClosestObject } from '@/core/ModelUtils.ts' |
|
||||
import EventBus from '@/runtime/EventBus.ts' |
|
||||
import type { Object3DLike } from '@/types/ModelTypes.ts' |
|
||||
import InstancePointManager, { PointManageWrap } from '@/core/manager/InstancePointManager.ts' |
|
||||
import { LineManageWrap } from '@/core/manager/LineSegmentManager.ts' |
|
||||
|
|
||||
/** |
|
||||
* ThreeJS 拖拽管理器(仅限 X/Z 平面) |
|
||||
*/ |
|
||||
export default class DragControl { |
|
||||
private viewport: Viewport |
|
||||
private _is_enabled: boolean = true |
|
||||
private domElement: HTMLElement |
|
||||
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 |
|
||||
|
|
||||
|
|
||||
private static readonly SHADOW_MATERIAL = new THREE.MeshBasicMaterial({ |
|
||||
color: 0x222222, |
|
||||
transparent: true, |
|
||||
opacity: 0.5, |
|
||||
depthWrite: false, |
|
||||
side: THREE.DoubleSide |
|
||||
}) |
|
||||
|
|
||||
public isDragging: boolean = false |
|
||||
|
|
||||
init(viewport: Viewport): void { |
|
||||
this.viewport = viewport |
|
||||
const domElement = this.viewport.renderer.domElement |
|
||||
this.domElement = 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' |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 卸载资源 |
|
||||
*/ |
|
||||
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.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(() => { |
|
||||
this.isPointerDown = true |
|
||||
this.dragStartMouse.set(intersected.position.x, intersected.position.z) |
|
||||
|
|
||||
let selectedObjects = [intersected] |
|
||||
const multiSelected = this.viewport.state.multiSelectedObjects |
|
||||
if (multiSelected.length > 0 && multiSelected.includes(intersected)) { |
|
||||
selectedObjects = multiSelected |
|
||||
} |
|
||||
|
|
||||
this.createShadows(selectedObjects) |
|
||||
this.domElement.style.cursor = 'grabbing' |
|
||||
this.checkStateInterval = setInterval(() => { |
|
||||
if (isNaN(CurrentMouseInfo.x) || isNaN(CurrentMouseInfo.z) || !this.isPointerDown) { |
|
||||
this.cancelDrag() |
|
||||
} |
|
||||
}, 100) // 每100毫秒检查一次状态, 鼠标移出要主动清理
|
|
||||
|
|
||||
}, 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' |
|
||||
this.updateShadows(new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z)) |
|
||||
|
|
||||
} else { |
|
||||
// 射线方法修改 ==========================
|
|
||||
const mouse = this.getMousePosition(event.clientX, event.clientY) |
|
||||
const intersected = this.getIntersectedDraggableObject(mouse) |
|
||||
// =====================================
|
|
||||
// const ids = this.viewport.itemFindManager.getItemsByPosition(CurrentMouseInfo.x, CurrentMouseInfo.z)
|
|
||||
this.domElement.style.cursor = intersected ? 'grab' : 'auto' |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* pointerup 事件处理 |
|
||||
*/ |
|
||||
private onPointerUp = (event: PointerEvent): void => { |
|
||||
if (this.isDragging) { |
|
||||
const startPos = this.dragStartMouse.clone() |
|
||||
const targetPos = new THREE.Vector2(CurrentMouseInfo.x, CurrentMouseInfo.z) |
|
||||
|
|
||||
if (startPos && targetPos && !_.isNaN(startPos.x) && !_.isNaN(startPos.y)) { |
|
||||
this.dragComplete(startPos, targetPos) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.cleanupDrag() |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 清理拖拽状态 |
|
||||
*/ |
|
||||
private cleanupDrag(): void { |
|
||||
if (this.domElement) { |
|
||||
this.domElement.style.cursor = 'auto' |
|
||||
} |
|
||||
|
|
||||
this.isDragging = false |
|
||||
this.isPointerDown = false |
|
||||
this.dragStartMouse.set(NaN, NaN) |
|
||||
|
|
||||
this.removeShadows() |
|
||||
|
|
||||
if (this.viewport) { |
|
||||
this.viewport.controls.enabled = true |
|
||||
} |
|
||||
|
|
||||
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): Object3DLike | undefined { |
|
||||
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(this.viewport, intersects[0].object, intersects[0].instanceId) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 创建拖拽阴影 |
|
||||
*/ |
|
||||
private createShadows(objects: Object3DLike[]): void { |
|
||||
this.removeShadows() |
|
||||
|
|
||||
this.dragShadowsGroup = new THREE.Group() |
|
||||
for (const obj of objects) { |
|
||||
if (_.isNil(_.get(obj, 'userData.entityId'))) { |
|
||||
console.error(`Object ${obj.name} missing entityId`) |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
// 克隆对象的几何体和材质
|
|
||||
// const shadowBox = obj.clone()
|
|
||||
let box: THREE.Box3 |
|
||||
if (obj instanceof THREE.Object3D) { |
|
||||
box = new THREE.Box3().setFromObject(obj) |
|
||||
} else if (obj instanceof PointManageWrap) { |
|
||||
box = obj.createBox3() |
|
||||
} |
|
||||
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.update(({ getEntity, putEntity, deleteEntity, addEntity }) => { |
|
||||
for (const object of this.dragShadowsGroup.children) { |
|
||||
const entityId = object.userData.entityId |
|
||||
if (entityId) { |
|
||||
const entity = getEntity(entityId) |
|
||||
// 更新实体位置
|
|
||||
entity.tf[0][0] = object.position.x |
|
||||
entity.tf[0][2] = object.position.z |
|
||||
putEntity(entity) |
|
||||
|
|
||||
} else { |
|
||||
system.showErrorDialog('not found entity') |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
// EventBus.dispatch('multiSelectedObjectsChanged', {
|
|
||||
// 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 |
|
||||
} |
|
||||
} |
|
||||
@ -1,83 +0,0 @@ |
|||||
import type Viewport from '@/core/engine/Viewport' |
|
||||
import * as THREE from 'three' |
|
||||
|
|
||||
let pmFn, otFn, lvFn |
|
||||
|
|
||||
/** |
|
||||
* 鼠标移动时,将鼠标位置的坐标转换为设计图上的坐标,并设置到 designer.mousePos 属性中 |
|
||||
*/ |
|
||||
export default class MouseMoveInspect { |
|
||||
viewport: Viewport |
|
||||
canvas: HTMLCanvasElement |
|
||||
|
|
||||
constructor() { |
|
||||
} |
|
||||
|
|
||||
init(viewport: Viewport) { |
|
||||
this.viewport = viewport |
|
||||
this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement |
|
||||
|
|
||||
pmFn = this.mouseMove.bind(this) |
|
||||
otFn = this.mouseLv.bind(this) |
|
||||
lvFn = this.mouseLv.bind(this) |
|
||||
this.canvas.addEventListener('pointermove', pmFn) |
|
||||
this.canvas.addEventListener('pointerout', otFn) |
|
||||
this.canvas.addEventListener('mouseleave', lvFn) |
|
||||
} |
|
||||
|
|
||||
dispose() { |
|
||||
this.canvas.removeEventListener('pointermove', pmFn) |
|
||||
pmFn = undefined |
|
||||
this.canvas.removeEventListener('pointerout', otFn) |
|
||||
otFn = undefined |
|
||||
this.canvas.removeEventListener('mouseleave', lvFn) |
|
||||
lvFn = undefined |
|
||||
} |
|
||||
|
|
||||
mouseLv(event: MouseEvent) { |
|
||||
this.viewport.state.mouse.x = NaN |
|
||||
this.viewport.state.mouse.z = NaN |
|
||||
window['CurrentMouseInfo'] = { |
|
||||
x: NaN, |
|
||||
z: NaN, |
|
||||
isShiftKey: false, |
|
||||
isCtrlKey: false, |
|
||||
isAltKey: false, |
|
||||
isMetaKey: false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
mouseMove = _.throttle(function(this: MouseMoveInspect, event: MouseEvent) { |
|
||||
|
|
||||
const pointv = new THREE.Vector2() |
|
||||
pointv.x = event.offsetX / this.viewport.renderer.domElement.offsetWidth |
|
||||
pointv.y = event.offsetY / this.viewport.renderer.domElement.offsetHeight |
|
||||
|
|
||||
const mouse = new THREE.Vector2() |
|
||||
mouse.set((pointv.x * 2) - 1, -(pointv.y * 2) + 1) |
|
||||
|
|
||||
// 当前鼠标所在的点
|
|
||||
const point = this.viewport.getClosestIntersection(event) |
|
||||
if (!point) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
this.viewport.state.mouse.x = point.x |
|
||||
this.viewport.state.mouse.z = point.z |
|
||||
|
|
||||
window['CurrentMouseInfo'] = { |
|
||||
viewport: this.viewport, |
|
||||
x: point.x, |
|
||||
z: point.z, |
|
||||
isShiftKey: event.shiftKey, |
|
||||
isCtrlKey: event.ctrlKey, |
|
||||
isAltKey: event.altKey, |
|
||||
isMetaKey: event.metaKey, |
|
||||
mouse: mouse |
|
||||
} |
|
||||
|
|
||||
}, 1) |
|
||||
|
|
||||
animate(): void { |
|
||||
} |
|
||||
} |
|
||||
@ -1,441 +0,0 @@ |
|||||
import * as THREE from 'three' |
|
||||
import type Viewport from '@/core/engine/Viewport' |
|
||||
import { Line2 } from 'three/examples/jsm/lines/Line2.js' |
|
||||
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' |
|
||||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' |
|
||||
import EventBus from '@/runtime/EventBus' |
|
||||
import { markRaw } from 'vue' |
|
||||
import { getSetter } from '@/core/manager/ModuleManager.ts' |
|
||||
import Constract from '@/core/Constract.ts' |
|
||||
import type { Object3DLike } from '@/types/ModelTypes.ts' |
|
||||
import { PointManageWrap } from '@/core/manager/InstancePointManager.ts' |
|
||||
import { getAABBox, getOBBox } from '@/core/ModelUtils.ts' |
|
||||
|
|
||||
/** |
|
||||
* 选择工具,用于在设计器中显示选中对象的包围盒 |
|
||||
*/ |
|
||||
export default class SelectInspect { |
|
||||
viewport: Viewport |
|
||||
/** |
|
||||
* 线框材质,用于显示选中对象的包围盒 |
|
||||
*/ |
|
||||
yellowMaterial: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 3 }) |
|
||||
|
|
||||
/** |
|
||||
* 线框材质,用于显示选中对象的包围盒 |
|
||||
*/ |
|
||||
redMaterial: LineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 3 }) |
|
||||
|
|
||||
/** |
|
||||
* 矩形材质,用于显示鼠标拖拽选择的矩形区域 |
|
||||
*/ |
|
||||
rectMaterial: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({ |
|
||||
color: 0x000000, |
|
||||
opacity: 0.3, |
|
||||
transparent: true |
|
||||
}) |
|
||||
|
|
||||
/** |
|
||||
* 当前选中对象的矩形选择框 |
|
||||
*/ |
|
||||
rectangle: THREE.Mesh | null = null |
|
||||
|
|
||||
/** |
|
||||
* 当前选中对象的包围盒线框 |
|
||||
*/ |
|
||||
selectionBox: Line2 |
|
||||
|
|
||||
/** |
|
||||
* 当前鼠标所在的画布, 对应 viewport.renderer.domElement |
|
||||
*/ |
|
||||
canvas: HTMLCanvasElement |
|
||||
|
|
||||
/** |
|
||||
* 鼠标按下时记录的起始位置,用于绘制矩形选择框 |
|
||||
*/ |
|
||||
recStartPos: THREE.Vector3 | null |
|
||||
|
|
||||
/** |
|
||||
* 当前黄选的实体 ID |
|
||||
*/ |
|
||||
selectionId: string |
|
||||
|
|
||||
clickTime: number | null = null |
|
||||
|
|
||||
constructor() { |
|
||||
} |
|
||||
|
|
||||
init(viewport: Viewport) { |
|
||||
this.viewport = viewport |
|
||||
this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement |
|
||||
|
|
||||
// 监听 shift 按住之后的矩形
|
|
||||
this.canvas.addEventListener('pointerdown', this.onMouseDown) |
|
||||
this.canvas.addEventListener('pointermove', this.onMouseMove) |
|
||||
this.canvas.addEventListener('pointerup', this.onMouseUp) |
|
||||
|
|
||||
EventBus.on('selectedObjectChanged', this.updateSelectionBox) |
|
||||
EventBus.on('multiSelectedObjectsChanged', this.updateMultiSelectionBoxes) |
|
||||
EventBus.on('selectedObjectPropertyChanged', this.updateSelectionBox) |
|
||||
// EventBus.on('multiselectedObjectChanged', this.updateMultiSelectionBoxes)
|
|
||||
} |
|
||||
|
|
||||
redSelectionGroup = new THREE.Group() |
|
||||
|
|
||||
updateMultiSelectionBoxes = () => { |
|
||||
const multiSelectedObjects = this.viewport.state.multiSelectedObjects |
|
||||
// 为所有多选对象创建包围盒线框
|
|
||||
this.clearRedSelectionBoxes() |
|
||||
|
|
||||
if (!multiSelectedObjects || multiSelectedObjects.length === 0) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
for (const object of multiSelectedObjects) { |
|
||||
if (object.userData.entityId) { |
|
||||
this.createRedSelectionBox(object) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 取消红选
|
|
||||
cancelMultiSelect() { |
|
||||
this.viewport.state.multiSelectedObjects = [] |
|
||||
this.viewport.state.multiSelectedItems = [] |
|
||||
this.viewport.state.multiSelectedEntityIds = [] |
|
||||
EventBus.dispatch('multiSelectedObjectsChanged', { |
|
||||
viewport: markRaw(this.viewport), |
|
||||
multiSelectedObjects: [], |
|
||||
multiSelectedItems: [], |
|
||||
multiSelectedEntityIds: [] |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
// 取消黄选
|
|
||||
cancelSelect() { |
|
||||
this.viewport.state.selectedObject = null |
|
||||
this.viewport.state.selectedItem = null |
|
||||
this.viewport.state.selectedEntityId = null |
|
||||
this.viewport.state.selectedObjectSetter = null |
|
||||
EventBus.dispatch('selectedObjectChanged', { |
|
||||
viewport: markRaw(this.viewport), |
|
||||
selectedObject: null, |
|
||||
selectedItem: null, |
|
||||
selectedEntityId: null, |
|
||||
selectedObjectSetter: null |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
// 根据 entityId 选择对象
|
|
||||
selectById(entityId: string | null) { |
|
||||
if (!entityId) { |
|
||||
this.viewport.state.selectedObject = null |
|
||||
this.viewport.state.selectedItem = null |
|
||||
this.viewport.state.selectedEntityId = null |
|
||||
this.viewport.state.selectedObjectSetter = null |
|
||||
EventBus.dispatch('selectedObjectChanged', { |
|
||||
viewport: markRaw(this.viewport), |
|
||||
selectedObject: null, |
|
||||
selectedItem: null, |
|
||||
selectedEntityId: null, |
|
||||
selectedObjectSetter: null |
|
||||
}) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
const item = this.viewport.entityManager.findItemById(entityId) |
|
||||
const object = this.viewport.entityManager.findObjectById(entityId) |
|
||||
const itemTypeName = item.t |
|
||||
if (item.dt.protected !== true) { |
|
||||
this.viewport.state.selectedObject = markRaw(object) |
|
||||
this.viewport.state.selectedItem = markRaw(item) |
|
||||
this.viewport.state.selectedEntityId = entityId |
|
||||
this.viewport.state.selectedObjectSetter = getSetter(itemTypeName) |
|
||||
EventBus.dispatch('selectedObjectChanged', { |
|
||||
viewport: markRaw(this.viewport), |
|
||||
selectedObject: this.viewport.state.selectedObject, |
|
||||
selectedItem: this.viewport.state.selectedItem, |
|
||||
selectedEntityId: this.viewport.state.selectedEntityId, |
|
||||
selectedObjectSetter: this.viewport.state.selectedObjectSetter |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 多选对象
|
|
||||
multiSelectByIds(entityIds: string[]) { |
|
||||
// 遍历找到的对象,添加到多选对象中
|
|
||||
const multiSelectedObjects = [] |
|
||||
const multiSelectedItems = [] |
|
||||
const multiSelectedEntityIds = [] |
|
||||
for (const entityId of entityIds) { |
|
||||
const object = this.viewport.entityManager.findObjectById(entityId) |
|
||||
if (object.userData.entityId && object.userData.t) { |
|
||||
const item = this.viewport.entityManager.findItemById(object.userData.entityId) |
|
||||
if (item && item.dt.protected !== true) { |
|
||||
multiSelectedObjects.push(object) |
|
||||
multiSelectedItems.push(item) |
|
||||
multiSelectedEntityIds.push(object.userData.entityId) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.viewport.state.multiSelectedObjects = markRaw(multiSelectedObjects) |
|
||||
this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems) |
|
||||
this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds |
|
||||
EventBus.dispatch('multiSelectedObjectsChanged', { |
|
||||
viewport: markRaw(this.viewport), |
|
||||
multiSelectedObjects: this.viewport.state.multiSelectedObjects, |
|
||||
multiSelectedItems: this.viewport.state.multiSelectedItems, |
|
||||
multiSelectedEntityIds: this.viewport.state.multiSelectedEntityIds |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
// 清除之前的红色包围盒线框
|
|
||||
private clearRedSelectionBoxes() { |
|
||||
if (this.redSelectionGroup.children.length > 0) { |
|
||||
for (const child of this.redSelectionGroup.children) { |
|
||||
this.redSelectionGroup.remove(child) |
|
||||
} |
|
||||
} |
|
||||
this.viewport.scene.remove(this.redSelectionGroup) |
|
||||
this.redSelectionGroup = new THREE.Group() |
|
||||
this.viewport.scene.add(this.redSelectionGroup) |
|
||||
} |
|
||||
|
|
||||
// 创建红选包围盒
|
|
||||
private createRedSelectionBox(object: Object3DLike) { |
|
||||
// 如果对象没有 entityId,则不创建包围盒线框
|
|
||||
if (!object.userData.entityId) { |
|
||||
return |
|
||||
} |
|
||||
let box: THREE.Box3 |
|
||||
if (object instanceof PointManageWrap) { |
|
||||
box = object.createBox3() |
|
||||
|
|
||||
} else if (object instanceof THREE.Object3D) { |
|
||||
box = new THREE.Box3().setFromObject(object) |
|
||||
} |
|
||||
|
|
||||
const min = box.min |
|
||||
const max = box.max |
|
||||
|
|
||||
const corners = [ |
|
||||
new THREE.Vector3(min.x - Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, min.z - Constract.RED_EXPAND_AMOUNT), |
|
||||
new THREE.Vector3(max.x + Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, min.z - Constract.RED_EXPAND_AMOUNT), |
|
||||
new THREE.Vector3(max.x + Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, max.z + Constract.RED_EXPAND_AMOUNT), |
|
||||
new THREE.Vector3(min.x - Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, max.z + Constract.RED_EXPAND_AMOUNT) |
|
||||
] |
|
||||
|
|
||||
// 构建矩形边框(4 条边)
|
|
||||
const positions = [] |
|
||||
for (let i = 0; i < 4; i++) { |
|
||||
const p1 = corners[i] |
|
||||
const p2 = corners[(i + 1) % 4] |
|
||||
positions.push(p1.x, p1.y, p1.z) |
|
||||
positions.push(p2.x, p2.y, p2.z) |
|
||||
} |
|
||||
|
|
||||
// 创建几何体
|
|
||||
const lineGeom = new LineGeometry() |
|
||||
const vertices = new Float32Array(positions) |
|
||||
lineGeom.setPositions(vertices) |
|
||||
|
|
||||
const selectionBox = new Line2(lineGeom, this.redMaterial) |
|
||||
selectionBox.computeLineDistances() |
|
||||
|
|
||||
this.redSelectionGroup.add(selectionBox) |
|
||||
} |
|
||||
|
|
||||
// 更新选中对象的包围盒线框
|
|
||||
private updateSelectionBox = () => { |
|
||||
this.clearSelectionBox() |
|
||||
|
|
||||
const item = this.viewport.state.selectedItem |
|
||||
if (!item) { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// 构造 positions 数组
|
|
||||
const edgePositions = getOBBox(item) // getOBBox(item) // getAABBox(item)
|
|
||||
const positions = [] |
|
||||
for (let i = 0; i < edgePositions.length; i += 2) { |
|
||||
const p1 = edgePositions[i] |
|
||||
const p2 = edgePositions[i + 1] |
|
||||
positions.push(p1.x, p1.y, p1.z) |
|
||||
positions.push(p2.x, p2.y, p2.z) |
|
||||
} |
|
||||
|
|
||||
const lineGeom = new LineGeometry().setPositions(new Float32Array(positions)) |
|
||||
const selectionBox = new Line2(lineGeom, this.yellowMaterial) |
|
||||
selectionBox.computeLineDistances() |
|
||||
selectionBox.scale.set(1, 1, 1) |
|
||||
this.selectionBox = selectionBox |
|
||||
|
|
||||
console.log('selectedItem', this.viewport.state.selectedItem) |
|
||||
|
|
||||
this.viewport.scene.add(selectionBox) |
|
||||
} |
|
||||
|
|
||||
dispose() { |
|
||||
this.canvas.removeEventListener('pointerdown', this.onMouseDown) |
|
||||
this.canvas.removeEventListener('pointermove', this.onMouseMove) |
|
||||
this.canvas.removeEventListener('pointerup', this.onMouseUp) |
|
||||
|
|
||||
// 销毁选择工具
|
|
||||
this.clearSelectionBox() |
|
||||
this.disposeRect() |
|
||||
this.clearRedSelectionBoxes() |
|
||||
} |
|
||||
|
|
||||
// 清除当前选中对象的包围盒线框
|
|
||||
private clearSelectionBox = () => { |
|
||||
if (this.selectionBox) { |
|
||||
this.viewport.scene.remove(this.selectionBox) |
|
||||
this.selectionBox.geometry.dispose() |
|
||||
this.selectionBox = null |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private createRectangle = () => { |
|
||||
if (this.rectangle !== null) { |
|
||||
this.disposeRect() |
|
||||
} |
|
||||
if (this.recStartPos) { |
|
||||
// 创建矩形
|
|
||||
this.rectangle = new THREE.Mesh( |
|
||||
new THREE.PlaneGeometry(0.001, 0.001), |
|
||||
this.rectMaterial |
|
||||
) |
|
||||
this.rectangle.name = 'selectRectangle' |
|
||||
this.rectangle.rotation.x = -Math.PI / 2 // 关键!让平面正对相机
|
|
||||
this.rectangle.position.set( |
|
||||
this.recStartPos.x, |
|
||||
this.recStartPos.y, |
|
||||
this.recStartPos.z |
|
||||
) |
|
||||
this.viewport.scene.add(this.rectangle) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private updateRectangle = (position: THREE.Vector3) => { |
|
||||
if (!this.rectangle || !this.recStartPos) return |
|
||||
// console.log('updateRectangle', this.recStartPos, position)
|
|
||||
|
|
||||
const width = position.x - this.recStartPos.x |
|
||||
const height = position.z - this.recStartPos.z |
|
||||
|
|
||||
const newWidth = Math.abs(width) |
|
||||
const newHeight = Math.abs(height) |
|
||||
|
|
||||
// 清理旧几何体
|
|
||||
this.rectangle.geometry.dispose() |
|
||||
this.rectangle.geometry = new THREE.PlaneGeometry(newWidth, newHeight) |
|
||||
this.rectangle.position.set( |
|
||||
this.recStartPos.x + width / 2, |
|
||||
this.recStartPos.y, |
|
||||
this.recStartPos.z + height / 2 |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
private onMouseDown = (event: MouseEvent) => { |
|
||||
if (event.shiftKey) { |
|
||||
// 记录鼠标按下位置
|
|
||||
this.recStartPos = this.viewport.getClosestIntersection(event) |
|
||||
this.createRectangle() |
|
||||
this.viewport.controls.enabled = false // 禁用控制器
|
|
||||
|
|
||||
} else { |
|
||||
// 为 click 事件添加处理逻辑
|
|
||||
this.clickTime = Date.now() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private onMouseMove = (event: MouseEvent) => { |
|
||||
if (!this.recStartPos) { |
|
||||
this.disposeRect() |
|
||||
} |
|
||||
// 更新矩形大小或重新生成矩形
|
|
||||
const position = this.viewport.getClosestIntersection(event) |
|
||||
if (!position) return |
|
||||
this.updateRectangle(position) |
|
||||
} |
|
||||
|
|
||||
private onMouseUp = (event: MouseEvent) => { |
|
||||
this.disposeRect() |
|
||||
const clickTime = this.clickTime |
|
||||
this.clickTime = null |
|
||||
if (Date.now() - clickTime < 200) { |
|
||||
// 如果是点击事件,触发选中逻辑
|
|
||||
const objects: Object3DLike[] = this.viewport.entityManager.getObjectByCanvasMouse(event) |
|
||||
if (objects.length > 0 && objects[0]?.userData?.entityId && objects[0]?.userData?.createType !== 'line') { |
|
||||
console.log('mouseClick', objects) |
|
||||
const object = objects[0] |
|
||||
this.selectById(object.userData.entityId) |
|
||||
|
|
||||
} else { |
|
||||
// 如果没有选中任何对象,清除选中状态
|
|
||||
this.selectById(null) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private disposeRect = () => { |
|
||||
if (this.rectangle !== null) { |
|
||||
// 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects
|
|
||||
this.calcRectangleObjectToState() |
|
||||
this.viewport.scene.remove(this.rectangle) |
|
||||
this.rectangle.geometry.dispose() |
|
||||
this.rectangle = null |
|
||||
} |
|
||||
this.recStartPos = null |
|
||||
this.viewport.controls.enabled = true // 启用控制器
|
|
||||
} |
|
||||
|
|
||||
private calcRectangleObjectToState = () => { |
|
||||
if (!this.rectangle || !this.recStartPos) return |
|
||||
|
|
||||
// 获取矩形的包围盒
|
|
||||
const box = new THREE.Box3().setFromObject(this.rectangle) |
|
||||
|
|
||||
// 获取盒子的 startX, startZ, endX, endZ
|
|
||||
const startX = box.min.x |
|
||||
const startZ = box.min.z |
|
||||
const endX = box.max.x |
|
||||
const endZ = box.max.z |
|
||||
|
|
||||
// 查找所有在矩形内的对象
|
|
||||
const ids = this.viewport.itemFindManager.getItemsByRect(startX, startZ, endX, endZ) |
|
||||
|
|
||||
// 清空之前的多选对象
|
|
||||
this.viewport.state.multiSelectedObjects = [] |
|
||||
|
|
||||
// 遍历找到的对象,添加到多选对象中
|
|
||||
const multiSelectedObjects = [] |
|
||||
const multiSelectedItems = [] |
|
||||
const multiSelectedEntityIds = [] |
|
||||
for (const id of ids) { |
|
||||
const object = this.viewport.entityManager.findObjectById(id) |
|
||||
if (object.userData.entityId && object.userData.t) { |
|
||||
const item = this.viewport.entityManager.findItemById(object.userData.entityId) |
|
||||
if (item && item.dt.protected !== true) { |
|
||||
multiSelectedObjects.push(object) |
|
||||
multiSelectedItems.push(item) |
|
||||
multiSelectedEntityIds.push(id) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 触发多选对象更新事件
|
|
||||
this.viewport.state.multiSelectedObjects = markRaw(multiSelectedObjects) |
|
||||
this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems) |
|
||||
this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds |
|
||||
EventBus.dispatch('multiSelectedObjectsChanged', { |
|
||||
viewport: markRaw(this.viewport), |
|
||||
multiSelectedObjects: this.viewport.state.multiSelectedObjects, |
|
||||
multiSelectedItems: this.viewport.state.multiSelectedItems, |
|
||||
multiSelectedEntityIds: this.viewport.state.multiSelectedEntityIds |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,5 +1,26 @@ |
|||||
import BaseEntity from '@/core/base/BaseItemEntity.ts' |
import BaseEntity from '@/core/base/BaseItemEntity.ts' |
||||
|
import Constract from '@/core/Constract.ts' |
||||
|
import * as THREE from 'three' |
||||
|
|
||||
export default class PalletEntity extends BaseEntity { |
export default class CartonSetting extends BaseEntity { |
||||
|
/** |
||||
|
* 默认高度 |
||||
|
*/ |
||||
|
defulePositionY: number = Constract.HEIGHT_WAY |
||||
|
|
||||
|
/** |
||||
|
* 默认缩放 |
||||
|
*/ |
||||
|
defaultScale: THREE.Vector3 = new THREE.Vector3(1, 1, 1) |
||||
|
|
||||
|
/** |
||||
|
* 默认旋转角度 |
||||
|
*/ |
||||
|
defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 默认颜色 |
||||
|
*/ |
||||
|
defaultColor: THREE.Color = new THREE.Color(0xc29a70) |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue