From 1b56e8d08436ef87c4f558d35eda4a47882a96ce Mon Sep 17 00:00:00 2001 From: luoyifan Date: Tue, 10 Jun 2025 00:00:20 +0800 Subject: [PATCH] =?UTF-8?q?InstancePointManager=20/=20LineSegmentManager?= =?UTF-8?q?=20=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/ModelUtils.ts | 6 +- src/core/base/BaseRenderer.ts | 8 ++- src/core/controls/DragControl.ts | 17 ++++-- src/core/controls/SelectInspect.ts | 20 ++++++- src/core/engine/Viewport.ts | 5 +- src/core/manager/EntityManager.ts | 6 +- src/core/manager/InstancePointManager.ts | 96 ++++++++++++++++++-------------- src/core/manager/LineSegmentManager.ts | 10 ++-- src/example/example1.js | 6 +- src/modules/measure/MeasureRenderer.ts | 8 +-- 10 files changed, 113 insertions(+), 69 deletions(-) diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index c175616..1327d62 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -133,15 +133,17 @@ export function getLineId(startId: string, endId: string, type: LinkType): strin /** * 获取某个 Object3D 先上查找最近的有效业务 Object3D - * @param object */ -export function getClosestObject(object: THREE.Object3D) { +export function getClosestObject(viewport: Viewport, object: THREE.Object3D, instanceId: number = -1): Object3DLike { if (!object) { return undefined } while (object) { if (object.userData && object.userData.t && object.userData.entityId) { // 找到第一个有效的业务 Object3D + if (object instanceof THREE.InstancedMesh && instanceId >= 0) { + return viewport.pointManagerMap.get(object.userData.t)?.findByMeshInstanceId(instanceId) + } return object } // 向上查找父级 diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index 731a02e..3cd9622 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -4,7 +4,7 @@ import { getLineId, setUserDataForItem, setUserDataForLine } from '@/core/ModelU import { Line2 } from 'three/examples/jsm/lines/Line2' import InstancePointManager, { PointManageWrap } from '@/core/manager/InstancePointManager.ts' import Constract from '@/core/Constract.ts' -import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts' +import LineSegmentManager, { LineManageWrap } from '@/core/manager/LineSegmentManager.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' /** @@ -234,6 +234,9 @@ export default abstract class BaseRenderer { ) point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2]) + if (point instanceof PointManageWrap) { + point.manager.syncMeshObject3D(point) + } return point } @@ -282,6 +285,9 @@ export default abstract class BaseRenderer { if (line instanceof Line2) { const geom = line.geometry geom.setFromPoints([startPoint.position, endPoint.position]) + + } else if (line instanceof LineManageWrap) { + line.manager.updateLine(lineId, startPoint.position, endPoint.position, line.color) } this.afterCreateOrUpdateLine(start, end, type, option, line) diff --git a/src/core/controls/DragControl.ts b/src/core/controls/DragControl.ts index 6835e03..902b0c6 100644 --- a/src/core/controls/DragControl.ts +++ b/src/core/controls/DragControl.ts @@ -3,6 +3,9 @@ import type Viewport from '@/core/engine/Viewport.ts' import type IControls from '@/core/controls/IControls.ts' import { getClosestObject } from '@/core/ModelUtils.ts' import EventBus from '@/runtime/EventBus.ts' +import type { Object3DLike } from '@/types/ModelTypes.ts' +import InstancePointManager, { PointManageWrap } from '@/core/manager/InstancePointManager.ts' +import { LineManageWrap } from '@/core/manager/LineSegmentManager.ts' /** * ThreeJS 拖拽管理器(仅限 X/Z 平面) @@ -171,7 +174,7 @@ export default class DragControl implements IControls { /** * 射线检测,返回第一个可拖拽对象 */ - private getIntersectedDraggableObject(mouse: THREE.Vector2): THREE.Object3D | null { + private getIntersectedDraggableObject(mouse: THREE.Vector2): Object3DLike | undefined { const raycaster = new THREE.Raycaster() raycaster.setFromCamera(mouse, this.viewport.camera) @@ -179,7 +182,7 @@ export default class DragControl implements IControls { const intersects = raycaster.intersectObjects(draggableObjects, true) if (intersects.length > 0) { - return getClosestObject(intersects[0].object) + return getClosestObject(this.viewport, intersects[0].object, intersects[0].instanceId) } } @@ -187,7 +190,7 @@ export default class DragControl implements IControls { /** * 创建拖拽阴影 */ - private createShadows(objects: THREE.Object3D[]): void { + private createShadows(objects: Object3DLike[]): void { this.removeShadows() this.dragShadowsGroup = new THREE.Group() @@ -199,8 +202,12 @@ export default class DragControl implements IControls { // 克隆对象的几何体和材质 // const shadowBox = obj.clone() - - const box = new THREE.Box3().setFromObject(obj) + let box: THREE.Box3 + if (obj instanceof THREE.Object3D) { + box = new THREE.Box3().setFromObject(obj) + } else if (obj instanceof PointManageWrap) { + box = obj.createBox3() + } const size = new THREE.Vector3() box.getSize(size) const geometry = new THREE.PlaneGeometry(size.x, size.z) diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index 2ca1756..278ea9f 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -8,6 +8,8 @@ import EventBus from '@/runtime/EventBus' import { markRaw } from 'vue' import { getSetter } from '@/core/manager/ModuleManager.ts' import Constract from '@/core/Constract.ts' +import type { Object3DLike } from '@/types/ModelTypes.ts' +import { PointManageWrap } from '@/core/manager/InstancePointManager.ts' /** * 选择工具,用于在设计器中显示选中对象的包围盒 @@ -206,7 +208,12 @@ export default class SelectInspect implements IControls { if (!object.userData.entityId) { return } - const box = new THREE.Box3().setFromObject(object) + let box: THREE.Box3 + if (object instanceof PointManageWrap) { + box = object.createBox3() + } else if (object instanceof THREE.Object3D) { + box = new THREE.Box3().setFromObject(object) + } const min = box.min const max = box.max @@ -250,7 +257,14 @@ export default class SelectInspect implements IControls { // 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb) selectedObject.updateWorldMatrix(false, true) - const box = new THREE.Box3().setFromObject(selectedObject) + + let box: THREE.Box3 + if (selectedObject instanceof THREE.Object3D) { + box = new THREE.Box3().setFromObject(selectedObject) + + } else if (selectedObject instanceof PointManageWrap) { + box = selectedObject.createBox3() + } const min = box.min const max = box.max @@ -376,7 +390,7 @@ export default class SelectInspect implements IControls { this.clickTime = null if (Date.now() - clickTime < 200) { // 如果是点击事件,触发选中逻辑 - const objects: THREE.Object3D[] = this.viewport.entityManager.getObjectByCanvasMouse(event) + const objects: Object3DLike[] = this.viewport.entityManager.getObjectByCanvasMouse(event) if (objects.length > 0 && objects[0]?.userData?.entityId && objects[0]?.userData?.createType !== 'line') { console.log('mouseClick', objects) const object = objects[0] diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 7bf6da6..3bf8795 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -25,6 +25,7 @@ import type { PropertySetter } from '@/core/base/PropertyTypes.ts' import LabelManager from '@/core/manager/LabelManager.ts' import type InstancePointManager from '@/core/manager/InstancePointManager.ts' import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts' +import type { Object3DLike } from '@/types/ModelTypes.ts' /** * 视窗对象 @@ -595,7 +596,7 @@ export interface ViewportState { /** * 黄选的对象 */ - selectedObject: THREE.Object3D | undefined + selectedObject: Object3DLike | undefined selectedItem: ItemJson | undefined selectedEntityId: string | undefined selectedObjectSetter: PropertySetter | undefined @@ -603,7 +604,7 @@ export interface ViewportState { /** * 红选的对象集 */ - multiSelectedObjects: THREE.Object3D[] + multiSelectedObjects: Object3DLike[] multiSelectedItems: ItemJson[] multiSelectedEntityIds: string[] diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index d200225..47135d1 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -460,7 +460,7 @@ export default class EntityManager { return getFreezeDeep(this.___entityMap.get(linkStartPointId)) } - getObjectByCanvasMouse(event: MouseEvent): THREE.Object3D[] { + getObjectByCanvasMouse(event: MouseEvent): Object3DLike[] { const _domElement = this.viewport.renderer.domElement const rect = _domElement.getBoundingClientRect() const _pointer = new Vector2() @@ -476,8 +476,8 @@ export default class EntityManager { // 根据距离排序射线命中的对象集 return _.map( _intersections.sort((a, b) => a.distance - b.distance), - r => getClosestObject(r.object) - ).filter(obj => obj?.userData && obj.userData.selectable !== false) + r => getClosestObject(this.viewport, r.object, r.instanceId) + ).filter((obj: Object3DLike) => obj?.userData && obj.userData.selectable !== false) } /** diff --git a/src/core/manager/InstancePointManager.ts b/src/core/manager/InstancePointManager.ts index 7caecad..8de512f 100644 --- a/src/core/manager/InstancePointManager.ts +++ b/src/core/manager/InstancePointManager.ts @@ -2,16 +2,22 @@ import * as THREE from 'three' import type Viewport from '@/core/engine/Viewport.ts' import { Vector3 } from 'three/src/math/Vector3' +/** + * 点实例管理器 + */ export default class InstancePointManager { - private readonly viewport: Viewport - private readonly instancedMesh: THREE.InstancedMesh + public readonly viewport: Viewport + public readonly instancedMesh: THREE.InstancedMesh + private readonly freeIndices: number[] = [] private readonly maxInstanceCount: number private readonly geometry: THREE.BufferGeometry private readonly material: THREE.Material private readonly dummy: THREE.Object3D = new THREE.Object3D() // itemId -> instanceId - private instanceData = new Map() + private __uuidMap = new Map() + // instanceId -> itemId + private __indexIdMap = new Map() /** * 创建点实例 @@ -46,7 +52,7 @@ export default class InstancePointManager { * @param option 更新选项 */ updatePoint(item: ItemJson, option: { position?: Vector3, rotation?: Vector3, scale?: Vector3 } = {}): void { - const wrap = this.instanceData.get(item.id) + const wrap = this.__uuidMap.get(item.id) if (wrap === undefined) return wrap.visible = item.v !== false @@ -78,7 +84,16 @@ export default class InstancePointManager { * 获取点实例数据 */ getObject3DLike(id: string): PointManageWrap { - return this.instanceData.get(id) + return this.__uuidMap.get(id) + } + + /** + * 获取点实例数据 + */ + findByMeshInstanceId(instanceId: number): PointManageWrap { + const uuid = this.__indexIdMap.get(instanceId) + if (!uuid) return + return this.__uuidMap.get(uuid) } syncMeshObject3D(wrap: PointManageWrap) { @@ -86,7 +101,6 @@ export default class InstancePointManager { console.error('InstancePointManager: Invalid meshIndex for wrap', wrap) return } - if (!wrap.visible) { this.dummy.scale.set(0, 0, 0) } else { @@ -96,7 +110,11 @@ export default class InstancePointManager { } this.dummy.updateMatrix() - wrap.parent = this.instancedMesh + if (!wrap.parent) { + wrap.parent = this.instancedMesh + this.__uuidMap.set(wrap.uuid, wrap) + this.__indexIdMap.set(wrap.meshIndex, wrap.uuid) + } this.instancedMesh.setMatrixAt(wrap.meshIndex, this.dummy.matrix) this.instancedMesh.instanceMatrix.needsUpdate = true } @@ -106,7 +124,7 @@ export default class InstancePointManager { * @param id 点ID */ deletePoint(id: string): void { - const wrap = this.instanceData.get(id) + const wrap = this.__uuidMap.get(id) if (wrap === undefined) return // 隐藏实例 @@ -117,52 +135,24 @@ export default class InstancePointManager { // 回收索引 this.freeIndices.push(wrap.meshIndex) - this.instanceData.delete(id) + this.__uuidMap.delete(id) + this.__indexIdMap.delete(wrap.meshIndex) wrap.dispose() } - /** - * 获取点实例的世界位置 - * @param id 点ID - * @param target 目标向量 - */ - getWorldPosition(id: string, target: THREE.Vector3): void { - const instanceData = this.instanceData.get(id) - if (instanceData === undefined) return - - const matrix = new THREE.Matrix4() - this.instancedMesh.getMatrixAt(instanceData.meshIndex, matrix) - target.setFromMatrixPosition(matrix) - } - - // init(viewport: Viewport): void { - // this.viewport = viewport - // - // const geometry = new THREE.PlaneGeometry(0.25, 0.25) - // const material = new THREE.MeshBasicMaterial({ - // color: 0xFFFF99, - // transparent: true, - // depthWrite: false, - // side: THREE.DoubleSide - // }) - // - // this.mesh = new THREE.InstancedMesh(geometry, material, this.maxInstances) - // this.mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) - // viewport.scene.add(this.mesh) - // } - - public static create( + name: string, viewport: Viewport, geometry: THREE.BufferGeometry, material: THREE.Material, maxInstances: number = 1000 ): InstancePointManager { - return new InstancePointManager(viewport, geometry, material, maxInstances) + return new InstancePointManager(name, viewport, geometry, material, maxInstances) } - constructor( + private constructor( + name: string, viewport: Viewport, geometry: THREE.BufferGeometry, material: THREE.Material, @@ -175,7 +165,13 @@ export default class InstancePointManager { this.instancedMesh = new THREE.InstancedMesh(geometry, material, maxInstances) this.instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) + this.instancedMesh.name = name + console.log('InstancePointManager: [' + name + '] created with maxInstances:', maxInstances) viewport.scene.add(this.instancedMesh) + this.viewport.entityManager._selectableObjects.push(this.instancedMesh) + this.viewport.entityManager._draggableObjects.push(this.instancedMesh) + this.instancedMesh.userData.t = name + this.instancedMesh.userData.entityId = 'InstancePointManager' this.dummy.scale.set(0, 0, 0) for (let i = 0; i < maxInstances; i++) { @@ -200,7 +196,8 @@ export default class InstancePointManager { } this.instancedMesh.dispose() - this.instanceData.clear() + this.__uuidMap.clear() + this.__indexIdMap.clear() this.freeIndices.length = 0 } } @@ -219,6 +216,9 @@ export class PointManageWrap { //@ts-ignore userData: UserData = {} + updateWorldMatrix() { + } + get type() { return 'PointManageWrap' } @@ -238,4 +238,14 @@ export class PointManageWrap { this.parent = null this.meshIndex = -1 } + + createBox3(): THREE.Box3 { + const instancedMesh = this.manager.instancedMesh + const matrix = new THREE.Matrix4() + instancedMesh.getMatrixAt(this.meshIndex, matrix) + // 创建包围盒并应用矩阵 + const geometry = instancedMesh.geometry + const localBox = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) + return localBox.clone().applyMatrix4(matrix) + } } diff --git a/src/core/manager/LineSegmentManager.ts b/src/core/manager/LineSegmentManager.ts index 883fd99..c011da0 100644 --- a/src/core/manager/LineSegmentManager.ts +++ b/src/core/manager/LineSegmentManager.ts @@ -248,11 +248,11 @@ export default class LineSegmentManager { /** * 创建线段管理器实例 */ - public static create(viewport: Viewport, lineMaterial: LineMaterial): LineSegmentManager { - return new LineSegmentManager(viewport, lineMaterial) + public static create(name: string, viewport: Viewport, lineMaterial: LineMaterial): LineSegmentManager { + return new LineSegmentManager(name, viewport, lineMaterial) } - private constructor(viewport: Viewport, lineMaterial: LineMaterial) { + private constructor(name: string, viewport: Viewport, lineMaterial: LineMaterial) { this.viewport = viewport this.lineMaterial = lineMaterial @@ -260,7 +260,7 @@ export default class LineSegmentManager { // 创建线段的渲染对象 this.lineSegments = new LineSegments2(this.lineGeometry, this.lineMaterial) - this.lineSegments.name = 'batch_line_segments' + this.lineSegments.name = name this.lineSegments.frustumCulled = false this.viewport.scene.add(this.lineSegments) } @@ -309,6 +309,8 @@ export class LineManageWrap { return false } + updateWorldMatrix(){} + constructor(lineManager: LineSegmentManager, data: any, start: THREE.Vector3, end: THREE.Vector3, color: THREE.Color) { this.manager = lineManager _.extend(this, data) diff --git a/src/example/example1.js b/src/example/example1.js index 821b4e5..478c946 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -66,13 +66,15 @@ export default { v: true, tf: [[-4, 0.1, 4], [90, 0, 0], [0.25, 0.25, 0.1]], dt: { in: [], out: [], center: ['P2'] } - }, { + }, + { id: 'P2', t: 'measure', v: true, tf: [[5, 0.1, 4], [90, 0, 0], [0.25, 0.25, 0.1]], dt: { in: [], out: [], center: ['P1', 'P3'] } - }, { + }, + { id: 'P3', t: 'measure', v: true, diff --git a/src/modules/measure/MeasureRenderer.ts b/src/modules/measure/MeasureRenderer.ts index fdb3d8a..b5d81a8 100644 --- a/src/modules/measure/MeasureRenderer.ts +++ b/src/modules/measure/MeasureRenderer.ts @@ -37,15 +37,15 @@ export default class MeasureRenderer extends BaseRenderer { dashed: false }) readonly defulePositionY = Constract.HEIGHT_MEASURE - readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.1, 0.25) - readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) + readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.25, 0.1) + readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(90, 0, 0) createPointManager(): InstancePointManager { - return InstancePointManager.create(this.tempViewport, this.pointGeometry, this.pointMaterial, Constract.MAX_MEASURE_INSTANCES) + return InstancePointManager.create(this.itemTypeName, this.tempViewport, this.pointGeometry, this.pointMaterial, Constract.MAX_MEASURE_INSTANCES) } createLineSegmentManager(): LineSegmentManager { - return LineSegmentManager.create(this.tempViewport, this.lineMaterial) + return LineSegmentManager.create(this.itemTypeName, this.tempViewport, this.lineMaterial) } /**