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.
440 lines
14 KiB
440 lines
14 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 { 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)
|
|
|
|
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()
|
|
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.calcRectangleObjectToState()
|
|
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 < 100) {
|
|
// 如果是点击事件,触发选中逻辑
|
|
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 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 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
|
|
})
|
|
}
|
|
|
|
|
|
cancelMultiSelect() {
|
|
this.viewport.state.multiSelectedObjects = []
|
|
this.viewport.state.multiSelectedItems = []
|
|
this.viewport.state.multiSelectedEntityIds = []
|
|
this.viewport.state.multiSelectedObjectMetas = []
|
|
EventBus.dispatch('multiSelectedObjectsChanged', {
|
|
viewport: markRaw(this.viewport),
|
|
multiSelectedObjects: [],
|
|
multiSelectedItems: [],
|
|
multiSelectedEntityIds: [],
|
|
multiSelectedObjectMetas: []
|
|
})
|
|
}
|
|
|
|
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
|
|
})
|
|
}
|
|
}
|
|
|