18 changed files with 386 additions and 93 deletions
|
After Width: | Height: | Size: 261 B |
@ -0,0 +1,5 @@ |
|||||
|
import BaseEntity from '@/core/base/BaseItemEntity.ts' |
||||
|
|
||||
|
export default class WayEntity extends BaseEntity { |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
import BaseInteraction from '@/core/base/BaseInteraction.ts' |
||||
|
|
||||
|
export default class WayInteraction extends BaseInteraction { |
||||
|
|
||||
|
constructor(itemTypeName: string) { |
||||
|
super(itemTypeName) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import type { IMeta } from '@/core/base/IMeta.ts' |
||||
|
|
||||
|
export default [ |
||||
|
{ field: 'uuid', editor: 'UUID', label: 'uuid', readonly: true, category: 'basic' }, |
||||
|
{ field: 'name', editor: 'TextInput', label: '名称', category: 'basic' }, |
||||
|
{ field: 'dt.label', editor: 'TextInput', label: '标签', category: 'basic' }, |
||||
|
{ editor: 'TransformEditor', category: 'basic' }, |
||||
|
{ field: 'dt.color', editor: 'Color', label: '颜色', category: 'basic' }, |
||||
|
{ editor: '-', category: 'basic' }, |
||||
|
{ field: 'tf', editor: 'InOutCenterEditor', category: 'basic' }, |
||||
|
{ field: 'dt.selectable', editor: 'Switch', label: '可选中', category: 'basic' }, |
||||
|
{ field: 'dt.protected', editor: 'Switch', label: '受保护', category: 'basic' }, |
||||
|
{ field: 'visible', editor: 'Switch', label: '可见', category: 'basic' } |
||||
|
] as IMeta |
||||
@ -0,0 +1,199 @@ |
|||||
|
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' |
||||
|
import MoveLinePointPng from '@/assets/images/moveline_point.png' |
||||
|
|
||||
|
/** |
||||
|
* 辅助测量工具渲染器 |
||||
|
*/ |
||||
|
export default class WayRenderer extends BaseRenderer { |
||||
|
static LABEL_NAME = 'way_label' |
||||
|
static POINT_NAME = 'way_point' |
||||
|
static LINE_NAME = 'way_line' |
||||
|
|
||||
|
public useHtmlLabel = false |
||||
|
|
||||
|
pointMaterial: THREE.Material |
||||
|
lineMaterial: LineMaterial |
||||
|
|
||||
|
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.5, 0.5, 0.1) |
||||
|
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(90, 0, 0) |
||||
|
|
||||
|
constructor(itemTypeName: string) { |
||||
|
super(itemTypeName) |
||||
|
} |
||||
|
|
||||
|
async init() { |
||||
|
this.lineMaterial = new LineMaterial({ |
||||
|
color: 0xE63C17, // 主颜色
|
||||
|
linewidth: 2, // 实际可用的线宽
|
||||
|
vertexColors: true, // 启用顶点颜色
|
||||
|
dashed: false, |
||||
|
alphaToCoverage: true |
||||
|
}) |
||||
|
return Promise.all([ |
||||
|
super.init(), |
||||
|
this.loadFont() |
||||
|
]) |
||||
|
} |
||||
|
|
||||
|
async loadFont() { |
||||
|
return new Promise<void>((resolve, reject) => { |
||||
|
new THREE.TextureLoader().load( |
||||
|
MoveLinePointPng, |
||||
|
(texture) => { |
||||
|
this.pointMaterial = new THREE.SpriteMaterial({ |
||||
|
map: texture, |
||||
|
transparent: true, |
||||
|
side: THREE.DoubleSide |
||||
|
}) |
||||
|
resolve() |
||||
|
}, |
||||
|
undefined, |
||||
|
function(err) { |
||||
|
reject(err) |
||||
|
} |
||||
|
) |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 所有的点,必须使用同一个尺寸, 改属性也无效 |
||||
|
*/ |
||||
|
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 = WayRenderer.LINE_NAME |
||||
|
obj.uuid = getLineId(start.id, end.id, type) |
||||
|
|
||||
|
return [obj] |
||||
|
} |
||||
|
|
||||
|
createPointBasic(item: ItemJson, option?: RendererCudOption): THREE.Object3D[] { |
||||
|
const obj = new THREE.Sprite(this.pointMaterial as THREE.SpriteMaterial) |
||||
|
obj.name = WayRenderer.POINT_NAME |
||||
|
return [obj] |
||||
|
} |
||||
|
|
||||
|
appendToScene(...objects: THREE.Object3D[]) { |
||||
|
const dragObjects = objects.filter(obj => !!obj.userData.draggable) |
||||
|
this.tempViewport.dragControl.setDragObjects(dragObjects, 'push') |
||||
|
// this.tempViewport.dragControl.setDragObjects(objects, 'remove')
|
||||
|
|
||||
|
this.tempViewport?.scene.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.appendToScene(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 = WayRenderer.LABEL_NAME |
||||
|
return obj |
||||
|
|
||||
|
} else { |
||||
|
const label = new Text() |
||||
|
label.text = text |
||||
|
label.font = SimSunTTF |
||||
|
label.fontSize = 0.4 |
||||
|
label.color = '#ff0000' |
||||
|
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 = WayRenderer.LABEL_NAME |
||||
|
|
||||
|
label.sync() |
||||
|
return label |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
dispose() { |
||||
|
super.dispose() |
||||
|
this.pointMaterial.dispose() |
||||
|
this.lineMaterial.dispose() |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
import { defineModule } from '@/core/manager/ModuleManager.ts' |
||||
|
import WayRenderer from './WayRenderer.ts' |
||||
|
import WayEntity from './WayEntity.ts' |
||||
|
import WayMeta from './WayMeta.ts' |
||||
|
import WayInteraction from './WayInteraction.ts' |
||||
|
|
||||
|
export const ITEM_TYPE_NAME = 'way' |
||||
|
|
||||
|
export default defineModule({ |
||||
|
name: ITEM_TYPE_NAME, |
||||
|
renderer: new WayRenderer(ITEM_TYPE_NAME), |
||||
|
interaction: new WayInteraction(ITEM_TYPE_NAME), |
||||
|
meta: WayMeta, |
||||
|
entity: WayEntity |
||||
|
}) |
||||
Loading…
Reference in new issue