You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

359 lines
10 KiB

import _ from 'lodash'
import PointPng from '@/assets/images/logo.png'
import * as THREE from 'three'
import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts'
import Viewport from '@/designer/Viewport.ts'
import { numberToString, getUnitString } from '@/utils/webutils'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Vector3 } from 'three'
let pdFn, pmFn, puFn, kdFn
/**
* 用于在 threejs 中创建一系列的 object_for_measure 点,并标记他的距离
*/
export default class RulerTool implements ITool {
static OBJ_NAME = 'object_for_measure'
static LABEL_NAME = 'label_for_measure'
static MAX_DISTANCE = 500 //当相交物体的距离太远时,忽略它
viewport: Viewport
// 当前测绘内容组
group: THREE.Group
isCompleted = false
mouseMoved = false
canvas: HTMLCanvasElement
// 用户在测量时绘制的线的当前实例
protected polyline?: THREE.Line
// 用于存储临时点
protected tempPointMarker?: THREE.Mesh
// 用于存储临时线条,用于在鼠标移动时绘制线条/区域/角度
protected tempLine?: THREE.Line
// 用于在鼠标移动时存储临时标签,只有测量距离时才有
protected tempLabel?: CSS2DObject
// 存储点
protected pointArray: THREE.Vector3[] = []
//保存上次点击时间,以便检测双击事件
protected lastClickTime: number = 0
static LINE_MATERIAL = new THREE.LineBasicMaterial({
color: 0xE63C17,
linewidth: 2,
opacity: 0.9,
transparent: true,
side: THREE.DoubleSide,
depthWrite: false,
depthTest: false
})
init(viewport: Viewport) {
this.viewport = viewport
const viewerDom = this.viewport.viewerDom
this.canvas = $(viewerDom).children('canvas')[0] as HTMLCanvasElement
pdFn = this.mousedown.bind(this)
this.canvas.addEventListener('pointerdown', pdFn)
pmFn = this.mousemove.bind(this)
this.canvas.addEventListener('pointermove', pmFn)
puFn = this.mouseup.bind(this)
this.canvas.addEventListener('pointerup', puFn)
// 初始化group
this.group = new THREE.Group()
this.group.name = `${RulerTool.OBJ_NAME}_group`
this.group.userData = {
mode: this.viewport.state.cursorMode
}
this.viewport.scene.add(this.group)
// 测量距离、面积和角度需要折线
this.polyline = this.createLine()
this.group.add(this.polyline)
this.isCompleted = false
this.viewport.viewerDom.style.cursor = 'crosshair'
// 当次绘制点
this.pointArray = []
system.msg('进入鼠标测距模式')
}
destory(): void {
system.msg('退出鼠标测距模式')
const viewerDom = this.viewport.viewerDom
this.isCompleted = true
viewerDom.style.cursor = ''
this.canvas.removeEventListener('pointerdown', pdFn)
pdFn = undefined
this.canvas.removeEventListener('pointermove', pmFn)
pmFn = undefined
this.canvas.removeEventListener('pointerup', puFn)
puFn = undefined
this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker)
this.tempLine && this.viewport.scene.remove(this.tempLine)
this.tempLabel && this.viewport.scene.remove(this.tempLabel)
this.tempPointMarker = undefined
this.tempLine = undefined
this.tempLabel = undefined
}
mousedown = () => {
this.mouseMoved = false
}
// 鼠标移动,创建对应的临时点与线
mousemove = (e: MouseEvent) => {
if (this.isCompleted) return
this.mouseMoved = true
const point = this.getClosestIntersection(e)
if (!point) {
return
}
// 在鼠标移动时绘制临时点
if (this.tempPointMarker) {
this.tempPointMarker.position.set(point.x, point.y, point.z)
} else {
this.tempPointMarker = this.createPointMarker(point)
this.viewport.scene.add(this.tempPointMarker)
}
// 移动时绘制临时线
if (this.pointArray.length > 0) {
const p0 = this.pointArray[this.pointArray.length - 1] // 获取最后一个点
const line = this.tempLine || this.createLine()
const geom = line.geometry
const startPoint = this.pointArray[0]
const lastPoint = this.pointArray[this.pointArray.length - 1]
if (this.viewport.state.cursorMode === 'RulerArea') {
geom.setFromPoints([lastPoint, point, startPoint])
} else {
geom.setFromPoints([lastPoint, point])
}
if (this.viewport.state.cursorMode === 'Ruler') {
const dist = p0.distanceTo(point)
const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}`
const position = new THREE.Vector3((point.x + p0.x) / 2, (point.y + p0.y) / 2, (point.z + p0.z) / 2)
this.addOrUpdateTempLabel(label, position)
}
// tempLine 只需添加到场景一次
if (!this.tempLine) {
this.viewport.scene.add(line)
this.tempLine = line
}
}
// this.viewport.dispatchSignal('sceneGraphChanged')
}
mouseup = (e: MouseEvent) => {
// 如果mouseMoved是true,那么它可能在移动,而不是点击
if (!this.mouseMoved) {
// 右键点击表示完成绘图操作
if (e.button === 2) {
this.viewport.state.cursorMode = 'normal'
} else if (e.button === 0) { // 左键点击表示添加点
this.onMouseClicked(e)
}
}
}
onMouseClicked = (e: MouseEvent) => {
if (this.isCompleted) {
return
}
const point = this.getClosestIntersection(e)
if (!point) {
return
}
// 双击触发两次点击事件,我们需要避免这里的第二次点击
const now = Date.now()
if (this.lastClickTime && (now - this.lastClickTime < 10)) return
this.lastClickTime = now
this.pointArray.push(point)
const count = this.pointArray.length
const marker = this.createPointMarker(point)
marker.userData.point = point
marker.userData.pointIndex = count - 1
this.group.add(marker)
// 把点加入拖拽控制器
this.viewport.dragControl.setDragObjects([marker], 'push')
if (this.polyline) {
this.polyline.geometry.setFromPoints(this.pointArray)
if (this.tempLabel && count > 1) {
const p0 = this.pointArray[count - 2]
this.tempLabel.position.set((p0.x + point.x) / 2, (p0.y + point.y) / 2, (p0.z + point.z) / 2)
this.group.add(this.tempLabel)
// 创建距离测量线时,此处的 临时label 将作为正式的使用,不在this.clearTemp()中清除,故置为undefined
this.tempLabel = undefined
}
}
// this.redrawComplete()
// this.viewport.dispatchSignal('sceneGraphChanged')
}
/**
* Creates THREE.Line
*/
private createLine(): THREE.Line {
const geom = new THREE.BufferGeometry()
const obj = new THREE.Line(geom, RulerTool.LINE_MATERIAL)
obj.frustumCulled = false
obj.name = RulerTool.OBJ_NAME
obj.userData = {
type: 'line'
}
return obj
}
/**
* 创建点标记
*/
createPointMarker(position?: THREE.Vector3): THREE.Mesh {
const p = position
const scale = 0.25
const tt = new THREE.BoxGeometry(1, 1, 1)
const t2 = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
const obj = new THREE.Mesh(tt, t2)
obj.scale.set(scale, 0.1, scale)
if (p) {
obj.position.set(p.x, p.y, p.z)
}
obj.name = RulerTool.OBJ_NAME
obj.userData = {
mode: this.viewport.state.cursorMode,
type: 'measure-marker'
}
return obj
}
/**
* 获取按下对应三维位置
*/
getClosestIntersection(e: MouseEvent) {
const _point = new THREE.Vector2()
_point.x = e.offsetX / this.viewport.renderer.domElement.offsetWidth
_point.y = e.offsetY / this.viewport.renderer.domElement.offsetHeight
const intersects = this.viewport.getIntersects(_point)
if (intersects && intersects.length > 2) {
if (intersects.length > 0 && intersects[0].distance < RulerTool.MAX_DISTANCE) {
return new Vector3(
intersects[0].point.x,
0.1,
intersects[1].point.z
)
}
}
return null
}
/**
* 添加或更新临时标签和位置
*/
addOrUpdateTempLabel(label: string, position: THREE.Vector3) {
if (!this.tempLabel) {
this.tempLabel = this.createLabel(label)
console.log('addOrUpdateTempLabel', label, position)
this.viewport.scene.add(this.tempLabel)
}
this.tempLabel.position.set(position.x, position.y, position.z)
this.tempLabel.element.innerHTML = label
}
/**
* 创建标签
*/
createLabel(text: string): CSS2DObject {
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 = RulerTool.LABEL_NAME
obj.userData = {
type: 'label'
}
return obj
}
// 重绘完成
redrawComplete() {
if (!this.tempPointMarker) return
const point = this.tempPointMarker.userData.point
this.pointArray[this.tempPointMarker.userData.pointIndex] = point
const count = this.pointArray.length
if (this.polyline) {
this.polyline.geometry.setFromPoints(this.pointArray)
// 如果是距离测量,则清除group中已有的label,再重新创建
if (this.viewport.state.cursorMode === 'Ruler' && count > 1) {
this.clearCurrentLabel()
// 绘制label
for (let i = 0; i < count - 1; i++) {
const p0 = this.pointArray[i]
const p1 = this.pointArray[i + 1]
if (!p0 || !p1) continue
const dist = p0.distanceTo(p1)
const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}`
const position = new THREE.Vector3((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, (p0.z + p1.z) / 2)
const labelObj = this.createLabel(label)
labelObj.position.set(position.x, position.y, position.z)
labelObj.element.innerHTML = label
this.group.add(labelObj)
}
}
}
// this.destory()
}
/**
* 清除当前group label
*/
clearCurrentLabel() {
for (let i = this.group.children.length - 1; i >= 0; i--) {
const c = this.group.children[i]
if (c.userData.type === 'label') {
this.group.remove(c)
}
}
}
}