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