8 changed files with 929 additions and 123 deletions
@ -0,0 +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 { |
|||
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<string, number> = new Map() |
|||
|
|||
/** |
|||
* 创建点实例 |
|||
* @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) |
|||
|
|||
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 |
|||
} |
|||
} |
|||
@ -0,0 +1,207 @@ |
|||
import * as THREE from 'three' |
|||
import type IControls from '@/core/controls/IControls.ts' |
|||
import type Viewport from '@/core/engine/Viewport.ts' |
|||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' |
|||
import { Text } from 'troika-three-text' |
|||
import SimSunTTF from '@/assets/fonts/simsunb.ttf' |
|||
|
|||
export interface LabelOption { |
|||
/** |
|||
* 标签组件名称 |
|||
*/ |
|||
name?: string |
|||
/** |
|||
* 是否使用 HTML 标签 |
|||
*/ |
|||
useHtmlLabel: boolean |
|||
/** |
|||
* ex: css='14px', text=0.4 |
|||
*/ |
|||
fontSize: number | string |
|||
/** |
|||
* ex: '#ffffff' |
|||
*/ |
|||
color: string |
|||
/** |
|||
* ex: css='5px 8px', text=0.2 |
|||
*/ |
|||
padding?: number | string |
|||
text?: string |
|||
} |
|||
|
|||
/** |
|||
* 标签管理器 |
|||
*/ |
|||
export default class LabelManager implements IControls { |
|||
viewport: Viewport |
|||
private labelMap: Map<string, Text | CSS2DObject> = new Map() |
|||
|
|||
init(viewport: Viewport): void { |
|||
this.viewport = viewport |
|||
} |
|||
|
|||
createOrUpdateLabelByDistance(parentObj: THREE.Object3D, startPos: THREE.Vector3, endPos: THREE.Vector3, option: LabelOption): Text | CSS2DObject { |
|||
let labelObj = this.labelMap.get(parentObj.userData.labelObjectId) |
|||
if (!labelObj) { |
|||
labelObj = this.createLabel(parentObj, option) |
|||
} |
|||
|
|||
const position = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5) |
|||
labelObj.position.set(position.x, position.y + 0.3, position.z - 0.2) |
|||
|
|||
// 计算距离
|
|||
const distance = startPos.distanceTo(endPos) |
|||
const text = distance.toFixed(2) + ' m' |
|||
this.updateLabel(parentObj, text) |
|||
|
|||
return labelObj |
|||
} |
|||
|
|||
createLabel(parentObj: THREE.Object3D, option: LabelOption): Text | CSS2DObject { |
|||
const labelObj = this.createLabelObject(option) |
|||
parentObj.userData.labelObjectId = labelObj.uuid |
|||
|
|||
this.viewport.scene.add(labelObj) |
|||
|
|||
if (labelObj instanceof CSS2DObject) { |
|||
labelObj.element.innerHTML = option.text |
|||
|
|||
} else if (labelObj instanceof Text) { |
|||
// 让文本朝向摄像机
|
|||
labelObj.quaternion.copy(this.viewport.camera.quaternion) |
|||
labelObj.text = option.text |
|||
labelObj.sync() |
|||
} |
|||
|
|||
return labelObj |
|||
} |
|||
|
|||
|
|||
updateLabel(parentObj: THREE.Object3D, text: string) { |
|||
const labelObj = this.labelMap.get(parentObj.userData.labelObjectId) |
|||
if (labelObj) { |
|||
if (labelObj instanceof CSS2DObject) { |
|||
labelObj.element.innerHTML = text |
|||
|
|||
} else if (labelObj instanceof Text) { |
|||
labelObj.text = text |
|||
labelObj.sync() |
|||
} |
|||
} else { |
|||
console.warn('Label not found for parent object:', parentObj) |
|||
} |
|||
} |
|||
|
|||
removeLabel(parentObj: THREE.Object3D) { |
|||
if (parentObj?.userData?.labelObjectId) { |
|||
const labelObj = this.labelMap.get(parentObj.userData.labelObjectId) |
|||
this.labelMap.delete(labelObj.uuid) |
|||
this.viewport.scene.remove(labelObj) |
|||
labelObj.dispose() |
|||
parentObj.userData.labelObjectId = undefined |
|||
} |
|||
} |
|||
|
|||
removeById(id: string): void { |
|||
const labelObj = this.labelMap.get(id) |
|||
if (labelObj) { |
|||
this.viewport.scene.remove(labelObj) |
|||
this.labelMap.delete(id) |
|||
labelObj.dispose() |
|||
} |
|||
} |
|||
|
|||
private createLabelObject(option: LabelOption): Text | CSS2DObject { |
|||
if (option.useHtmlLabel) { |
|||
const div = document.createElement('div') |
|||
div.className = 'css2dObjectLabel' |
|||
div.innerHTML = option.text |
|||
div.style.padding = (option.padding as string) |
|||
div.style.color = option.color |
|||
div.style.fontSize = (option.fontSize as string) |
|||
div.style.position = 'absolute' |
|||
div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)' |
|||
div.style.borderRadius = '12px' |
|||
div.style.top = '0px' |
|||
div.style.left = '0px' |
|||
// div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
|
|||
const obj = new CSS2DObject(div) |
|||
obj.uuid = system.createUUID() |
|||
obj.name = option.name |
|||
this.labelMap.set(obj.uuid, obj) |
|||
return obj |
|||
|
|||
} else { |
|||
const label = new Text() |
|||
label.uuid = system.createUUID() |
|||
label.text = option.text |
|||
label.font = SimSunTTF |
|||
label.fontSize = (option.fontSize as number) |
|||
label.color = option.color |
|||
label.opacity = 0.8 |
|||
label.padding = (option.padding as number) |
|||
label.anchorX = 'center' |
|||
label.anchorY = 'middle' |
|||
label.depthOffset = 1 |
|||
label.backgroundColor = '#000000' // 黑色背景
|
|||
label.backgroundOpacity = 0.6 // 背景半透明
|
|||
label.padding = 0.2 // 内边距
|
|||
label.material.depthTest = false |
|||
label.name = option.name |
|||
|
|||
label.sync() |
|||
this.labelMap.set(label.uuid, label) |
|||
return label |
|||
} |
|||
} |
|||
|
|||
dispose(): void { |
|||
// 清理资源
|
|||
this.viewport = undefined |
|||
} |
|||
|
|||
|
|||
// /**
|
|||
// * 手动创建标签示例
|
|||
// */
|
|||
// createLabel(text: string): Text | CSS2DObject {
|
|||
// if (this.useHtmlLabel) {
|
|||
// const div = document.createElement('div')
|
|||
// div.className = 'css2dObjectLabel'
|
|||
// div.innerHTML = text
|
|||
// div.style.padding = '5px 8px'
|
|||
// div.style.color = '#fff'
|
|||
// div.style.fontSize = '14px'
|
|||
// div.style.position = 'absolute'
|
|||
// div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)'
|
|||
// div.style.borderRadius = '12px'
|
|||
// div.style.top = '0px'
|
|||
// div.style.left = '0px'
|
|||
// // div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
|
|||
// const obj = new CSS2DObject(div)
|
|||
// obj.name = MeasureRenderer.LABEL_NAME
|
|||
// return obj
|
|||
//
|
|||
// } else {
|
|||
// const label = new Text()
|
|||
// label.text = text
|
|||
// label.font = SimSunTTF
|
|||
// label.fontSize = 0.4
|
|||
// label.color = '#333333'
|
|||
// label.opacity = 0.8
|
|||
// label.padding = 0.2
|
|||
// label.anchorX = 'center'
|
|||
// label.anchorY = 'middle'
|
|||
// label.depthOffset = 1
|
|||
// label.backgroundColor = '#000000' // 黑色背景
|
|||
// label.backgroundOpacity = 0.6 // 背景半透明
|
|||
// label.padding = 0.2 // 内边距
|
|||
// label.material.depthTest = false
|
|||
// label.name = MeasureRenderer.LABEL_NAME
|
|||
//
|
|||
// label.sync()
|
|||
// return label
|
|||
// }
|
|||
// }
|
|||
} |
|||
|
|||
@ -0,0 +1,269 @@ |
|||
import * as THREE from 'three' |
|||
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 { |
|||
private readonly viewport: Viewport |
|||
private readonly lineGeometry: LineSegmentsGeometry |
|||
private readonly lineMaterial: LineMaterial |
|||
private readonly lineSegments: LineSegments2 |
|||
private readonly segments: Map<string, LineSegmentData> = new Map() |
|||
public needsUpdate: boolean = false |
|||
private colorArray: Float32Array | null = null |
|||
private positionArray: Float32Array | null = null |
|||
|
|||
/** |
|||
* 创建或更新线段 |
|||
* @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() { |
|||
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; |
|||
} |
|||
@ -0,0 +1,120 @@ |
|||
import * as THREE from 'three' |
|||
import type Viewport from '@/core/engine/Viewport.ts' |
|||
|
|||
/** |
|||
* 资源管理器 |
|||
* 管理 Three.js 中的几何体、材质、纹理和网格等资源 |
|||
* 提供创建、获取和销毁资源的方法 |
|||
*/ |
|||
export default class ResourceManager { |
|||
public static readonly instance: ResourceManager = new ResourceManager() |
|||
|
|||
viewport: Viewport |
|||
geometries: Map<string, THREE.BufferGeometry> = new Map() |
|||
materials: Map<string, THREE.Material> = new Map() |
|||
textures: Map<string, THREE.Texture> = new Map() |
|||
meshes: Map<string, THREE.Mesh> = new Map() |
|||
|
|||
createGeometry(key: string, geometry: THREE.BufferGeometry) { |
|||
this.geometries.set(key, geometry) |
|||
return geometry |
|||
} |
|||
|
|||
createMaterial(key: string, material: THREE.Material) { |
|||
this.materials.set(key, material) |
|||
return material |
|||
} |
|||
|
|||
createTexture(key: string, texture: THREE.Texture) { |
|||
this.textures.set(key, texture) |
|||
return texture |
|||
} |
|||
|
|||
createMesh(key: string, mesh: THREE.Mesh) { |
|||
this.meshes.set(key, mesh) |
|||
return mesh |
|||
} |
|||
|
|||
getGeometry(key: string) { |
|||
return this.geometries.get(key) |
|||
} |
|||
|
|||
getMaterial(key: string) { |
|||
return this.materials.get(key) |
|||
} |
|||
|
|||
getTexture(key: string) { |
|||
return this.textures.get(key) |
|||
} |
|||
|
|||
getMesh(key: string) { |
|||
return this.meshes.get(key) |
|||
} |
|||
|
|||
disposeGeometry(key: string) { |
|||
const geometry = this.geometries.get(key) |
|||
if (geometry) { |
|||
geometry.dispose() |
|||
this.geometries.delete(key) |
|||
} |
|||
} |
|||
|
|||
disposeMaterial(key: string) { |
|||
const material = this.materials.get(key) |
|||
if (material) { |
|||
material.dispose() |
|||
this.materials.delete(key) |
|||
} |
|||
} |
|||
|
|||
disposeTexture(key: string) { |
|||
const texture = this.textures.get(key) |
|||
if (texture) { |
|||
texture.dispose() |
|||
this.textures.delete(key) |
|||
} |
|||
} |
|||
|
|||
disposeMesh(key: string) { |
|||
const mesh = this.meshes.get(key) |
|||
if (mesh) { |
|||
if (mesh.geometry) { |
|||
mesh.geometry.dispose() |
|||
} |
|||
if (mesh.material) { |
|||
if (Array.isArray(mesh.material)) { |
|||
mesh.material.forEach(mat => mat.dispose()) |
|||
} else { |
|||
mesh.material.dispose() |
|||
} |
|||
} |
|||
this.meshes.delete(key) |
|||
} |
|||
} |
|||
|
|||
dispose(): void { |
|||
// 清理资源
|
|||
this.geometries.forEach(geometry => geometry.dispose()) |
|||
this.materials.forEach(material => material.dispose()) |
|||
this.textures.forEach(texture => texture.dispose()) |
|||
this.meshes.forEach(mesh => { |
|||
if (mesh.geometry) { |
|||
mesh.geometry.dispose() |
|||
} |
|||
if (mesh.material) { |
|||
if (Array.isArray(mesh.material)) { |
|||
mesh.material.forEach(mat => mat.dispose()) |
|||
} else { |
|||
mesh.material.dispose() |
|||
} |
|||
} |
|||
}) |
|||
|
|||
this.geometries.clear() |
|||
this.materials.clear() |
|||
this.textures.clear() |
|||
this.meshes.clear() |
|||
|
|||
this.viewport = undefined |
|||
} |
|||
} |
|||
Loading…
Reference in new issue