From 40d2eb4f8b9a0378da146b40d45b0d9995750c38 Mon Sep 17 00:00:00 2001 From: yvan Date: Sun, 1 Jun 2025 20:45:38 +0800 Subject: [PATCH] =?UTF-8?q?stateManager=20=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B1=20=E9=80=9A=E8=BF=87;=20stateManager.beginStateUpdat?= =?UTF-8?q?e();=20stateManager.vdata.items[1].tf[0]=20=3D=20[-10,=200,=204?= =?UTF-8?q?];=20stateManager.endStateUpdate();?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/base/BaseRenderer.ts | 76 +++++++++++++++++++++------------- src/core/engine/Viewport.ts | 4 +- src/core/manager/EntityManager.ts | 52 ++++++++++++++++------- src/core/manager/StateManager.ts | 58 +++++++++----------------- src/editor/Model2DEditor.vue | 4 ++ src/modules/measure/MeasureRenderer.ts | 16 +++++++ src/types/model.d.ts | 12 +++--- 7 files changed, 135 insertions(+), 87 deletions(-) diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index edcbf28..bc33c7a 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -18,7 +18,7 @@ export default abstract class BaseRenderer { * 开始更新 * @param viewport 当前视口 */ - beginUpdate(viewport: Viewport): void { + beginRendererUpdate(viewport: Viewport): void { this.tempViewport = viewport } @@ -32,6 +32,46 @@ export default abstract class BaseRenderer { */ abstract createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] + fillObjectUserDataFromItem(item: ItemJson, ...objects: THREE.Object3D[]) { + _.forEach(objects, (object) => { + if (!object.name && item.name) { + object.name = item.name + } + object.userData = { + ...object.userData, + createType: 'point', + entityId: item.id, + t: item.t + } + }) + } + + fillObjectUserDataFromLine(start: ItemJson, end: ItemJson, type: LinkType, ...objects: THREE.Object3D[]) { + const id = getLineId(start.id, end.id, type) + _.forEach(objects, (object) => { + if (!object.name) { + object.name = id + } + object.userData = { + ...object.userData, + createType: 'line', + entityId: getLineId(start.id, end.id, type), + t: start.t + } + }) + } + + /** + * 将对象添加到当前视口的场景中 + */ + appendToScene(...objects: THREE.Object3D[]) { + if (!this.tempViewport || !this.tempViewport.scene) { + console.warn('No active viewport to append objects to.') + return + } + this.tempViewport.scene.add(...objects) + } + /** * 创建一个点 * @param item 点的定义 @@ -44,15 +84,6 @@ export default abstract class BaseRenderer { if (item.name) { point.name = item.name } - point.userData = _.cloneDeep(item.dt) || {} - _.extend(point.userData, { - t: item.t, - center: [], - in: [], - out: [], - selectable: true, - protected: false - }) point.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) @@ -64,7 +95,9 @@ export default abstract class BaseRenderer { point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2]) }) + this.fillObjectUserDataFromItem(item, ...points) this.tempViewport.entityManager.appendObject(item.id, points) + this.appendToScene(...points) } @@ -97,15 +130,8 @@ export default abstract class BaseRenderer { _.forEach(objects, (point) => { point.name = item.name || point.name - point.userData = _.cloneDeep(item.dt) || {} - _.extend(point.userData, { - t: item.t, - center: [], - in: [], - out: [], - selectable: true, - protected: false - }) + + point.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2]) point.rotation.set( THREE.MathUtils.degToRad(item.tf[1][0]), @@ -132,21 +158,15 @@ export default abstract class BaseRenderer { const endPoint = this.tempViewport.entityManager.findObjectsById(end.id)?.[0] _.forEach(lines, (line) => { - line.userData = { - t: 'line', - start: start.id, - end: end.id, - type: type - } - line.name = `${start.id}-${end.id}-${type}` - this.tempViewport.entityManager.findObjectsById(id) const geom = line.geometry geom.setFromPoints([startPoint.position, endPoint.position]) }) + this.fillObjectUserDataFromLine(start, end, type, ...lines) this.tempViewport.entityManager.appendLineObject(id, lines) + this.appendToScene(...lines) } /** @@ -190,7 +210,7 @@ export default abstract class BaseRenderer { /** * 结束更新 */ - endUpdate(): void { + endRendererUpdate(): void { this.tempViewport = undefined } } diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 1750f4d..95a219c 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -330,11 +330,11 @@ export default class Viewport { } } - beginUpdate() { + beginViewUpdate() { this.state.isUpdating = true } - endUpdate() { + endViewUpdate() { this.state.isUpdating = false } diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index 38ff585..cf8f008 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -16,21 +16,21 @@ export default class EntityManager { viewport: Viewport // 所有数据点的实体 - readonly entities = new Map() + private readonly entities = new Map() // 关系索引 - readonly relationIndex = new Map() + private readonly relationIndex = new Map() // 所有 THREEJS "点"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组 - readonly objects = new Map() + private readonly objects = new Map() // 所有 THREEJS "线"对象, 检索值是"线实体"的 id, 取值方式是 {type}${startId}${endId}, 值是 THREE.Object3D 数组 - readonly lines = new Map() + private readonly lines = new Map() // 差量渲染器 - readonly diffRenderer = new Map() + private readonly diffRenderer = new Map() // 线差量记录 - readonly lineDiffs = { + private readonly lineDiffs = { create: new Map(), update: new Map(), delete: new Map() @@ -44,9 +44,9 @@ export default class EntityManager { /** * 批量更新开始 */ - beginUpdate(): void { + beginEntityUpdate(): void { this.isUpdating = true - this.viewport.beginUpdate() + this.viewport.beginViewUpdate() this.diffRenderer.clear() this.lineDiffs.create.clear() this.lineDiffs.update.clear() @@ -56,10 +56,11 @@ export default class EntityManager { /** * 创建或更新一个实体, 这个点的 center[] / in[] / out[] 关联的点, 可能都要对应进行关联 */ - createOrUpdateEntity(entity: ItemJson, option: EntityCudOption = {}): void { - if (!entity?.id) { + createOrUpdateEntity(entityRaw: ItemJson, option: EntityCudOption = {}): void { + if (!entityRaw?.id) { throw new Error('Entity must have an id') } + const entity = _.cloneDeep(entityRaw) as ItemJson // 找到这个数据的渲染器 const renderer = this.getDiffRenderer(entity.t) @@ -70,6 +71,29 @@ export default class EntityManager { // 更新关系网 this.updateRelations(entity, originEntity) + // 判断坐标是否变化 + + if (originEntity?.tf && entity?.tf && !_.isEqual(originEntity.tf[0], entity.tf[0])) { + // 点的坐标发生变化, 要通知所有关联线更新 + const relations = this.relationIndex.get(entity.id) + if (!relations) return + + for (const type of (['center', 'in', 'out'] as LinkType[])) { + const relatedIds = relations[type] + if (!relatedIds) continue + + for (const relatedId of relatedIds) { + const lineId = getLineId(entity.id, relatedId, type) + this.lineDiffs.update.set(lineId, { startId: entity.id, endId: relatedId, type }) + + // 如果是双向线(比如 center),也要反向加一次 + if (type === 'center') { + this.lineDiffs.update.set(lineId, { startId: relatedId, endId: entity.id, type }) + } + } + } + } + if (typeof originEntity === 'undefined') { renderer.createPoint(entity, option) @@ -101,7 +125,7 @@ export default class EntityManager { * - 如果进行了更新, 如果改了颜色/位置, 则需要在UI上进行对应修改,如果改了关系,需要与关联的节点批量调整 * 将影响到的所有数据, 都变成一个修改集合, 统一调用对应单元类型渲染器(BaseRenderer)的 createPoint / deletePoint / updatePoint / createLine / updateLine / deleteLine 方法 */ - commitUpdate(): void { + 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) @@ -133,9 +157,9 @@ export default class EntityManager { renderer.deleteLine(start, end, lineDiffItem.type) } - renderer.endUpdate() + renderer.endRendererUpdate() } - this.viewport.endUpdate() + this.viewport.endViewUpdate() this.isUpdating = false } @@ -311,7 +335,7 @@ export default class EntityManager { let renderer = this.diffRenderer.get(type) if (typeof renderer === 'undefined') { renderer = getRenderer(type) - renderer.beginUpdate(this.viewport) + renderer.beginRendererUpdate(this.viewport) this.diffRenderer.set(type, renderer) } return renderer diff --git a/src/core/manager/StateManager.ts b/src/core/manager/StateManager.ts index b247bff..6023cc1 100644 --- a/src/core/manager/StateManager.ts +++ b/src/core/manager/StateManager.ts @@ -9,9 +9,9 @@ import type Viewport from '@/core/engine/Viewport.ts' * 1. 管理场景数据的读取、保存, 以及临时保存、临时读取等功能 * 2. 管理撤销、重做功能 * 3. 各种 Interaction 交互控制组件通过如下步骤修改数据 - * - 1. 调用 beginUserWrite 开始修改数据 + * - 1. 调用 beginStateUpdate 开始修改数据 * - 2. 直接修改 vdata 数据 - * - 3. 调用 endUserWrite 完成数据修改 + * - 3. 调用 endStateUpdate 完成数据修改 * 4. 内部如果进行了撤销、还原等操作,会通过 syncDataState() 方法将 vdata 数据与 viewport 进行同步 * 5. syncDataState 方法会对比 vdata 与 viewport * - 分别进行添加、删除、更新等操作 @@ -19,28 +19,12 @@ import type Viewport from '@/core/engine/Viewport.ts' * 状态管理器,管理内部数据结构的读取、保存、临时保存、临时读取、撤销、重做等功能 * 当数据结构发生变化时,只对发生变化的对象进行更新 * - * // 初始化 - * const stateManager = new StateManager('scene-1', viewport) - * - * // 加载大数据 - * await stateManager.load(largeDataSet) // 5万+ items - * - * // 修改数据 - * stateManager.beginUserWrite() - * - * // 直接修改状态(实际项目应通过封装方法) - * stateManager.vdata.items.push(newItem) - * stateManager.vdata.items[0].name = 'updated' - * stateManager.vdata.items = stateManager.vdata.items.filter(i => i.id !== 'remove-id') - * - * stateManager.endUserWrite() // 自动计算差异并保存 + * // 修改坐标点 + * stateManager.beginStateUpdate();stateManager.vdata.items[1].tf[0] = [-10, 0, 4];stateManager.endStateUpdate(); * * // 撤销操作 * stateManager.undo() * - * // 保存到云端 - * const data = await stateManager.save() - * */ export default class StateManager { /** @@ -117,7 +101,7 @@ export default class StateManager { /** * 开始用户操作(创建数据快照) */ - beginUserWrite() { + beginStateUpdate() { // 创建当前状态快照(非深拷贝) this.lastSnapshot = new Map( this.vdata.items.map(item => [item.id, _.cloneDeep(item)]) @@ -128,7 +112,8 @@ export default class StateManager { /** * 结束用户操作(计算差异并保存) */ - endUserWrite() { + endStateUpdate() { + debugger this.calculateDiff() this.saveStep() this.syncDataState() @@ -197,11 +182,11 @@ export default class StateManager { /** * 将当前数据 与 viewport 进行同步, 对比出不同的部分,分别进行更新 - * - 调用 viewport.entityManager.beginUpdate() 开始更新 - * - 调用 viewport.entityManager.createEntity(vdataItem) 添加场景中新的实体 - * - 调用 viewport.entityManager.updateEntity(vdataItem) 新场景中已存在的实体 - * - 调用 viewport.entityManager.deleteEntity(id) 删除场景中的实体 - * - 调用 viewport.entityManager.commitUpdate() 结束更新场景 + * - 调用 entityManager.beginEntityUpdate() 开始更新 + * - 调用 entityManager.createEntity(vdataItem) 添加场景中新的实体 + * - 调用 entityManager.updateEntity(vdataItem) 新场景中已存在的实体 + * - 调用 entityManager.deleteEntity(id) 删除场景中的实体 + * - 调用 entityManager.endEntityUpdate() 结束更新场景 */ syncDataState() { // 没有变化时跳过同步 @@ -213,7 +198,7 @@ export default class StateManager { return } - this.entityManager.beginUpdate() + this.entityManager.beginEntityUpdate() // 处理删除 if (this.changeTracker.removed) { @@ -236,7 +221,7 @@ export default class StateManager { } } - this.entityManager.commitUpdate() + this.entityManager.endEntityUpdate() } @@ -262,8 +247,8 @@ export default class StateManager { this.fullSync() // 同步到视口 // 初始状态作为第一步 - this.beginUserWrite() - this.endUserWrite() + this.beginStateUpdate() + this.endStateUpdate() this.isChanged.value = false this.pendingChanges = false @@ -272,7 +257,7 @@ export default class StateManager { await this.saveToLocalstore() this.pendingChanges = false - console.log('[StateManager] 加载完成,共 ', data.items.length, '个对象') + console.log('[StateManager] 加载完成,共', data.items.length, '个对象') } finally { this.isLoading.value = false @@ -306,6 +291,7 @@ export default class StateManager { * 撤销 */ undo() { + debugger if (!this.undoEnabled()) return const step = this.historySteps[this.historyIndex] @@ -356,8 +342,6 @@ export default class StateManager { updateMap.has(item.id) ? updateMap.get(item.id)! : item ) } - - this.lastSnapshot = new Map(this.vdata.items.map(item => [item.id, _.cloneDeep(item)])) } /** @@ -392,8 +376,6 @@ export default class StateManager { restoreMap.has(item.id) ? restoreMap.get(item.id)! : item ) } - - this.lastSnapshot = new Map(this.vdata.items.map(item => [item.id, _.cloneDeep(item)])) } // /** @@ -462,11 +444,11 @@ export default class StateManager { } private fullSync() { - this.entityManager.beginUpdate() + this.entityManager.beginEntityUpdate() this.vdata.items.forEach(item => { this.entityManager.createOrUpdateEntity(item) }) - this.entityManager.commitUpdate() + this.entityManager.endEntityUpdate() } undoEnabled() { diff --git a/src/editor/Model2DEditor.vue b/src/editor/Model2DEditor.vue index 5ffa920..e7734cb 100644 --- a/src/editor/Model2DEditor.vue +++ b/src/editor/Model2DEditor.vue @@ -125,6 +125,8 @@ export default defineComponent({ delete window['editor'] delete window['viewport'] delete window['scene'] + delete window['stateManager'] + delete window['entityManager'] delete window['renderer'] delete window['camera'] delete window['renderer'] @@ -157,6 +159,8 @@ export default defineComponent({ window['viewport'] = viewport window['THREE'] = THREE window['scene'] = sceneHelp.scene + window['stateManager'] = viewport.stateManager + window['entityManager'] = viewport.entityManager window['renderer'] = viewport.renderer window['camera'] = viewport.camera window['renderer'] = viewport.renderer diff --git a/src/modules/measure/MeasureRenderer.ts b/src/modules/measure/MeasureRenderer.ts index f780c37..f6b461e 100644 --- a/src/modules/measure/MeasureRenderer.ts +++ b/src/modules/measure/MeasureRenderer.ts @@ -3,11 +3,17 @@ import * as THREE from 'three' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry' import { Line2 } from 'three/examples/jsm/lines/Line2' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' +import { getLineId } from '@/core/ModelUtils.ts' /** * 辅助测量工具渲染器 */ export default class MeasureRenderer extends BaseRenderer { + /** + * 当前测绘内容组, 所有测量点、线、标签都在这个组中. 但不包括临时点、线 + */ + group: THREE.Group + static GROUP_NAME = 'measure-group' static LABEL_NAME = 'measure_label' static POINT_NAME = 'measure_point' @@ -28,6 +34,7 @@ export default class MeasureRenderer extends BaseRenderer { const obj = new Line2(geom, this.lineMaterial) obj.frustumCulled = false obj.name = MeasureRenderer.LINE_NAME + obj.uuid = getLineId(start.id, end.id, type) return [obj] } @@ -36,8 +43,17 @@ export default class MeasureRenderer extends BaseRenderer { const tt = new THREE.BoxGeometry(1, 1, 1) const obj = new THREE.Mesh(tt, this.pointMaterial) obj.name = MeasureRenderer.POINT_NAME + obj.uuid = item.id return [obj] } + appendToScene(...objects: THREE.Object3D[]) { + if (!this.group) { + this.group = new THREE.Group() + this.group.name = MeasureRenderer.GROUP_NAME + this.tempViewport?.scene.add(this.group) + } + this.group.add(...objects) + } } \ No newline at end of file diff --git a/src/types/model.d.ts b/src/types/model.d.ts index 44a8b7c..a70d4a7 100644 --- a/src/types/model.d.ts +++ b/src/types/model.d.ts @@ -83,16 +83,16 @@ interface IGridHelper { * 物体单元(点) * 举例: * { - * id: 'p1', // 物体唯一ID, 也用于 three.js 中的 uuid + * id: 'p1', // 物体唯一ID * t: 'measure', // 物体类型, measure表示测量, 需交给 itemType.name == 'measure' 的组件处理 * tf: [ // 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前的右手坐标系 * [-9.0, 0, -1.0], // 平移向量 position * [0, 0, 0], // 旋转向量 rotation, 表示绕Y轴旋转的角度, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算 * [0.25, 0.1, 0.25] // 缩放向量 scale * ], - * dt: { // 用户数据, 可自定义, 一般用在 three.js 的 userData 中 + * dt: { // 实体的自定义数据 * label: '测量1', // 标签名称, 显示用 - * color: '#ff0000', // 颜色, 显示用. 十六进制颜色值, three.js 中的材质颜色 + * color: '#ff0000', // 颜色, 显示用. 十六进制颜色值 * center: ['p2'], // S连线(又称逻辑连线), 与其他点之间的无方向性关联, 关系的起点需要在他的 dt.center[] 数组中添加目标点的id, 关系的终点需要在他的 dt.center[] 数组中添加起点的 id * in: [], // A连线(又称物体流动线)的输入, 关系的终点需要在 dt.in[] 数组中添加起点的 id * out: [] // A连线(又称物体流动线)的输出, 关系的起点需要在 dt.out[] 数组中添加目标点的 id @@ -144,12 +144,12 @@ interface ItemJson { */ dt: { /** - * 标签名称, 显示用, 最后初始化到 three.js 的 userData.label 中, 最终应该如何渲染, 每个单元类型有自己的逻辑, 取决于物流单元类型t的 renderer 逻辑 + * 标签名称, 显示用, 每个单元类型的 renderer 有自己的渲染逻辑 */ label?: string /** - * 颜色, 最后初始化到 three.js 的 userData.color 中, 最终颜色应该如何渲染, 每个单元类型有自己的逻辑, 取决于物流单元类型t的 renderer 逻辑 + * 颜色, 每个单元类型的 renderer 有自己的渲染逻辑 */ color?: string @@ -157,10 +157,12 @@ interface ItemJson { * S连线(又称逻辑连线), 与其他点之间的无方向性关联, 关系的起点需要在他的 dt.center[] 数组中添加目标点的id, 关系的终点需要在他的 dt.center[] 数组中添加起点的 id */ center?: string[] + /** * A连线(又称物体流动线)的输入, 关系的终点需要在 dt.in[] 数组中添加起点的 id */ in?: string[] + /** * A连线(又称物体流动线)的输出, 关系的起点需要在 dt.out[] 数组中添加目标点的 id */