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
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)
|
|
}
|
|
}
|
|
}
|
|
}
|