6 changed files with 429 additions and 91 deletions
@ -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() { |
||||
|
} |
||||
|
} |
||||
@ -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,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 |
||||
|
} |
||||
|
} |
||||
@ -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