import * as THREE from 'three' import type Viewport from '@/core/engine/Viewport' import type BaseRenderer from '@/core/base/BaseRenderer' import { getRenderer } from './ModuleManager' import { getClosestObject, getLineId, parseLineId } from '@/core/ModelUtils' import { Vector2 } from 'three' /** * 实体管理器 * 缓存所有 数据(ItemJson)和他们的关系, 以及渲染对象 THREE.Object3D * 数据更新的流程是: * 1. 状态管理器 StateManager 发出数据被修改的请求, 这些请求通常只有 ItemJson 数据 * 2. 实体管理器 (EntityManager) 需要计算所有相关的点和线的变化. 在 endUpdate 时需要根据 center / in / out 关系, 计算出: 点的创建 / 点的更新 / 点的删除 / 线的创建 / 线的更新 / 线的删除 * 3. 通知对应的渲染器 (Renderer) 进行点和线的创建 / 更新 / 删除 */ export default class EntityManager { viewport: Viewport // 所有数据点的实体 private readonly entities = new Map() // 关系索引 private readonly relationIndex = new Map() // 所有 THREEJS "点"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组 private readonly objects = new Map() // 所有 THREEJS "可选中"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组 private readonly _selectableObjects: THREE.Object3D[] = [] // 所有 THREEJS "线"对象, 检索值是"线实体"的 id, 取值方式是 {type}${startId}${endId}, 值是 THREE.Object3D 数组 private readonly lines = new Map() // 差量渲染器 private readonly diffRenderer = new Map() // 线差量记录 private readonly lineDiffs = { create: new Map(), update: new Map(), delete: new Map() } // 记录所有发生过变化的实体, 用于在 endEntityUpdate 时进行写回给 StateManager private readonly writeBackEntities = new Set() isUpdating = false dispose() { // 清理所有差量渲染器 for (const renderer of this.diffRenderer.values()) { renderer.dispose() } this.diffRenderer.clear() // 清理所有实体和关系索引 this.entities.clear() this.relationIndex.clear() this.objects.clear() this.lines.clear() this.writeBackEntities.clear() } /** * 克隆需要回写的实体集合 */ cloneWriteBackEntities(): Map { const entities = new Map() for (const id of this.writeBackEntities) { const entity = this.entities.get(id) if (!entity) continue entities.set(id, _.cloneDeep(entity)) } console.log('需要回写', entities.size, '行数据') return entities } init(viewport: Viewport) { this.viewport = viewport } /** * 批量更新开始 */ beginEntityUpdate(): void { this.isUpdating = true this.viewport.beginViewUpdate() this.writeBackEntities.clear() this.diffRenderer.clear() this.lineDiffs.create.clear() this.lineDiffs.update.clear() this.lineDiffs.delete.clear() } /** * 创建或更新一个实体, 这个点的 center[] / in[] / out[] 关联的点, 可能都要对应进行关联 */ createOrUpdateEntity(entityRaw: ItemJson, option: EntityCudOption = {}): void { if (!entityRaw?.id) { throw new Error('Entity must have an id') } const entity = _.cloneDeep(entityRaw) as ItemJson const originEntity = this.entities.get(entity.id) // 找到这个数据的渲染器 const renderer = this.getDiffRenderer(entity.t) // 先判断坐标是否变化 const coordinateChanged = originEntity?.tf && entity?.tf && !_.isEqual(originEntity.tf[0], entity.tf[0]) this.entities.set(entity.id, entity) // 更新关系网 this.updateRelations(entity, originEntity) if (coordinateChanged) { this.writeBackEntities.add(entity.id) // 点的坐标发生变化, 要通知所有关联线更新 const relation = this.relationIndex.get(entity.id) if (relation) { for (const type of (['center', 'in', 'out'] as LinkType[])) { const relatedIds = relation[type] if (!relatedIds) continue for (const relatedId of relatedIds) { const lineId = getLineId(entity.id, relatedId, type) console.log(`[update] ${entity.id} -> ${relatedId} [${type}] => ${lineId}`) this.lineDiffs.update.set(lineId, { startId: entity.id, endId: relatedId, type }) this.writeBackEntities.add(relatedId) // 如果是双向线(比如 center),也要反向加一次 if (type === 'center') { this.lineDiffs.update.set(lineId, { startId: relatedId, endId: entity.id, type }) } } } } } if (typeof originEntity === 'undefined') { renderer.createPoint(entity, option as RendererCudOption) } else { option.originEntity = _.cloneDeep(originEntity) renderer.updatePoint(entity, option as RendererCudOption) } } /** * 删除实体, 与这个点有关的所有线都要删除, 与这个点有关系的点,都要对应的 center[] / in[] / out[] 都要断绝关系 */ deleteEntity(id: string, option: EntityCudOption = {}): void { const entity = this.entities.get(id) if (!entity) return option.originEntity = _.cloneDeep(entity) this.writeBackEntities.add(id) // 先生成线差量,再清理关系 this.generateLineDiffsForDelete(id) this.removeRelations(id) // 清理关系 this.entities.delete(id) // 删除实体 this.getDiffRenderer(entity.t).deletePoint(id, option as RendererCudOption) } generateLineDiffsForDelete(id: string): void { const relations = this.relationIndex.get(id) if (!relations) return const removeLine = (relatedId: string, type: LinkType) => { const lineId = getLineId(id, relatedId, type) this.writeBackEntities.add(relatedId) this.lineDiffs.delete.set(lineId, { startId: id, endId: relatedId, type }) } relations.center.forEach(relatedId => removeLine(relatedId, 'center')) relations.input.forEach(relatedId => removeLine(relatedId, 'in')) relations.output.forEach(relatedId => removeLine(relatedId, 'out')) } /** * 批量更新结束, 结束后会触发视窗的渲染 * 这个方法最重要的是进行连线逻辑的处理 * - 如果进行了添加, 那么这个点的 center[] / in[] / out[] 关联的点, 都要对应进行关联 * - 如果进行了删除, 与这个点有关的所有线都要删除, 与这个点有关系的点,都要对应的 center[] / in[] / out[] 都要断绝关系 * - 如果进行了更新, 如果改了颜色/位置, 则需要在UI上进行对应修改,如果改了关系,需要与关联的节点批量调整 * 将影响到的所有数据, 都变成一个修改集合, 统一调用对应单元类型渲染器(BaseRenderer)的 createPoint / deletePoint / updatePoint / createLine / updateLine / deleteLine 方法 */ endEntityUpdate(): void { for (const [itemTypeName, renderer] of this.diffRenderer.entries()) { // "线"创建 for (const [lineId, lineDiffItem] of this.lineDiffs.create.entries()) { const start = this.entities.get(lineDiffItem.startId) const end = this.entities.get(lineDiffItem.endId) // 添加存在性检查 if (!start || !end) { system.showErrorDialog(`无法创建线 ${lineId}, 起点或终点不存在`) continue } if (start.t !== itemTypeName) { // 只通知起点对应的渲染器 continue } renderer.createLine(start, end, lineDiffItem.type) } // "线"更新 for (const [lineId, lineDiffItem] of this.lineDiffs.update.entries()) { const start = this.entities.get(lineDiffItem.startId) const end = this.entities.get(lineDiffItem.endId) // 添加存在性检查 if (!start || !end) { system.showErrorDialog(`无法更新线 ${lineId}, 起点或终点不存在`) continue } if (start.t !== itemTypeName) { // 只通知起点对应的渲染器 console.error(`Line ${lineId} start point type mismatch: expected ${itemTypeName}, got ${start.t}`) continue } renderer.updateLine(start, end, lineDiffItem.type) } // "线"删除 for (const [lineId, lineDiffItem] of this.lineDiffs.delete.entries()) { const start = this.entities.get(lineDiffItem.startId) const end = this.entities.get(lineDiffItem.endId) if (!start || !end) { // 即使实体不存在也要处理删除 renderer.deleteLine( start || { id: lineDiffItem.startId, t: 'unknown' } as ItemJson, end || { id: lineDiffItem.endId, t: 'unknown' } as ItemJson, lineDiffItem.type) continue } if (start.t !== itemTypeName) { // 只通知起点对应的渲染器 continue } // 使用安全删除方法, 即使实体不存在也要处理删除 renderer.deleteLine(start, end, lineDiffItem.type) } renderer.endRendererUpdate() } // 将 lineDiffs 中新的关系网数据, 更新到实体点里 const needUpdateIds = new Set() for (const [lineId, lineDiffItem] of this.lineDiffs.create.entries()) { needUpdateIds.add(lineDiffItem.startId) needUpdateIds.add(lineDiffItem.endId) } for (const [lineId, lineDiffItem] of this.lineDiffs.update.entries()) { needUpdateIds.add(lineDiffItem.startId) needUpdateIds.add(lineDiffItem.endId) } for (const [lineId, lineDiffItem] of this.lineDiffs.delete.entries()) { needUpdateIds.add(lineDiffItem.startId) needUpdateIds.add(lineDiffItem.endId) } for (const id of needUpdateIds) { const entity = this.entities.get(id) if (entity) { entity.dt.center = Array.from(this.relationIndex.get(id)?.center || []) entity.dt.in = Array.from(this.relationIndex.get(id)?.input || []) entity.dt.out = Array.from(this.relationIndex.get(id)?.output || []) } } this.viewport.endViewUpdate() this.isUpdating = false } /** * 更新关系关系网, 计算出差值, 可以临时放在 diffRenderer 中, 等待 commitUpdate 时统一处理 */ private updateRelations(entity: ItemJson, originEntity?: ItemJson): void { const { id, dt } = entity if (!id || !dt) return const oldCenter = new Set(originEntity?.dt?.center || []) const oldIn = new Set(originEntity?.dt?.in || []) const oldOut = new Set(originEntity?.dt?.out || []) const newCenter = new Set(dt.center || []) const newIn = new Set(dt.in || []) const newOut = new Set(dt.out || []) // 更新正向关系 const relations = this.relationIndex.get(id) || new Relation() relations.center = newCenter relations.input = newIn relations.output = newOut this.relationIndex.set(id, relations) // 更新反向关系 this.updateReverseRelations(id, oldCenter, newCenter, 'center') this.updateReverseRelations(id, oldIn, newIn, 'out') // 入边的反向是出边 this.updateReverseRelations(id, oldOut, newOut, 'in') // 出边的反向是入边 // 更新线差量 this.calculateLineDiffs(id, oldCenter, newCenter, 'center') this.calculateLineDiffs(id, oldIn, newIn, 'in') this.calculateLineDiffs(id, oldOut, newOut, 'out') } private updateReverseRelations(id: string, oldIds: Set, newIds: Set, relationType: LinkType) { // 确保关系索引存在 if (!this.relationIndex.has(id)) { this.relationIndex.set(id, new Relation()) } // 移除旧关系 for (const relatedId of oldIds) { if (!newIds.has(relatedId)) { const rev = this.relationIndex.get(relatedId) rev.delete(relationType, id) this.writeBackEntities.add(relatedId) } } // 添加新关系 for (const relatedId of newIds) { if (!oldIds.has(relatedId)) { let rev = this.relationIndex.get(relatedId) if (!rev) { rev = new Relation() this.relationIndex.set(relatedId, rev) } rev.add(relationType, id) this.writeBackEntities.add(relatedId) } } } private calculateLineDiffs(id: string, oldIds: Set, newIds: Set, lineType: LinkType) { // 确保关系索引存在 if (!this.relationIndex.has(id)) return // 删除被移除的线 for (const relatedId of oldIds) { if (!newIds.has(relatedId)) { const lineId = getLineId(id, relatedId, lineType) // 如果这条线已经在 update 列表中,则跳过 delete if (this.lineDiffs.update.has(lineId)) continue console.log(`[delete] ${id} -> ${relatedId} [${lineType}] => ${lineId}`) this.lineDiffs.delete.set(lineId, { startId: id, endId: relatedId, type: lineType }) } } // 新增新增的线 for (const relatedId of newIds) { if (!oldIds.has(relatedId)) { const lineId = getLineId(id, relatedId, lineType) // 如果这条线已经在 update 列表中,则跳过 create if (this.lineDiffs.update.has(lineId)) continue this.lineDiffs.create.set(lineId, { startId: id, endId: relatedId, type: lineType }) } } } /** * 删除关系关系网, 计算出差值, 可以临时放在 diffRenderer 中, 等待 commitUpdate 时统一处理 */ private removeRelations(id: string): void { const relations = this.relationIndex.get(id) if (!relations) return relations.center.forEach(relatedId => { const rev = this.relationIndex.get(relatedId) if (rev) rev.center.delete(id) this.writeBackEntities.add(relatedId) }) relations.input.forEach(relatedId => { const rev = this.relationIndex.get(relatedId) if (rev) rev.output.delete(id) this.writeBackEntities.add(relatedId) }) relations.output.forEach(relatedId => { const rev = this.relationIndex.get(relatedId) if (rev) rev.input.delete(id) this.writeBackEntities.add(relatedId) }) this.relationIndex.delete(id) } private getDiffRenderer(type: string) { let renderer = this.diffRenderer.get(type) if (typeof renderer === 'undefined') { renderer = getRenderer(type) renderer.beginRendererUpdate(this.viewport) this.diffRenderer.set(type, renderer) } return renderer } // /** // * 重命名一个点 // * 注意, 不能在更新时刻改名. 所有的关系节点都应该改名 // */ // renamePoint(newId: string, originId: string) { // if (this.isUpdating) { // throw new Error('Cannot rename point during update') // } // const entity = this.entities.get(originId) // if (!entity) { // throw new Error(`Entity with id ${originId} does not exist`) // } // if (this.entities.has(newId)) { // throw new Error(`Entity with id ${newId} already exists`) // } // entity.id = newId // this.entities.set(newId, entity) // this.entities.delete(originId) // this.objects.set(newId, this.objects.get(originId) || []) // this.objects.delete(originId) // // // 更新关系索引 // const relations = this.relationIndex.get(originId) // if (relations) { // this.relationIndex.delete(originId) // // // 更新所有关系中的 id // relations.center.forEach((relatedId) => { // const rev = this.relationIndex.get(relatedId) // if (rev && rev.delete('center', originId)) { // rev.add('center', newId) // } // }) // relations.input.forEach((relatedId) => { // const rev = this.relationIndex.get(relatedId) // if (rev && rev.delete('out', originId)) { // rev.add('out', newId) // } // }) // relations.output.forEach((relatedId) => { // const rev = this.relationIndex.get(relatedId) // if (rev && rev.delete('in', originId)) { // rev.add('in', newId) // } // }) // // this.relationIndex.set(newId, relations) // } // // // 更新所有线段数据 // for (const [lineId, lineObjects] of this.lines.entries()) { // const [type, startId, endId] = parseLineId(lineId) // if (startId === originId) { // const newLineId = getLineId(newId, endId, type) // this.lines.set(newLineId, lineObjects) // this.lines.delete(lineId) // // } else if (endId === originId) { // const newLineId = getLineId(startId, newId, type) // this.lines.set(newLineId, lineObjects) // this.lines.delete(lineId) // } // } // } deleteEntityOnly(id: string) { return this.entities.delete(id) } findObjectsById(id: string) { return this.objects.get(id) || [] } deleteObjectsOnly(id: string) { // 删除对象时,也需要从 _selectableObjects 中移除 const rel = this.objects.get(id) if (rel) { _.remove(this._selectableObjects, obj => !rel.includes(obj)) } return this.objects.delete(id) } appendObject(id: string, points: THREE.Object3D[]) { this.objects.set(id, points) // 如果是可选中对象,添加到 _selectableObjects 中 if (points.some(obj => obj.userData.selectable !== false)) { this._selectableObjects.push(...points) } } appendLineObject(id: string, lines: THREE.Object3D[]) { this.lines.set(id, lines) // 如果是可选中对象,添加到 _selectableObjects 中 if (lines.some(obj => obj.userData.selectable !== false)) { this._selectableObjects.push(...lines) } } findLineObjectsById(lineId: string): THREE.Object3D[] { return this.lines.get(lineId) || [] } deleteLineObjectOnly(id: string) { // 删除线对象时,也需要从 _selectableObjects 中移除 const rel = this.lines.get(id) if (rel) { _.remove(this._selectableObjects, obj => !rel.includes(obj)) } return this.lines.delete(id) } /** * 根据 linkStartPointId 查找对应的 ItemJson */ findItemById(linkStartPointId: string): ItemJson | undefined { if (!linkStartPointId) { return } return this.entities.get(linkStartPointId) } getObjectByCanvasMouse(event: MouseEvent): THREE.Object3D[] { const _domElement = this.viewport.renderer.domElement const rect = _domElement.getBoundingClientRect() const _pointer = new Vector2() _pointer.x = (event.clientX - rect.left) / rect.width * 2 - 1 _pointer.y = -(event.clientY - rect.top) / rect.height * 2 + 1 this.viewport.raycaster.setFromCamera(_pointer, this.viewport.camera) const _intersections = this.viewport.raycaster.intersectObjects(this._selectableObjects, true) if (!_intersections || _intersections.length === 0) { return [] } // 根据距离排序射线命中的对象集 return _.map( _intersections.sort((a, b) => a.distance - b.distance), 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 { startId: string endId: string type: LinkType } /** * 关系详情 */ export class Relation { center = new Set() input = new Set() output = new Set() add(type: LinkType, id: string) { if (type === 'in') return this.input.add(id) else if (type === 'out') return this.output.add(id) else if (type === 'center') return this.center.add(id) else throw new Error(`Unknown link type: ${type}`) } delete(type: LinkType, id: string) { if (type === 'in') return this.input.delete(id) else if (type === 'out') return this.output.delete(id) else if (type === 'center') return this.center.delete(id) else throw new Error(`Unknown link type: ${type}`) } }