import * as THREE from 'three' import type IControls from './IControls' 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' let pdFn, pmFn, puFn /** * 选择工具,用于在设计器中显示选中对象的包围盒 */ export default class SelectInspect implements IControls { 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 按住之后的矩形 pdFn = this.onMouseDown.bind(this) this.canvas.addEventListener('pointerdown', pdFn) pmFn = this.onMouseMove.bind(this) this.canvas.addEventListener('pointermove', pmFn) puFn = this.onMouseUp.bind(this) this.canvas.addEventListener('pointerup', puFn) EventBus.on('selectedObjectChanged', (data) => { this.updateSelectionBox(this.viewport.state.selectedObject) }) EventBus.on('multiSelectedObjectsChanged', (data) => { // 如果多选对象发生变化,清除当前选中对象的包围盒线框 this.updateMultiSelectionBoxes(data.multiSelectedObjects) }) EventBus.on('selectedObjectPropertyChanged', (data) => { this.updateSelectionBox(this.viewport.state.selectedObject) }) EventBus.on('multiselectedObjectChanged', (data) => { this.updateMultiSelectionBoxes(data.multiSelectedObjects) }) EventBus.on('entityDeleted', (data) => { const id = data.deleteEntityId // 如果删除的是当前选中对象,则清除选中状态 if (this.selectionId === id) { this.updateSelectionBox(null) } }) } redSelectionGroup = new THREE.Group() private updateMultiSelectionBoxes(multiSelectedObjects: THREE.Object3D[]) { // 为所有多选对象创建包围盒线框 this.clearRedSelectionBoxes() if (!multiSelectedObjects || multiSelectedObjects.length === 0) { return } for (const object of multiSelectedObjects) { if (object.userData.entityId) { this.createRedSelectionBox(object) } } } 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) } /** * 创建红选包围盒 */ 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 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) } /** * 更新选中对象的包围盒线框 */ updateSelectionBox(selectedObject: THREE.Object3D) { this.clearSelectionBox() if (!selectedObject) { return } this.selectionId = selectedObject.userData?.entityId // 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb) selectedObject.updateWorldMatrix(false, true) const box = new THREE.Box3().setFromObject(selectedObject) const min = box.min const max = box.max const corners = [ new THREE.Vector3(min.x - Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, min.z - Constract.YELLOW_EXPAND_AMOUNT), new THREE.Vector3(max.x + Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, min.z - Constract.YELLOW_EXPAND_AMOUNT), new THREE.Vector3(max.x + Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, max.z + Constract.YELLOW_EXPAND_AMOUNT), new THREE.Vector3(min.x - Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, max.z + Constract.YELLOW_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.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) this.viewport.scene.add(selectionBox) } dispose() { this.canvas.removeEventListener('pointerdown', pdFn) pdFn = undefined this.canvas.removeEventListener('pointermove', pmFn) pmFn = undefined this.canvas.removeEventListener('pointerup', puFn) puFn = undefined // 销毁选择工具 this.clearSelectionBox() this.disposeRect() this.clearRedSelectionBoxes() } clearSelectionBox() { if (this.selectionBox) { this.viewport.scene.remove(this.selectionBox) this.selectionBox.geometry.dispose() this.selectionBox = null } } 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) } } 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 ) } 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() } } onMouseMove(event: MouseEvent) { if (!this.recStartPos) { this.disposeRect() } // 更新矩形大小或重新生成矩形 const position = this.viewport.getClosestIntersection(event) if (!position) return this.updateRectangle(position) } disposeRect() { if (this.rectangle !== null) { // 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects this.multipleSelectedObjects() this.viewport.scene.remove(this.rectangle) this.rectangle.geometry.dispose() this.rectangle = null } this.recStartPos = null this.viewport.controls.enabled = true // 启用控制器 } onMouseUp(event: MouseEvent) { this.disposeRect() const clickTime = this.clickTime this.clickTime = null if (Date.now() - clickTime < 200) { // 如果是点击事件,触发选中逻辑 const objects: THREE.Object3D[] = 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] const entityId = object.userData.entityId let item = this.viewport.entityManager.findItemById(entityId) const itemTypeName = object.userData.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, }) } } else { // 如果没有选中任何对象,清除选中状态 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, }) } } } private multipleSelectedObjects() { 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 objects = this.viewport.entityManager.getObjectsInBox(startX, startZ, endX, endZ) // 清空之前的多选对象 this.viewport.state.multiSelectedObjects = [] // 遍历找到的对象,添加到多选对象中 const multiSelectedObjects = [] const multiSelectedItems = [] const multiSelectedEntityIds = [] const multiSelectedObjectMetas = [] for (const object of objects) { 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) // multiSelectedObjectMetas.push(getMeta(object.userData.t)) } } } // 触发多选对象更新事件 this.viewport.state.multiSelectedObjects = markRaw(objects) this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems) this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds this.viewport.state.multiSelectedObjectMetas = multiSelectedObjectMetas EventBus.dispatch('multiSelectedObjectsChanged', { viewport: markRaw(this.viewport), multiSelectedObjects: this.viewport.state.multiSelectedObjects, multiSelectedItems: this.viewport.state.multiSelectedItems, multiSelectedEntityIds: this.viewport.state.multiSelectedEntityIds, multiSelectedObjectMetas: this.viewport.state.multiSelectedObjectMetas }) } }