import * as THREE from 'three' import BaseRenderer from '@/core/base/BaseRenderer.ts' import { getLineId } from '@/core/ModelUtils.ts' 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 { 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' /** * 辅助测量工具渲染器 */ export default class MeasureRenderer extends BaseRenderer { /** * 当前测绘内容组, 所有测量点、线、标签都在这个组中. 但不包括临时点、线 */ group: THREE.Group static GROUP_NAME = 'measure_group' static LABEL_NAME = 'measure_label' static POINT_NAME = 'measure_point' static LINE_NAME = 'measure_line' public useHtmlLabel = false pointMaterial: THREE.Material lineMaterial: LineMaterial readonly defulePositionY = 0.01 readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.1, 0.1, 0.1) readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(90, 0, 0) constructor(itemTypeName: string) { super(itemTypeName) } 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() ]) } /** * 所有的点,必须使用同一个尺寸, 改属性也无效 */ override afterCreateOrUpdatePoint(item: ItemJson, option: RendererCudOption, objects: THREE.Object3D[]) { super.afterCreateOrUpdatePoint(item, option, objects) const point = objects[0] point.scale.set(this.defaultScale.x, this.defaultScale.y, this.defaultScale.z) point.rotation.set( THREE.MathUtils.degToRad(this.defaultRotation.x), THREE.MathUtils.degToRad(this.defaultRotation.y), THREE.MathUtils.degToRad(this.defaultRotation.z) ) } createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { const geom = new LineGeometry() const obj = new Line2(geom, this.lineMaterial) obj.frustumCulled = false obj.name = MeasureRenderer.LINE_NAME obj.uuid = getLineId(start.id, end.id, type) return [obj] } createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { // const tt = new THREE.BoxGeometry(1, 1, 1) // const obj = new THREE.Mesh(tt, this.movelinePoint) // obj.name = MeasureRenderer.POINT_NAME // obj.uuid = item.id // // return [obj] // 创建平面几何体 const obj = new THREE.Sprite(this.pointMaterial as THREE.SpriteMaterial) obj.name = MeasureRenderer.POINT_NAME return [obj] } appendToScene(...objects: THREE.Object3D[]) { if (!this.group || this.group.parent !== this.tempViewport.scene.scene) { if (this.group && this.group.parent !== this.tempViewport.scene.scene) { // 幻影加载问题 this.group.parent.removeFromParent() } this.group = new THREE.Group() this.group.name = MeasureRenderer.GROUP_NAME this.tempViewport?.scene.add(this.group) } const dragObjects = objects.filter(obj => !!obj.userData.draggable) this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') this.group.add(...objects) } afterCreateOrUpdateLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, objects: THREE.Object3D[]) { super.afterCreateOrUpdateLine(start, end, type, option, objects) const startPoint = this.tempViewport?.entityManager.findObjectsById(start.id)?.[0] const endPoint = this.tempViewport?.entityManager.findObjectsById(end.id)?.[0] const p0 = startPoint.position const p1 = endPoint.position const dist = p0.distanceTo(p1) const label = numberToString(dist) + ' m' const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5) let labelObj: Text | CSS2DObject | undefined = objects[0].userData.labelObj if (!labelObj || !labelObj.parent) { labelObj = this.createLabel(label) this.group.add(labelObj) objects[0].userData.labelObj = labelObj } labelObj.position.set(position.x, position.y + 0.3, position.z - 0.2) if (this.useHtmlLabel) { labelObj.element.innerHTML = label } else { // 让文本朝向摄像机 labelObj.quaternion.copy(this.tempViewport.camera.quaternion) labelObj.text = label labelObj.sync() } } afterDeleteLine(start: ItemJson, end: ItemJson, type: LinkType, option: RendererCudOption, objects: THREE.Object3D[]) { super.afterDeleteLine(start, end, type, option, objects) // 删除标签 const labelObj = objects[0]?.userData?.labelObj if (labelObj && labelObj.parent) { labelObj.parent.remove(labelObj) } } /** * 创建标签 */ 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 } } dispose() { super.dispose() if (this.group && this.group.parent) { this.group.parent.remove(this.group) } this.group = undefined this.pointMaterial.dispose() this.lineMaterial.dispose() } }