You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

259 lines
7.4 KiB

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 { getMeta } from '@/core/manager/ModuleManager.ts'
import MouseMoveInspect from '@/core/controls/MouseMoveInspect.ts'
let pdFn, pmFn, puFn
/**
* 选择工具,用于在设计器中显示选中对象的包围盒
*/
export default class SelectInspect implements IControls {
viewport: Viewport
/**
* 线框材质,用于显示选中对象的包围盒
*/
material: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 })
/**
* 矩形材质,用于显示鼠标拖拽选择的矩形区域
*/
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('selectedObjectPropertyChanged', (data) => {
this.updateSelectionBox(this.viewport.state.selectedObject)
})
EventBus.on('entityDeleted', (data) => {
const id = data.deleteEntityId
// 如果删除的是当前选中对象,则清除选中状态
if (this.selectionId === id) {
this.updateSelectionBox(null)
}
})
}
/**
* 更新选中对象的包围盒线框
*/
updateSelectionBox(selectedObject: THREE.Object3D) {
this.disposeSelectionBox()
if (!selectedObject) {
return
}
this.selectionId = selectedObject.userData?.entityId
const expandAmount = 0.2 // 扩展包围盒的大小
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
selectedObject.updateWorldMatrix(false, true)
const box = new THREE.Box3().setFromObject(selectedObject)
box.expandByScalar(expandAmount)
const size = new THREE.Vector3()
box.getSize(size)
const center = new THREE.Vector3()
box.getCenter(center)
// 创建包围盒几何体
const helperGeometry = new THREE.BoxGeometry(size.x, size.y, size.z)
const edgesGeometry = new THREE.EdgesGeometry(helperGeometry)
// 使用 LineGeometry 包装 edgesGeometry
const lineGeom = new LineGeometry()
//@ts-ignore
lineGeom.setPositions(edgesGeometry.attributes.position.array)
const selectionBox = new Line2(lineGeom, this.material)
selectionBox.computeLineDistances()
selectionBox.position.copy(center)
selectionBox.name = 'selectionBox'
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.disposeSelectionBox()
this.disposeRect()
}
disposeSelectionBox() {
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(1, 1),
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()
} 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) {
this.viewport.scene.remove(this.rectangle)
this.rectangle.geometry.dispose()
this.rectangle = null
}
this.recStartPos = null
}
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) {
const object = objects[0]
const entityId = object.userData.entityId
const 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.selectedObjectMeta = getMeta(itemTypeName)
EventBus.dispatch('selectedObjectChanged', {
viewport: markRaw(this.viewport),
selectedObject: this.viewport.state.selectedObject,
selectedItem: this.viewport.state.selectedItem,
selectedEntityId: this.viewport.state.selectedEntityId,
selectedObjectMeta: this.viewport.state.selectedObjectMeta
})
}
}
}
}
}