Browse Source

统一标签管理器 LabelManager

master
修宁 6 months ago
parent
commit
27dcba2dff
  1. 6
      src/core/engine/Viewport.ts
  2. 30
      src/core/manager/InstancePointManager.ts
  3. 207
      src/core/manager/LabelManager.ts
  4. 16
      src/core/manager/LineSegmentManager.ts
  5. 120
      src/core/manager/ResourceManager.ts
  6. 141
      src/modules/measure/MeasureRenderer.ts

6
src/core/engine/Viewport.ts

@ -22,6 +22,8 @@ import EventBus from '@/runtime/EventBus.ts'
import Constract from '@/core/Constract.ts' import Constract from '@/core/Constract.ts'
import DragControl from '@/core/controls/DragControl.ts' import DragControl from '@/core/controls/DragControl.ts'
import type { PropertySetter } from '@/core/base/PropertyTypes.ts' import type { PropertySetter } from '@/core/base/PropertyTypes.ts'
import LabelManager from '@/core/manager/LabelManager.ts'
import ResourceManager from '@/core/manager/ResourceManager.ts'
/** /**
* *
@ -40,11 +42,13 @@ export default class Viewport {
selectInspect = new SelectInspect() selectInspect = new SelectInspect()
mouseMoveInspect = new MouseMoveInspect() mouseMoveInspect = new MouseMoveInspect()
dragControl = new DragControl() dragControl = new DragControl()
labelManager = new LabelManager()
tools: IControls[] = [ tools: IControls[] = [
markRaw(this.selectInspect), markRaw(this.selectInspect),
markRaw(this.mouseMoveInspect), markRaw(this.mouseMoveInspect),
markRaw(this.dragControl) markRaw(this.dragControl),
markRaw(this.labelManager)
] ]
// 状态管理器 // 状态管理器

30
src/core/manager/InstancePointManager.ts

@ -0,0 +1,30 @@
import * as THREE from 'three'
import type IControls from '@/core/controls/IControls.ts'
import type Viewport from '@/core/engine/Viewport.ts'
export default class InstancePointManager implements IControls {
viewport: Viewport
mesh: THREE.InstancedMesh
dummy: THREE.Object3D = new THREE.Object3D()
count: number = 0
maxInstances: number = 100000
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)
}
dispose() {
}
}

207
src/core/manager/LabelManager.ts

@ -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
// }
// }
}

16
src/core/manager/LineSegmentManager.ts

@ -0,0 +1,16 @@
import * as THREE from 'three'
import type IControls from '@/core/controls/IControls.ts'
import type Viewport from '@/core/engine/Viewport.ts'
export default class LineSegmentManager implements IControls {
viewport: Viewport
init(viewport: Viewport): void {
this.viewport = viewport
}
dispose(): void {
// 清理资源
this.viewport = undefined
}
}

120
src/core/manager/ResourceManager.ts

@ -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
}
}

141
src/modules/measure/MeasureRenderer.ts

@ -4,10 +4,6 @@ import { getLineId } from '@/core/ModelUtils.ts'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
import { Line2 } from 'three/examples/jsm/lines/Line2.js' import { Line2 } from 'three/examples/jsm/lines/Line2.js'
import { numberToString } from '@/utils/webutils.ts'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Text } from 'troika-three-text'
import SimSunTTF from '@/assets/fonts/simsunb.ttf'
import Constract from '@/core/Constract.ts' import Constract from '@/core/Constract.ts'
/** /**
@ -114,92 +110,57 @@ export default class MeasureRenderer extends BaseRenderer {
// //
// this.group.add(...objects) // this.group.add(...objects)
// } // }
//
// afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: THREE.Object3D) { afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: THREE.Object3D) {
// super.afterCreateOrUpdateLine(start, end, type, option, object) super.afterCreateOrUpdateLine(start, end, type, option, object)
//
// const startPoint = this.tempViewport?.entityManager.findObjectById(start.id) const startPoint = this.tempViewport?.entityManager.findObjectById(start.id)
// const endPoint = this.tempViewport?.entityManager.findObjectById(end.id) const endPoint = this.tempViewport?.entityManager.findObjectById(end.id)
//
// const p0 = startPoint.position this.tempViewport.labelManager.createOrUpdateLabelByDistance(object, startPoint.position, endPoint.position, {
// const p1 = endPoint.position useHtmlLabel: this.useHtmlLabel,
// fontSize: 0.4,
// const dist = p0.distanceTo(p1) color: '#333333'
// const label = numberToString(dist) + ' m' })
//
// const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5) // const p0 = startPoint.position
// let labelObj: Text | CSS2DObject | undefined = object.userData.labelObj // const p1 = endPoint.position
// if (!labelObj || !labelObj.parent) { //
// labelObj = this.createLabel(label) // const dist = p0.distanceTo(p1)
// this.group.add(labelObj) // const label = numberToString(dist) + ' m'
// object.userData.labelObj = labelObj //
// } // const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5)
// // let labelObj: Text | CSS2DObject | undefined = object.userData.labelObj
// labelObj.position.set(position.x, position.y + 0.3, position.z - 0.2) // if (!labelObj || !labelObj.parent) {
// // labelObj = this.createLabel(label)
// if (this.useHtmlLabel) { // this.appendToScene(labelObj)
// labelObj.element.innerHTML = label // object.userData.labelObj = labelObj
// // }
// } else { //
// // 让文本朝向摄像机 // labelObj.position.set(position.x, position.y + 0.3, position.z - 0.2)
// labelObj.quaternion.copy(this.tempViewport.camera.quaternion) //
// labelObj.text = label // if (this.useHtmlLabel) {
// labelObj.sync() // labelObj.element.innerHTML = label
// } //
// } // } else {
// // // 让文本朝向摄像机
// afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: THREE.Object3D) { // labelObj.quaternion.copy(this.tempViewport.camera.quaternion)
// super.afterDeleteLine(start, end, type, option, object) // labelObj.text = label
// // labelObj.sync()
// // 删除标签 // }
// const labelObj = object.userData?.labelObj }
// if (labelObj && labelObj.parent) {
// labelObj.parent.remove(labelObj) afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, object: THREE.Object3D) {
// } super.afterDeleteLine(start, end, type, option, object)
// }
// this.tempViewport.labelManager.removeLabel(object)
// /** // 删除标签
// * 创建标签 // const labelObj = object.userData?.labelObj
// */ // if (labelObj && labelObj.parent) {
// createLabel(text: string): Text | CSS2DObject { // labelObj.parent.remove(labelObj)
// 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
// }
// }
dispose() { dispose() {
super.dispose() super.dispose()

Loading…
Cancel
Save