Browse Source

stateManager 测试用例1 通过;

stateManager.beginStateUpdate();
stateManager.vdata.items[1].tf[0] = [-10, 0, 4];
stateManager.endStateUpdate();
master
修宁 7 months ago
parent
commit
40d2eb4f8b
  1. 76
      src/core/base/BaseRenderer.ts
  2. 4
      src/core/engine/Viewport.ts
  3. 52
      src/core/manager/EntityManager.ts
  4. 58
      src/core/manager/StateManager.ts
  5. 4
      src/editor/Model2DEditor.vue
  6. 16
      src/modules/measure/MeasureRenderer.ts
  7. 12
      src/types/model.d.ts

76
src/core/base/BaseRenderer.ts

@ -18,7 +18,7 @@ export default abstract class BaseRenderer {
* *
* @param viewport * @param viewport
*/ */
beginUpdate(viewport: Viewport): void { beginRendererUpdate(viewport: Viewport): void {
this.tempViewport = viewport this.tempViewport = viewport
} }
@ -32,6 +32,46 @@ export default abstract class BaseRenderer {
*/ */
abstract createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] 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 * @param item
@ -44,15 +84,6 @@ export default abstract class BaseRenderer {
if (item.name) { if (item.name) {
point.name = 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]) 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]) 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.tempViewport.entityManager.appendObject(item.id, points)
this.appendToScene(...points)
} }
@ -97,15 +130,8 @@ export default abstract class BaseRenderer {
_.forEach(objects, (point) => { _.forEach(objects, (point) => {
point.name = item.name || point.name point.name = item.name || point.name
point.userData = _.cloneDeep(item.dt) || {}
_.extend(point.userData, { point.position.set(item.tf[0][0], item.tf[0][1], item.tf[0][2])
t: item.t,
center: [],
in: [],
out: [],
selectable: true,
protected: false
})
point.rotation.set( point.rotation.set(
THREE.MathUtils.degToRad(item.tf[1][0]), 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] const endPoint = this.tempViewport.entityManager.findObjectsById(end.id)?.[0]
_.forEach(lines, (line) => { _.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) this.tempViewport.entityManager.findObjectsById(id)
const geom = line.geometry const geom = line.geometry
geom.setFromPoints([startPoint.position, endPoint.position]) geom.setFromPoints([startPoint.position, endPoint.position])
}) })
this.fillObjectUserDataFromLine(start, end, type, ...lines)
this.tempViewport.entityManager.appendLineObject(id, 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 this.tempViewport = undefined
} }
} }

4
src/core/engine/Viewport.ts

@ -330,11 +330,11 @@ export default class Viewport {
} }
} }
beginUpdate() { beginViewUpdate() {
this.state.isUpdating = true this.state.isUpdating = true
} }
endUpdate() { endViewUpdate() {
this.state.isUpdating = false this.state.isUpdating = false
} }

52
src/core/manager/EntityManager.ts

@ -16,21 +16,21 @@ export default class EntityManager {
viewport: Viewport viewport: Viewport
// 所有数据点的实体 // 所有数据点的实体
readonly entities = new Map<string, ItemJson>() private readonly entities = new Map<string, ItemJson>()
// 关系索引 // 关系索引
readonly relationIndex = new Map<string, Relation>() private readonly relationIndex = new Map<string, Relation>()
// 所有 THREEJS "点"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组 // 所有 THREEJS "点"对象, 检索值是"点实体"的 id, 值是 THREE.Object3D 数组
readonly objects = new Map<string, THREE.Object3D[]>() private readonly objects = new Map<string, THREE.Object3D[]>()
// 所有 THREEJS "线"对象, 检索值是"线实体"的 id, 取值方式是 {type}${startId}${endId}, 值是 THREE.Object3D 数组 // 所有 THREEJS "线"对象, 检索值是"线实体"的 id, 取值方式是 {type}${startId}${endId}, 值是 THREE.Object3D 数组
readonly lines = new Map<string, THREE.Object3D[]>() private readonly lines = new Map<string, THREE.Object3D[]>()
// 差量渲染器 // 差量渲染器
readonly diffRenderer = new Map<string, BaseRenderer>() private readonly diffRenderer = new Map<string, BaseRenderer>()
// 线差量记录 // 线差量记录
readonly lineDiffs = { private readonly lineDiffs = {
create: new Map<string, LineDiffItem>(), create: new Map<string, LineDiffItem>(),
update: new Map<string, LineDiffItem>(), update: new Map<string, LineDiffItem>(),
delete: new Map<string, LineDiffItem>() delete: new Map<string, LineDiffItem>()
@ -44,9 +44,9 @@ export default class EntityManager {
/** /**
* *
*/ */
beginUpdate(): void { beginEntityUpdate(): void {
this.isUpdating = true this.isUpdating = true
this.viewport.beginUpdate() this.viewport.beginViewUpdate()
this.diffRenderer.clear() this.diffRenderer.clear()
this.lineDiffs.create.clear() this.lineDiffs.create.clear()
this.lineDiffs.update.clear() this.lineDiffs.update.clear()
@ -56,10 +56,11 @@ export default class EntityManager {
/** /**
* , center[] / in[] / out[] , * , center[] / in[] / out[] ,
*/ */
createOrUpdateEntity(entity: ItemJson, option: EntityCudOption = {}): void { createOrUpdateEntity(entityRaw: ItemJson, option: EntityCudOption = {}): void {
if (!entity?.id) { if (!entityRaw?.id) {
throw new Error('Entity must have an id') throw new Error('Entity must have an id')
} }
const entity = _.cloneDeep(entityRaw) as ItemJson
// 找到这个数据的渲染器 // 找到这个数据的渲染器
const renderer = this.getDiffRenderer(entity.t) const renderer = this.getDiffRenderer(entity.t)
@ -70,6 +71,29 @@ export default class EntityManager {
// 更新关系网 // 更新关系网
this.updateRelations(entity, originEntity) 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') { if (typeof originEntity === 'undefined') {
renderer.createPoint(entity, option) renderer.createPoint(entity, option)
@ -101,7 +125,7 @@ export default class EntityManager {
* - , /, UI上进行对应修改 * - , /, UI上进行对应修改
* , , (BaseRenderer) createPoint / deletePoint / updatePoint / createLine / updateLine / deleteLine * , , (BaseRenderer) createPoint / deletePoint / updatePoint / createLine / updateLine / deleteLine
*/ */
commitUpdate(): void { endEntityUpdate(): void {
for (const [itemTypeName, renderer] of this.diffRenderer.entries()) { for (const [itemTypeName, renderer] of this.diffRenderer.entries()) {
for (const [lineId, lineDiffItem] of this.lineDiffs.create.entries()) { for (const [lineId, lineDiffItem] of this.lineDiffs.create.entries()) {
const start = this.entities.get(lineDiffItem.startId) const start = this.entities.get(lineDiffItem.startId)
@ -133,9 +157,9 @@ export default class EntityManager {
renderer.deleteLine(start, end, lineDiffItem.type) renderer.deleteLine(start, end, lineDiffItem.type)
} }
renderer.endUpdate() renderer.endRendererUpdate()
} }
this.viewport.endUpdate() this.viewport.endViewUpdate()
this.isUpdating = false this.isUpdating = false
} }
@ -311,7 +335,7 @@ export default class EntityManager {
let renderer = this.diffRenderer.get(type) let renderer = this.diffRenderer.get(type)
if (typeof renderer === 'undefined') { if (typeof renderer === 'undefined') {
renderer = getRenderer(type) renderer = getRenderer(type)
renderer.beginUpdate(this.viewport) renderer.beginRendererUpdate(this.viewport)
this.diffRenderer.set(type, renderer) this.diffRenderer.set(type, renderer)
} }
return renderer return renderer

58
src/core/manager/StateManager.ts

@ -9,9 +9,9 @@ import type Viewport from '@/core/engine/Viewport.ts'
* 1. , * 1. ,
* 2. * 2.
* 3. Interaction * 3. Interaction
* - 1. beginUserWrite * - 1. beginStateUpdate
* - 2. vdata * - 2. vdata
* - 3. endUserWrite * - 3. endStateUpdate
* 4. syncDataState() vdata viewport * 4. syncDataState() vdata viewport
* 5. 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) * stateManager.beginStateUpdate();stateManager.vdata.items[1].tf[0] = [-10, 0, 4];stateManager.endStateUpdate();
*
* // 加载大数据
* 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.undo() * stateManager.undo()
* *
* // 保存到云端
* const data = await stateManager.save()
*
*/ */
export default class StateManager { export default class StateManager {
/** /**
@ -117,7 +101,7 @@ export default class StateManager {
/** /**
* *
*/ */
beginUserWrite() { beginStateUpdate() {
// 创建当前状态快照(非深拷贝) // 创建当前状态快照(非深拷贝)
this.lastSnapshot = new Map( this.lastSnapshot = new Map(
this.vdata.items.map(item => [item.id, _.cloneDeep(item)]) this.vdata.items.map(item => [item.id, _.cloneDeep(item)])
@ -128,7 +112,8 @@ export default class StateManager {
/** /**
* *
*/ */
endUserWrite() { endStateUpdate() {
debugger
this.calculateDiff() this.calculateDiff()
this.saveStep() this.saveStep()
this.syncDataState() this.syncDataState()
@ -197,11 +182,11 @@ export default class StateManager {
/** /**
* viewport * viewport
* - viewport.entityManager.beginUpdate() * - entityManager.beginEntityUpdate()
* - viewport.entityManager.createEntity(vdataItem) * - entityManager.createEntity(vdataItem)
* - viewport.entityManager.updateEntity(vdataItem) * - entityManager.updateEntity(vdataItem)
* - viewport.entityManager.deleteEntity(id) * - entityManager.deleteEntity(id)
* - viewport.entityManager.commitUpdate() * - entityManager.endEntityUpdate()
*/ */
syncDataState() { syncDataState() {
// 没有变化时跳过同步 // 没有变化时跳过同步
@ -213,7 +198,7 @@ export default class StateManager {
return return
} }
this.entityManager.beginUpdate() this.entityManager.beginEntityUpdate()
// 处理删除 // 处理删除
if (this.changeTracker.removed) { 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.fullSync() // 同步到视口
// 初始状态作为第一步 // 初始状态作为第一步
this.beginUserWrite() this.beginStateUpdate()
this.endUserWrite() this.endStateUpdate()
this.isChanged.value = false this.isChanged.value = false
this.pendingChanges = false this.pendingChanges = false
@ -272,7 +257,7 @@ export default class StateManager {
await this.saveToLocalstore() await this.saveToLocalstore()
this.pendingChanges = false this.pendingChanges = false
console.log('[StateManager] 加载完成,共 ', data.items.length, '个对象') console.log('[StateManager] 加载完成,共', data.items.length, '个对象')
} finally { } finally {
this.isLoading.value = false this.isLoading.value = false
@ -306,6 +291,7 @@ export default class StateManager {
* *
*/ */
undo() { undo() {
debugger
if (!this.undoEnabled()) return if (!this.undoEnabled()) return
const step = this.historySteps[this.historyIndex] const step = this.historySteps[this.historyIndex]
@ -356,8 +342,6 @@ export default class StateManager {
updateMap.has(item.id) ? updateMap.get(item.id)! : item 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 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() { private fullSync() {
this.entityManager.beginUpdate() this.entityManager.beginEntityUpdate()
this.vdata.items.forEach(item => { this.vdata.items.forEach(item => {
this.entityManager.createOrUpdateEntity(item) this.entityManager.createOrUpdateEntity(item)
}) })
this.entityManager.commitUpdate() this.entityManager.endEntityUpdate()
} }
undoEnabled() { undoEnabled() {

4
src/editor/Model2DEditor.vue

@ -125,6 +125,8 @@ export default defineComponent({
delete window['editor'] delete window['editor']
delete window['viewport'] delete window['viewport']
delete window['scene'] delete window['scene']
delete window['stateManager']
delete window['entityManager']
delete window['renderer'] delete window['renderer']
delete window['camera'] delete window['camera']
delete window['renderer'] delete window['renderer']
@ -157,6 +159,8 @@ export default defineComponent({
window['viewport'] = viewport window['viewport'] = viewport
window['THREE'] = THREE window['THREE'] = THREE
window['scene'] = sceneHelp.scene window['scene'] = sceneHelp.scene
window['stateManager'] = viewport.stateManager
window['entityManager'] = viewport.entityManager
window['renderer'] = viewport.renderer window['renderer'] = viewport.renderer
window['camera'] = viewport.camera window['camera'] = viewport.camera
window['renderer'] = viewport.renderer window['renderer'] = viewport.renderer

16
src/modules/measure/MeasureRenderer.ts

@ -3,11 +3,17 @@ import * as THREE from 'three'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { Line2 } from 'three/examples/jsm/lines/Line2' import { Line2 } from 'three/examples/jsm/lines/Line2'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import { getLineId } from '@/core/ModelUtils.ts'
/** /**
* *
*/ */
export default class MeasureRenderer extends BaseRenderer { export default class MeasureRenderer extends BaseRenderer {
/**
* , 线. 线
*/
group: THREE.Group
static GROUP_NAME = 'measure-group' static GROUP_NAME = 'measure-group'
static LABEL_NAME = 'measure_label' static LABEL_NAME = 'measure_label'
static POINT_NAME = 'measure_point' static POINT_NAME = 'measure_point'
@ -28,6 +34,7 @@ export default class MeasureRenderer extends BaseRenderer {
const obj = new Line2(geom, this.lineMaterial) const obj = new Line2(geom, this.lineMaterial)
obj.frustumCulled = false obj.frustumCulled = false
obj.name = MeasureRenderer.LINE_NAME obj.name = MeasureRenderer.LINE_NAME
obj.uuid = getLineId(start.id, end.id, type)
return [obj] return [obj]
} }
@ -36,8 +43,17 @@ export default class MeasureRenderer extends BaseRenderer {
const tt = new THREE.BoxGeometry(1, 1, 1) const tt = new THREE.BoxGeometry(1, 1, 1)
const obj = new THREE.Mesh(tt, this.pointMaterial) const obj = new THREE.Mesh(tt, this.pointMaterial)
obj.name = MeasureRenderer.POINT_NAME obj.name = MeasureRenderer.POINT_NAME
obj.uuid = item.id
return [obj] 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)
}
} }

12
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' 的组件处理 * t: 'measure', // 物体类型, measure表示测量, 需交给 itemType.name == 'measure' 的组件处理
* tf: [ // 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前的右手坐标系 * tf: [ // 变换矩阵, 3x3矩阵, 采用Y轴向上为正, X轴向右, Z轴向前的右手坐标系
* [-9.0, 0, -1.0], // 平移向量 position * [-9.0, 0, -1.0], // 平移向量 position
* [0, 0, 0], // 旋转向量 rotation, 表示绕Y轴旋转的角度, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算 * [0, 0, 0], // 旋转向量 rotation, 表示绕Y轴旋转的角度, 单位为度。对应 three.js 应进行"角度"转"弧度"的换算
* [0.25, 0.1, 0.25] // 缩放向量 scale * [0.25, 0.1, 0.25] // 缩放向量 scale
* ], * ],
* dt: { // 用户数据, 可自定义, 一般用在 three.js 的 userData 中 * dt: { // 实体的自定义数据
* label: '测量1', // 标签名称, 显示用 * label: '测量1', // 标签名称, 显示用
* color: '#ff0000', // 颜色, 显示用. 十六进制颜色值, three.js 中的材质颜色 * color: '#ff0000', // 颜色, 显示用. 十六进制颜色值
* center: ['p2'], // S连线(又称逻辑连线), 与其他点之间的无方向性关联, 关系的起点需要在他的 dt.center[] 数组中添加目标点的id, 关系的终点需要在他的 dt.center[] 数组中添加起点的 id * center: ['p2'], // S连线(又称逻辑连线), 与其他点之间的无方向性关联, 关系的起点需要在他的 dt.center[] 数组中添加目标点的id, 关系的终点需要在他的 dt.center[] 数组中添加起点的 id
* in: [], // A连线(又称物体流动线)的输入, 关系的终点需要在 dt.in[] 数组中添加起点的 id * in: [], // A连线(又称物体流动线)的输入, 关系的终点需要在 dt.in[] 数组中添加起点的 id
* out: [] // A连线(又称物体流动线)的输出, 关系的起点需要在 dt.out[] 数组中添加目标点的 id * out: [] // A连线(又称物体流动线)的输出, 关系的起点需要在 dt.out[] 数组中添加目标点的 id
@ -144,12 +144,12 @@ interface ItemJson {
*/ */
dt: { dt: {
/** /**
* , , three.js userData.label , , , t的 renderer * , , renderer
*/ */
label?: string label?: string
/** /**
* , three.js userData.color , , , t的 renderer * , renderer
*/ */
color?: string color?: string
@ -157,10 +157,12 @@ interface ItemJson {
* S连线(线), , dt.center[] id, dt.center[] id * S连线(线), , dt.center[] id, dt.center[] id
*/ */
center?: string[] center?: string[]
/** /**
* A连线(线), dt.in[] id * A连线(线), dt.in[] id
*/ */
in?: string[] in?: string[]
/** /**
* A连线(线), dt.out[] id * A连线(线), dt.out[] id
*/ */

Loading…
Cancel
Save