From ca12e043fb444dbbf4b3c79a22e169eff2addad0 Mon Sep 17 00:00:00 2001 From: luoyifan Date: Mon, 9 Jun 2025 18:15:35 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=BA=BF=E6=AE=B5=20/=20?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E7=82=B9=E4=BD=8D=E7=AE=A1=E7=90=86.=20Insta?= =?UTF-8?q?ncePointManager=20/=20LineSegmentManager?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/Constract.ts | 4 +- src/core/base/BaseRenderer.ts | 45 ++++++ src/core/engine/Viewport.ts | 44 +++-- src/core/manager/InstancePointManager.ts | 183 ++++++++++++++++++--- src/core/manager/LineSegmentManager.ts | 267 ++++++++++++++++++++++++++++++- src/modules/measure/MeasureRenderer.ts | 41 +++-- 6 files changed, 526 insertions(+), 58 deletions(-) diff --git a/src/core/Constract.ts b/src/core/Constract.ts index e691545..0f4e6c7 100644 --- a/src/core/Constract.ts +++ b/src/core/Constract.ts @@ -31,6 +31,8 @@ const Constract = Object.freeze({ HEIGHT_RACK: 0, HEIGHT_WAY: 0.01, HEIGHT_WAY_LABEL: 0.03, - HEIGHT_WAY_LINE: 0.02 + HEIGHT_WAY_LINE: 0.02, + + MAX_MEASURE_INSTANCES: 1000, }) export default Constract diff --git a/src/core/base/BaseRenderer.ts b/src/core/base/BaseRenderer.ts index 3008069..9f43815 100644 --- a/src/core/base/BaseRenderer.ts +++ b/src/core/base/BaseRenderer.ts @@ -2,6 +2,9 @@ import type Viewport from '@/core/engine/Viewport' import * as THREE from 'three' import { getLineId } from '@/core/ModelUtils.ts' import { Line2 } from 'three/examples/jsm/lines/Line2' +import InstancePointManager from '@/core/manager/InstancePointManager.ts' +import Constract from '@/core/Constract.ts' +import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts' /** * 基本渲染器基类 @@ -27,6 +30,48 @@ export default abstract class BaseRenderer { return Promise.resolve() } + get pointManager(): InstancePointManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set. Please call beginRendererUpdate first.') + } + let pointManager = this.tempViewport.pointManagerMap.get(this.itemTypeName) + if (!pointManager) { + pointManager = this.createPointManager() + if (pointManager) { + this.tempViewport.pointManagerMap.set(this.itemTypeName, pointManager) + } + } + return pointManager + } + + get lineSegmentManager(): LineSegmentManager { + if (!this.tempViewport) { + throw new Error('tempViewport is not set. Please call beginRendererUpdate first.') + } + let lineSegmentManager = this.tempViewport.lineSegmentManagerMap.get(this.itemTypeName) + if (!lineSegmentManager) { + lineSegmentManager = this.createLineSegmentManager() + if (lineSegmentManager) { + this.tempViewport.lineSegmentManagerMap.set(this.itemTypeName, lineSegmentManager) + } + } + return lineSegmentManager + } + + /** + * 创建点管理器 + */ + createPointManager(): InstancePointManager { + return null + } + + /** + * 创建线段管理器 + */ + createLineSegmentManager(): LineSegmentManager { + return null + } + /** * 开始更新 * @param viewport 当前视口 diff --git a/src/core/engine/Viewport.ts b/src/core/engine/Viewport.ts index 4448b35..7bf6da6 100644 --- a/src/core/engine/Viewport.ts +++ b/src/core/engine/Viewport.ts @@ -23,7 +23,8 @@ import Constract from '@/core/Constract.ts' import DragControl from '@/core/controls/DragControl.ts' import type { PropertySetter } from '@/core/base/PropertyTypes.ts' import LabelManager from '@/core/manager/LabelManager.ts' -import ResourceManager from '@/core/manager/ResourceManager.ts' +import type InstancePointManager from '@/core/manager/InstancePointManager.ts' +import type LineSegmentManager from '@/core/manager/LineSegmentManager.ts' /** * 视窗对象 @@ -60,6 +61,12 @@ export default class Viewport { // 交互管理器 interactionManager = new InteractionManager() + // 点实例管理器 moduleName -> InstancePointManager + pointManagerMap: Map = new Map() + + // 线段实例管理器 moduleName -> InstancePointManager + lineSegmentManagerMap: Map = new Map() + // 监听窗口大小变化 resizeObserver?: ResizeObserver @@ -331,23 +338,23 @@ export default class Viewport { */ animate() { this.animationFrameId = requestAnimationFrame(this.animate.bind(this)) - this.renderView() - if (window['lineMaterial']) { - this.offset -= 0.002 - window['lineMaterial'].dashOffset = this.offset + for (const lineSegmentManager of this.lineSegmentManagerMap.values()) { + if (lineSegmentManager.needsUpdate) { + lineSegmentManager.updateGeometry() + } } - } - /** - * 渲染视图 - */ - renderView() { this.statsControls?.update() this.renderer?.render(this.scene.scene, this.camera) this.css2DRenderer.render(this.scene.scene, this.camera) this.css3DRenderer.render(this.scene.scene, this.camera) + + // if (window['lineMaterial']) { + // this.offset -= 0.002 + // window['lineMaterial'].dashOffset = this.offset + // } } /** @@ -463,6 +470,23 @@ export default class Viewport { dispose() { this.state.isReady = false + if (this.pointManagerMap.size > 0) { + this.pointManagerMap.forEach((manager) => { + if (manager.dispose) { + manager.dispose() + } + }) + this.pointManagerMap.clear() + } + if (this.lineSegmentManagerMap.size > 0) { + this.lineSegmentManagerMap.forEach((manager) => { + if (manager.dispose) { + manager.dispose() + } + }) + this.lineSegmentManagerMap.clear() + } + if (this.tools) { for (const tool of this.tools) { if (tool.dispose) { diff --git a/src/core/manager/InstancePointManager.ts b/src/core/manager/InstancePointManager.ts index 1a7e458..62c6fd3 100644 --- a/src/core/manager/InstancePointManager.ts +++ b/src/core/manager/InstancePointManager.ts @@ -1,30 +1,177 @@ import * as THREE from 'three' import type IControls from '@/core/controls/IControls.ts' import type Viewport from '@/core/engine/Viewport.ts' +import { Vector3 } from 'three/src/math/Vector3' +import { Euler } from 'three/src/math/Euler' -export default class InstancePointManager implements IControls { - viewport: Viewport - mesh: THREE.InstancedMesh - dummy: THREE.Object3D = new THREE.Object3D() - count: number = 0 - maxInstances: number = 100000 +export default class InstancePointManager { + private readonly viewport: Viewport + private 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: Map = new Map() - init(viewport: Viewport): void { + /** + * 创建点实例 + * @param item 点数据 + * @returns 分配的实例ID (如果失败返回-1) + */ + createPoint(item: ItemJson): number { + if (this.freeIndices.length === 0) { + system.showErrorDialog('InstancePointManager is full') + return -1 + } + + const instanceId = this.freeIndices.pop()! + this.instanceData.set(item.id, instanceId) + + this.updatePoint(item) + return instanceId + } + + /** + * 更新点实例 + * @param item 点数据 + * @param option 更新选项 + */ + updatePoint(item: ItemJson, option: { + position?: Vector3 + rotation?: Vector3 + scale?: Vector3 + } = {}): void { + const instanceId = this.instanceData.get(item.id) + if (instanceId === undefined) return + + let [position, rotation, scale] = item.tf + if (option.position) { + position = option.position.toArray() + } + if (option.rotation) { + rotation = option.rotation.toArray() + } + if (option.scale) { + scale = option.scale.toArray() + } + + this.dummy.position.set(position[0], position[1], position[2]) + this.dummy.rotation.set( + THREE.MathUtils.degToRad(rotation[0]), + THREE.MathUtils.degToRad(rotation[1]), + THREE.MathUtils.degToRad(rotation[2]) + ) + this.dummy.scale.set(scale[0], scale[1], scale[2]) + + if (item.v === false) { + this.dummy.scale.set(0, 0, 0) + } + this.dummy.updateMatrix() + + this.instancedMesh.setMatrixAt(instanceId, this.dummy.matrix) + this.instancedMesh.instanceMatrix.needsUpdate = true + } + + /** + * 删除点实例 + * @param id 点ID + */ + deletePoint(id: string): void { + const instanceId = this.instanceData.get(id) + if (instanceId === undefined) return + + // 隐藏实例 + this.dummy.scale.set(0, 0, 0) + this.dummy.updateMatrix() + this.instancedMesh.setMatrixAt(instanceId, this.dummy.matrix) + this.instancedMesh.instanceMatrix.needsUpdate = true + + // 回收索引 + this.freeIndices.push(instanceId) + this.instanceData.delete(id) + } + + /** + * 获取点实例的世界位置 + * @param id 点ID + * @param target 目标向量 + */ + getWorldPosition(id: string, target: THREE.Vector3): void { + const instanceId = this.instanceData.get(id) + if (instanceId === undefined) return + + const matrix = new THREE.Matrix4() + this.instancedMesh.getMatrixAt(instanceId, 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( + viewport: Viewport, + geometry: THREE.BufferGeometry, + material: THREE.Material, + maxInstances: number = 1000 + ): InstancePointManager { + return new InstancePointManager(viewport, geometry, material, maxInstances) + } + + constructor( + viewport: Viewport, + geometry: THREE.BufferGeometry, + material: THREE.Material, + maxInstances: number = 1000 + ) { this.viewport = viewport + this.geometry = geometry + this.material = material + this.maxInstanceCount = maxInstances + + this.instancedMesh = new THREE.InstancedMesh(geometry, material, maxInstances) + this.instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage) + viewport.scene.add(this.instancedMesh) - 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) + this.dummy.scale.set(0, 0, 0) + for (let i = 0; i < maxInstances; i++) { + this.dummy.updateMatrix() + this.instancedMesh.setMatrixAt(i, this.dummy.matrix) + this.freeIndices.push(i) + } + + this.instancedMesh.instanceMatrix.needsUpdate = true } dispose() { + this.viewport.scene.remove(this.instancedMesh) + this.instancedMesh.geometry.dispose() + + if (this.instancedMesh.material) { + if (Array.isArray(this.instancedMesh.material)) { + this.instancedMesh.material.forEach(mat => mat.dispose()) + } else { + this.instancedMesh.material.dispose() + } + } + + this.instancedMesh.dispose() + this.instanceData.clear() + this.freeIndices.length = 0 } } diff --git a/src/core/manager/LineSegmentManager.ts b/src/core/manager/LineSegmentManager.ts index e1659d8..86137b2 100644 --- a/src/core/manager/LineSegmentManager.ts +++ b/src/core/manager/LineSegmentManager.ts @@ -1,16 +1,269 @@ import * as THREE from 'three' -import type IControls from '@/core/controls/IControls.ts' import type Viewport from '@/core/engine/Viewport.ts' +import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry' +import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2' +import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' -export default class LineSegmentManager implements IControls { - viewport: Viewport +/** + * 线段管理器 + */ +export default class LineSegmentManager { + private readonly viewport: Viewport + private readonly lineGeometry: LineSegmentsGeometry + private readonly lineMaterial: LineMaterial + private readonly lineSegments: LineSegments2 + private readonly segments: Map = new Map() + public needsUpdate: boolean = false + private colorArray: Float32Array | null = null + private positionArray: Float32Array | null = null - init(viewport: Viewport): void { + /** + * 创建或更新线段 + * @param key 线段唯一标识 + * @param start 起点 + * @param end 终点 + * @param color 线段颜色 (可选) + * @param userData 自定义数据 (可选) + */ + createLine(key: string, start: THREE.Vector3Like, end: THREE.Vector3Like, color?: THREE.Color | number | string, userData?: any): void { + const startVec = start instanceof THREE.Vector3 + ? start.clone() + : new THREE.Vector3(start[0], start[1], start[2]) + + const endVec = end instanceof THREE.Vector3 + ? end.clone() + : new THREE.Vector3(end[0], end[1], end[2]) + + let colorObj: THREE.Color | undefined + if (color instanceof THREE.Color) { + colorObj = color + } else if (typeof color === 'number') { + colorObj = new THREE.Color(color) + } else if (typeof color === 'string') { + colorObj = new THREE.Color(color) + } + + if (this.segments.has(key)) { + // 更新现有线段 + const segment = this.segments.get(key)! + segment.start.copy(startVec) + segment.end.copy(endVec) + if (colorObj) segment.color = colorObj + if (userData) segment.userData = userData + } else { + // 创建新线段 + this.segments.set(key, { + key, + start: startVec, + end: endVec, + color: colorObj, + visible: true, + userData + }) + } + + this.needsUpdate = true + } + + /** + * 更新线段位置 + * @param key 线段唯一标识 + * @param start 新起点 + * @param end 新终点 + */ + updateLinePosition(key: string, start: THREE.Vector3Like, end: THREE.Vector3Like): void { + const segment = this.segments.get(key) + if (!segment) return + + if (start instanceof THREE.Vector3) { + segment.start.copy(start) + } else { + segment.start.set(start[0], start[1], start[2]) + } + + if (end instanceof THREE.Vector3) { + segment.end.copy(end) + } else { + segment.end.set(end[0], end[1], end[2]) + } + + this.needsUpdate = true + } + + /** + * 更新线段颜色 + * @param key 线段唯一标识 + * @param color 新颜色 + */ + updateLineColor(key: string, color: THREE.Color | number | string): void { + const segment = this.segments.get(key) + if (!segment) return + + if (color instanceof THREE.Color) { + segment.color = color + } else if (typeof color === 'number') { + segment.color = new THREE.Color(color) + } else if (typeof color === 'string') { + segment.color = new THREE.Color(color) + } else { + segment.color = undefined + } + + this.needsUpdate = true + } + + /** + * 设置线段可见性 + * @param key 线段唯一标识 + * @param visible 是否可见 + */ + setLineVisible(key: string, visible: boolean): void { + const segment = this.segments.get(key) + if (segment && segment.visible !== visible) { + segment.visible = visible + this.needsUpdate = true + } + } + + /** + * 删除线段 + * @param key 线段唯一标识 + */ + deleteLine(key: string): void { + if (this.segments.delete(key)) { + this.needsUpdate = true + } + } + + /** + * 获取线段数据 + * @param key 线段唯一标识 + */ + getLineData(key: string): LineSegmentData | undefined { + return this.segments.get(key) + } + + /** + * 更新几何体数据 (应在渲染循环中调用) + */ + updateGeometry(): void { + if (!this.needsUpdate) return + + const segmentCount = this.segments.size + const positionCount = segmentCount * 6 // 每条线段2个点,每个点3个分量 + const colorCount = segmentCount * 6 // 每个顶点3个颜色分量 + + // 初始化或调整数组大小 + if (!this.positionArray || this.positionArray.length !== positionCount) { + this.positionArray = new Float32Array(positionCount) + } + + if (!this.colorArray || this.colorArray.length !== colorCount) { + this.colorArray = new Float32Array(colorCount) + } + + // 填充数据 + let positionIndex = 0 + let colorIndex = 0 + + const defaultColor = new THREE.Color(this.lineMaterial.color) + + this.segments.forEach(segment => { + if (!segment.visible) { + // 对于不可见的线段,跳过位置设置但保留颜色为黑色 + positionIndex += 6 + + // 设置颜色为黑色(透明) + this.colorArray![colorIndex++] = 0 + this.colorArray![colorIndex++] = 0 + this.colorArray![colorIndex++] = 0 + + this.colorArray![colorIndex++] = 0 + this.colorArray![colorIndex++] = 0 + this.colorArray![colorIndex++] = 0 + return + } + + // 设置起点位置 + this.positionArray![positionIndex++] = segment.start.x + this.positionArray![positionIndex++] = segment.start.y + this.positionArray![positionIndex++] = segment.start.z + + // 设置终点位置 + this.positionArray![positionIndex++] = segment.end.x + this.positionArray![positionIndex++] = segment.end.y + this.positionArray![positionIndex++] = segment.end.z + + // 设置起点颜色 + const startColor = segment.color || defaultColor + this.colorArray![colorIndex++] = startColor.r + this.colorArray![colorIndex++] = startColor.g + this.colorArray![colorIndex++] = startColor.b + + // 设置终点颜色 + const endColor = segment.color || defaultColor + this.colorArray![colorIndex++] = endColor.r + this.colorArray![colorIndex++] = endColor.g + this.colorArray![colorIndex++] = endColor.b + }) + + // 更新几何体 + this.lineGeometry.setPositions(this.positionArray) + this.lineGeometry.setColors(this.colorArray) + + // 设置实例计数为可见线段数量 + const visibleCount = Array.from(this.segments.values()).filter(s => s.visible).length + this.lineGeometry.instanceCount = visibleCount + + this.needsUpdate = false + } + + /** + * 创建线段管理器实例 + */ + public static create(viewport: Viewport, lineMaterial: LineMaterial): LineSegmentManager { + return new LineSegmentManager(viewport, lineMaterial) + } + + private constructor(viewport: Viewport, lineMaterial: LineMaterial) { this.viewport = viewport + this.lineMaterial = lineMaterial + + this.lineGeometry = new LineSegmentsGeometry() + + // 创建线段的渲染对象 + this.lineSegments = new LineSegments2(this.lineGeometry, this.lineMaterial) + this.lineSegments.name = 'batch_line_segments' + this.lineSegments.frustumCulled = false + this.viewport.scene.add(this.lineSegments) } - dispose(): void { - // 清理资源 - this.viewport = undefined + dispose() { + this.viewport.scene.remove(this.lineSegments) + this.lineGeometry.dispose() + + if (this.lineMaterial) { + if (Array.isArray(this.lineMaterial)) { + this.lineMaterial.forEach(mat => mat.dispose()) + } else { + this.lineMaterial.dispose() + } + } + + this.segments.clear() + this.positionArray = null + this.colorArray = null } } + +/** + * 线段数据接口 + */ +interface LineSegmentData { + key: string; + start: THREE.Vector3; + end: THREE.Vector3; + color?: THREE.Color; + visible?: boolean; + userData?: any; +} diff --git a/src/modules/measure/MeasureRenderer.ts b/src/modules/measure/MeasureRenderer.ts index acdfc7a..56eadf9 100644 --- a/src/modules/measure/MeasureRenderer.ts +++ b/src/modules/measure/MeasureRenderer.ts @@ -5,6 +5,8 @@ import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' import { Line2 } from 'three/examples/jsm/lines/Line2.js' import Constract from '@/core/Constract.ts' +import InstancePointManager from '@/core/manager/InstancePointManager.ts' +import LineSegmentManager from '@/core/manager/LineSegmentManager.ts' /** * 辅助测量工具渲染器 @@ -22,34 +24,29 @@ export default class MeasureRenderer extends BaseRenderer { public useHtmlLabel = false - pointMaterial: THREE.Material - lineMaterial: LineMaterial - + pointMaterial: THREE.Material = new THREE.MeshBasicMaterial({ + color: 0xFFFF99, + transparent: true, + depthWrite: false, + side: THREE.DoubleSide + }) + pointGeometry = new THREE.PlaneGeometry(0.25, 0.25) + lineMaterial: LineMaterial = new LineMaterial({ + color: 0xFF8C00, + linewidth: 1, + vertexColors: false, + dashed: false + }) readonly defulePositionY = Constract.HEIGHT_MEASURE readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.1, 0.1, 0.1) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) - constructor(itemTypeName: string) { - super(itemTypeName) + createPointManager(): InstancePointManager { + return InstancePointManager.create(this.tempViewport, this.pointGeometry, this.pointMaterial, Constract.MAX_MEASURE_INSTANCES) } - async init() { - this.pointMaterial = new THREE.SpriteMaterial({ - color: 0xFFFF99, // 0x303133, - transparent: true, - side: THREE.DoubleSide, - opacity: 1, - sizeAttenuation: true - }) - this.lineMaterial = new LineMaterial({ - color: 0xFF8C00, - linewidth: 1, - vertexColors: false, - dashed: false - }) - return Promise.all([ - super.init() - ]) + createLineSegmentManager(): LineSegmentManager { + return LineSegmentManager.create(this.tempViewport, this.lineMaterial) } /**