From d8d0ed688060ef94bb71d46f6579d8d294b33b7e Mon Sep 17 00:00:00 2001 From: yvan Date: Tue, 3 Jun 2025 01:13:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=80=89=E4=B8=AD=E5=AE=9E?= =?UTF-8?q?=E4=BD=93=20ID=20=E4=BB=A5=E6=94=AF=E6=8C=81=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=9B=B4=E6=96=B0=E5=92=8C=E5=AE=9E=E4=BD=93?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/ModelUtils.ts | 92 +++++++++++++++++++++--------- src/core/base/BaseInteraction.ts | 101 +++++++++++++++++++++------------ src/core/controls/EsDragControls.ts | 2 + src/core/controls/SelectInspect.ts | 13 ++++- src/core/manager/EntityManager.ts | 4 ++ src/core/manager/InteractionManager.ts | 16 +++--- src/core/manager/StateManager.ts | 21 +++++++ src/runtime/EventBus.ts | 6 +- 8 files changed, 183 insertions(+), 72 deletions(-) diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 1712690..146c474 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -153,21 +153,53 @@ export function escByKeyboard() { return } - viewport.state.cursorMode = 'normal' + viewport.interactionManager.exitInteraction() system.msg('操作已取消') } +/** + * 查找指定点附近指定距离内的所有 stateManager 下的 item 对象 + * 如果要修改数据做提交, 执行此方法之前必须执行 beginStateUpdate + * @param viewport 视窗 + * @param point 点位 x,z 坐标 + * @param distance 距离 + */ +export function findStateItemsByDistance(viewport: Viewport, point: Vector2, distance: number): ItemJson[] { + const result: ItemJson[] = [] + + for (const item of viewport.stateManager.vdata.items) { + // 安全校验 tf 结构 + if (!item.tf || !Array.isArray(item.tf) || item.tf[0].length < 3) { + continue + } + + const [x, , z] = item.tf[0] + const itemPoint = new Vector2(x, z) + const dist = itemPoint.distanceTo(point) + + if (dist <= distance) { + result.push(item) + } + } + + // 按距离升序排序(近距离优先) + return result.sort((a, b) => { + const aPos = new Vector2(a.tf[0][0], a.tf[0][2]) + const bPos = new Vector2(b.tf[0][0], b.tf[0][2]) + return aPos.distanceTo(point) - bPos.distanceTo(point) + }) +} + export function quickCopyByMouse() { // 获取鼠标位置,查看鼠标是否在某个 viewport 的画布上,并取得该 viewport - const currentMouseInfo = window['CurrentMouseInfo'] - if (!currentMouseInfo?.viewport || !currentMouseInfo.x || !currentMouseInfo.z) { + if (!CurrentMouseInfo?.viewport || !CurrentMouseInfo.x || !CurrentMouseInfo.z) { system.msg('无法获取鼠标位置') return } - const x = currentMouseInfo.x - const z = currentMouseInfo.z - const viewport: Viewport = currentMouseInfo.viewport + const x = CurrentMouseInfo.x + const z = CurrentMouseInfo.z + const viewport: Viewport = CurrentMouseInfo.viewport // const point: THREE.Vector2 = currentMouseInfo.mouse // // const ray = new THREE.Raycaster() @@ -181,26 +213,34 @@ export function quickCopyByMouse() { // console.log('intersections:', intersections) // 如果不在线上,查找0.2米内的有效点 Object3D, 如果有,则以这个点为起点, 延伸同类型的点,并让他们相连 - const r = findObject3DByCondition(viewport.scene, object => { - // 判断 object 是否是有效的 Object3D, 并且是当前 viewport 的对象 - if (object instanceof THREE.Object3D && object.visible && - object.userData.type && viewport.toolbox[object.userData.type]) { - - const toolbox: Toolbox = viewport.toolbox[object.userData.type] - - // 检查是否在 0.2 米内 - const distance = object.position.distanceTo(new THREE.Vector3(x, 0, z)) - if (distance < 0.2) { - // 找到一个有效点,执行复制操作 - viewport.toolStartObject = object - viewport.state.cursorMode = object.userData.type - // toolbox.start(object) - system.msg('连线成功') - return true - } - } - return false - }) + const items = findStateItemsByDistance(viewport, new Vector2(x, z), 0.2) + if (items[0]) { + // 找到一个有效点,执行复制操作 + viewport.interactionManager.startInteraction(items[0].t, { startPoint: items[0].id }) + system.msg('连线成功') + return + } + + // const r = findObject3DByCondition(viewport.scene, object => { + // // 判断 object 是否是有效的 Object3D, 并且是当前 viewport 的对象 + // if (object instanceof THREE.Object3D && object.visible && + // object.userData.type && viewport.toolbox[object.userData.type]) { + // + // const toolbox: Toolbox = viewport.toolbox[object.userData.type] + // + // // 检查是否在 0.2 米内 + // const distance = object.position.distanceTo(new THREE.Vector3(x, 0, z)) + // if (distance < 0.2) { + // // 找到一个有效点,执行复制操作 + // viewport.toolStartObject = object + // viewport.state.cursorMode = object.userData.type + // // toolbox.start(object) + // system.msg('连线成功') + // return true + // } + // } + // return false + // }) if (!r || r.length === 0) { system.msg('鼠标所在位置,没有可复制的对象') diff --git a/src/core/base/BaseInteraction.ts b/src/core/base/BaseInteraction.ts index 21e1010..84595a9 100644 --- a/src/core/base/BaseInteraction.ts +++ b/src/core/base/BaseInteraction.ts @@ -120,10 +120,10 @@ export default abstract class BaseInteraction { this.viewerDom = this.viewport.viewerDom this.canvas = this.viewport.renderer.domElement if (option.startPoint) { - this.linkStartPointId = option.startPoint.userData?.entityId - if (!this.linkStartPointId) { - this.linkStartPointObject = this.viewport.entityManager.findObjectsById(this.linkStartPointId)?.[0] - } + this.linkStartPointId = option.startPoint + } + if (this.linkStartPointId) { + this.linkStartPointObject = this.viewport.entityManager.findObjectsById(this.linkStartPointId)?.[0] } pdFn = this.mousedown.bind(this) @@ -264,47 +264,76 @@ export default abstract class BaseInteraction { } this.lastClickTime = now - const renderer = getRenderer(this.itemTypeName) + const from = this.viewport.stateManager.findItemById(this.linkStartPointId) + if (!from) { + system.showErrorDialog(`Cannot find state item: ${this.linkStartPointId}`) + return + } + + // 如果正式的点命中到同类型的节点上,则不添加新的点,只牵线到该点 + let catchPoint: ItemJson | null = this.viewport.stateManager.findItemByPosition(point, this.itemTypeName) - const defaultScale = renderer.getDefaultScale() - const defaultRotation = renderer.getDefaultRotation() - - // 添加正式点 - const itemJson = { - id: system.createUUID(), - t: this.itemTypeName, - v: true, - tf: [ - [point.x, point.y, point.z], - [defaultRotation.x, defaultRotation.y, defaultRotation.z], - [defaultScale.x, defaultScale.y, defaultScale.z] - ], - dt: { - in: [] as string[], - out: [] as string[], - center: [] as string[] + if (catchPoint) { + // 连线到目标点 + if (this.linkStartPointId === catchPoint.id) { + // 自己连接自己,忽略 + system.msg('Cannot link to itself.') + return } - } as ItemJson + // 关联2个点 - // 关联2个点 - const from = this.viewport.entityManager.findItemById(this.linkStartPointId) - if (this.linkStartPointId && from) { - itemJson.dt.center.push(this.linkStartPointId) - from.dt.center.push(itemJson.id) - } + if (this.linkStartPointId && from) { + catchPoint.dt.center.push(this.linkStartPointId) + from.dt.center.push(catchPoint.id) + } - // 提交状态管理器 - const stateManager = this.viewport.stateManager - stateManager.beginStateUpdate({ createFromInteraction: true }) - stateManager.vdata.items.push(itemJson) - stateManager.endStateUpdate() + // 提交状态管理器 + const stateManager = this.viewport.stateManager + stateManager.beginStateUpdate({ createFromInteraction: true }) + catchPoint.dt.center.push(this.linkStartPointId) + from.dt.center.push(catchPoint.id) + stateManager.endStateUpdate() + + } else { + const renderer = getRenderer(this.itemTypeName) + + const defaultScale = renderer.getDefaultScale() + const defaultRotation = renderer.getDefaultRotation() + + // 添加正式点 + catchPoint = { + id: system.createUUID(), + t: this.itemTypeName, + v: true, + tf: [ + [point.x, point.y, point.z], + [defaultRotation.x, defaultRotation.y, defaultRotation.z], + [defaultScale.x, defaultScale.y, defaultScale.z] + ], + dt: { + in: [] as string[], + out: [] as string[], + center: [] as string[] + } + } as ItemJson + + + // 提交状态管理器 + const stateManager = this.viewport.stateManager + stateManager.beginStateUpdate({ createFromInteraction: true }) + // 关联2个点 + stateManager.vdata.items.push(catchPoint) + catchPoint.dt.center.push(this.linkStartPointId) + from.dt.center.push(catchPoint.id) + stateManager.endStateUpdate() + } // 把点加入拖拽控制器 // this.viewport.dragControl.setDragObjects(marker, 'push') // 更新起始点为新添加的点 - this.linkStartPointId = itemJson.id - this.linkStartPointObject = this.viewport.entityManager.findObjectsById(itemJson.id)?.[0] + this.linkStartPointId = catchPoint.id + this.linkStartPointObject = this.viewport.entityManager.findObjectsById(catchPoint.id)?.[0] // 删除临时点 this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker) diff --git a/src/core/controls/EsDragControls.ts b/src/core/controls/EsDragControls.ts index 1911383..6a0b66e 100644 --- a/src/core/controls/EsDragControls.ts +++ b/src/core/controls/EsDragControls.ts @@ -151,6 +151,8 @@ export default class EsDragControls { const ret = this.currentInteraction?.dragPointComplete(this.viewport, e) if (!ret) return + EventBus.dispatch('selectedObjectPropertyChanged', {}) + // 拖拽结束启用其他控制器 this.viewport.controls.enabled = true this.isDragging = false diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index 3395731..1d07855 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -47,6 +47,11 @@ export default class SelectInspect implements IControls { */ recStartPos: THREE.Vector3 | null + /** + * 当前黄选的实体 ID + */ + selectionId: string + constructor() { } @@ -54,6 +59,7 @@ export default class SelectInspect implements IControls { 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) @@ -65,11 +71,15 @@ export default class SelectInspect implements IControls { 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.viewport.state.selectedEntityId === id) { + if (this.selectionId === id) { this.updateSelectionBox(null) } }) @@ -84,6 +94,7 @@ export default class SelectInspect implements IControls { if (!selectedObject) { return } + this.selectionId = selectedObject.userData?.entityId const expandAmount = 0.2 // 扩展包围盒的大小 // 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb) diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index 298e3d1..9af1f09 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -506,6 +506,10 @@ export default class EntityManager { } return this.entities.get(linkStartPointId) } + + getEntityMap() { + return this.entities + } } interface LineDiffItem { diff --git a/src/core/manager/InteractionManager.ts b/src/core/manager/InteractionManager.ts index 3e31109..422f599 100644 --- a/src/core/manager/InteractionManager.ts +++ b/src/core/manager/InteractionManager.ts @@ -9,7 +9,7 @@ export interface InteractionOption { /** * 起点对象 */ - startPoint?: THREE.Object3D + startPoint?: string } /** @@ -24,7 +24,7 @@ export default class InteractionManager implements IControls { currentTool: BaseInteraction | null = null //搭配 state.cursorMode = xxx 之后, currentTool.start(第一个参数) 使用 - toolStartObject: THREE.Object3D | null = null + option: InteractionOption | null = null init(viewport: Viewport) { this.viewport = viewport @@ -40,7 +40,7 @@ export default class InteractionManager implements IControls { * 实现方法:启动交互 */ startInteraction(itemType: string, option: InteractionOption = {}): void { - this._setActiveInteraction(itemType, option.startPoint) + this._setActiveInteraction(itemType, option) } /** @@ -59,7 +59,7 @@ export default class InteractionManager implements IControls { system.msg('退出新建模式') } - private _setActiveInteraction(mode: string, startPoint?: THREE.Object3D): void { + private _setActiveInteraction(mode: string, option?: InteractionOption): void { const state = this.viewport.state if (!state.isReady) return @@ -70,7 +70,7 @@ export default class InteractionManager implements IControls { } // 如果已有相同类型的交互,并且起点一致,无需重复初始化 - if (this.currentTool?.itemTypeName === mode && this.toolStartObject === startPoint) { + if (this.currentTool?.itemTypeName === mode) { return } @@ -85,15 +85,15 @@ export default class InteractionManager implements IControls { } // 保存参数 - if (startPoint) { - this.toolStartObject = startPoint + if (option) { + this.option = option } // 初始化交互 this.currentTool = interaction this.viewport.dragControl.dragControls.enabled = false - this.currentTool.start(this.viewport, { startPoint: this.toolStartObject }) + this.currentTool.start(this.viewport, this.option) // 更新 UI 状态 this.viewport.viewerDom.style.cursor = 'crosshair' diff --git a/src/core/manager/StateManager.ts b/src/core/manager/StateManager.ts index 6fda8b3..2cf149e 100644 --- a/src/core/manager/StateManager.ts +++ b/src/core/manager/StateManager.ts @@ -1,4 +1,5 @@ import _ from 'lodash' +import * as THREE from 'three' import localforage from 'localforage' import type EntityManager from './EntityManager' import { markRaw, reactive, ref } from 'vue' @@ -545,6 +546,26 @@ export default class StateManager { success: false } } + + /** + * 根据 ID 查找实体 + */ + findItemById(linkStartPointId: string): ItemJson | undefined { + return _.find(this.vdata.items, item => item.id === linkStartPointId) + } + + /** + * 根据位置查找实体, 可带类型 + */ + findItemByPosition(point: THREE.Vector3, itemTypeName?: string): ItemJson | undefined { + for (const item of this.vdata.items) { + if (item.tf?.[0]?.[0] === point.x && item.tf?.[0]?.[2] === point.z) { + if (!itemTypeName || item.t === itemTypeName) { + return item + } + } + } + } } export interface StateUpdateOption { diff --git a/src/runtime/EventBus.ts b/src/runtime/EventBus.ts index 46ca995..87350e4 100644 --- a/src/runtime/EventBus.ts +++ b/src/runtime/EventBus.ts @@ -2,7 +2,11 @@ import mitt from 'mitt' const instance = mitt() -export type DispatchNames = 'selectedObjectChanged' | 'catalogChanged' | 'dataLoadComplete' | 'entityDeleted' +export type DispatchNames = 'selectedObjectChanged' | + 'catalogChanged' | + 'dataLoadComplete' | + 'entityDeleted' | + 'selectedObjectPropertyChanged' export default { dispatch(name: DispatchNames, data?: any) {