diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index 7f54c3c..62c3eff 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -19,7 +19,12 @@ export default class SelectInspect implements IControls { /** * 线框材质,用于显示选中对象的包围盒 */ - material: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 }) + yellowMaterial: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 }) + + /** + * 线框材质,用于显示选中对象的包围盒 + */ + redMaterial: LineMaterial = new LineMaterial({ color: 0xff0000, linewidth: 2 }) /** * 矩形材质,用于显示鼠标拖拽选择的矩形区域 @@ -76,6 +81,11 @@ export default class SelectInspect implements IControls { this.updateSelectionBox(this.viewport.state.selectedObject) }) + EventBus.on('multiSelectedObjectsChanged', (data) => { + // 如果多选对象发生变化,清除当前选中对象的包围盒线框 + this.updateMultiSelectionBoxes(data.multiSelectedObjects) + }) + EventBus.on('selectedObjectPropertyChanged', (data) => { this.updateSelectionBox(this.viewport.state.selectedObject) }) @@ -90,6 +100,69 @@ export default class SelectInspect implements IControls { }) } + 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 + } + + // 如果选中的对象小于 0.5,要扩展包围盒 + const RED_EXPAND_AMOUNT = 0.01 // 扩展包围盒的大小 + // 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb) + object.updateWorldMatrix(false, true) + + const box = new THREE.Box3().setFromObject(object) + box.expandByScalar(RED_EXPAND_AMOUNT) + + 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) + const lineGeom = new LineGeometry() + // @ts-ignore + lineGeom.setPositions(edgesGeometry.attributes.position.array) + + const selectionBox = new Line2(lineGeom, this.redMaterial) + selectionBox.computeLineDistances() + selectionBox.position.copy(center) + + this.redSelectionGroup.add(selectionBox) + } + /** * 更新选中对象的包围盒线框 */ @@ -101,12 +174,13 @@ export default class SelectInspect implements IControls { } this.selectionId = selectedObject.userData?.entityId - const expandAmount = 0.2 // 扩展包围盒的大小 + // 如果选中的对象小于 0.5,要扩展包围盒 + const YELLOW_EXPAND_AMOUNT = 0.03 // 扩展包围盒的大小 // 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb) selectedObject.updateWorldMatrix(false, true) const box = new THREE.Box3().setFromObject(selectedObject) - box.expandByScalar(expandAmount) + box.expandByScalar(YELLOW_EXPAND_AMOUNT) const size = new THREE.Vector3() box.getSize(size) @@ -117,16 +191,13 @@ export default class SelectInspect implements IControls { // 创建包围盒几何体 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 + // @ts-ignore lineGeom.setPositions(edgesGeometry.attributes.position.array) - const selectionBox = new Line2(lineGeom, this.material) + const selectionBox = new Line2(lineGeom, this.yellowMaterial) selectionBox.computeLineDistances() selectionBox.position.copy(center) - selectionBox.name = 'selectionBox' this.selectionBox = selectionBox console.log('selectedItem', this.viewport.state.selectedItem) @@ -162,7 +233,7 @@ export default class SelectInspect implements IControls { if (this.recStartPos) { // 创建矩形 this.rectangle = new THREE.Mesh( - new THREE.PlaneGeometry(1, 1), + new THREE.PlaneGeometry(0.001, 0.001), this.rectMaterial ) this.rectangle.name = 'selectRectangle' @@ -176,6 +247,7 @@ export default class SelectInspect implements IControls { } } + updateRectangle(position: THREE.Vector3) { if (!this.rectangle || !this.recStartPos) return // console.log('updateRectangle', this.recStartPos, position) @@ -201,6 +273,8 @@ export default class SelectInspect implements IControls { // 记录鼠标按下位置 this.recStartPos = this.viewport.getClosestIntersection(event) this.createRectangle() + this.viewport.controls.enabled = false // 禁用控制器 + } else { // 为 click 事件添加处理逻辑 this.clickTime = Date.now() @@ -220,11 +294,14 @@ export default class SelectInspect implements IControls { 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) { @@ -253,7 +330,71 @@ export default class SelectInspect implements IControls { selectedObjectMeta: this.viewport.state.selectedObjectMeta }) } + } else { + // 如果没有选中任何对象,清除选中状态 + this.viewport.state.selectedObject = null + this.viewport.state.selectedItem = null + this.viewport.state.selectedEntityId = null + this.viewport.state.selectedObjectMeta = null + EventBus.dispatch('selectedObjectChanged', { + viewport: markRaw(this.viewport), + selectedObject: null, + selectedItem: null, + selectedEntityId: null, + selectedObjectMeta: 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 + }) } + + } diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 557ad11..c87036f 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -66,7 +66,17 @@ export default class Viewport { isReady: false, isUpdating: false, cursorMode: 'normal', - selectedObject: null, + + selectedObject: undefined, + selectedItem: undefined, + selectedEntityId: undefined, + selectedObjectMeta: undefined, + + multiSelectedObjects: [], + multiSelectedItems: [], + multiSelectedEntityIds: [], + multiSelectedObjectMetas: [], + view3DMode: Constract.Mode2D, camera: { position: { x: 0, y: 0, z: 0 }, @@ -550,20 +560,22 @@ export interface ViewportState { cursorMode: string // CursorMode, /** - * 选中的对象 + * 黄选的对象 */ selectedObject: THREE.Object3D | undefined - selectedItem: ItemJson | undefined - selectedEntityId: string | undefined - - view3DMode: string // Constract.Mode2D | Constract.Mode3D + selectedObjectMeta: IMeta | undefined /** - * 选中的对象的元数据 + * 红选的对象集 */ - selectedObjectMeta: IMeta | undefined + multiSelectedObjects: THREE.Object3D[] + multiSelectedItems: ItemJson[] + multiSelectedEntityIds: string[] + multiSelectedObjectMetas: IMeta[] + + view3DMode: string // Constract.Mode2D | Constract.Mode3D /** * 是否正在更新中 diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index fb88c4f..2fa5744 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -548,6 +548,27 @@ export default class EntityManager { r => getClosestObject(r.object) ).filter(obj => obj?.userData && obj.userData.selectable !== false) } + + /** + * 获取指定范围内的所有对象 + */ + getObjectsInBox(startX: number, startZ: number, endX: number, endZ: number) { + const box = new THREE.Box2( + new THREE.Vector2(startX, startZ), + new THREE.Vector2(endX, endZ) + ) + const objectsInBox: THREE.Object3D[] = [] + + for (const [id, objects] of this.objects.entries()) { + for (const obj of objects) { + if (box.containsPoint(new Vector2(obj.position.x, obj.position.z))) { + objectsInBox.push(obj) + } + } + } + + return objectsInBox + } } interface LineDiffItem { diff --git a/src/editor/widgets/property/PropertyView.vue b/src/editor/widgets/property/PropertyView.vue index 4ea9053..67acf02 100644 --- a/src/editor/widgets/property/PropertyView.vue +++ b/src/editor/widgets/property/PropertyView.vue @@ -93,12 +93,14 @@ export default { selectedObjectChanged(state) { const data = state.selectedItem; console.log("selectedObjectChanged data", data) - this.viewport.stateManager.beginStateUpdate() - const item = _.find(this.viewport.stateManager.vdata.items, item=>item.id === data.id) - // item.tf[0][0] = item.tf[0][0] / 2; - console.log("selectedObjectChanged item", item) - // _.extend(item, data) - this.viewport.stateManager.endStateUpdate() + if(data) { + this.viewport.stateManager.beginStateUpdate() + const item = _.find(this.viewport.stateManager.vdata.items, item => item.id === data.id) + // item.tf[0][0] = item.tf[0][0] / 2; + console.log("selectedObjectChanged item", item) + // _.extend(item, data) + this.viewport.stateManager.endStateUpdate() + } }, }, mounted() { @@ -170,4 +172,4 @@ export default { margin: 5px 0; } } - \ No newline at end of file + diff --git a/src/modules/way/WayRenderer.ts b/src/modules/way/WayRenderer.ts index a1e3fee..ac468ab 100644 --- a/src/modules/way/WayRenderer.ts +++ b/src/modules/way/WayRenderer.ts @@ -87,7 +87,7 @@ export default class WayRenderer extends BaseRenderer { const width = 1 const curve = new THREE.LineCurve3(startPosition, endPosition) - const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 8, false) + const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 2, false) const lineMesh = new THREE.Mesh(tubeGeometry, this.lineMaterial) group.add(lineMesh) @@ -169,4 +169,4 @@ export default class WayRenderer extends BaseRenderer { this.pointMaterial.dispose() this.lineMaterial.dispose() } -} \ No newline at end of file +} diff --git a/src/runtime/EventBus.ts b/src/runtime/EventBus.ts index 87350e4..160ce28 100644 --- a/src/runtime/EventBus.ts +++ b/src/runtime/EventBus.ts @@ -6,7 +6,8 @@ export type DispatchNames = 'selectedObjectChanged' | 'catalogChanged' | 'dataLoadComplete' | 'entityDeleted' | - 'selectedObjectPropertyChanged' + 'selectedObjectPropertyChanged' | + 'multiSelectedObjectsChanged' export default { dispatch(name: DispatchNames, data?: any) { @@ -18,4 +19,4 @@ export default { off(name: DispatchNames, callback: (data?: any) => void) { instance.off(name, callback) } -} \ No newline at end of file +}