|
|
|
@ -1,11 +1,7 @@ |
|
|
|
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 { getUnitString, numberToString } from '@/utils/webutils' |
|
|
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' |
|
|
|
import { Vector3 } from 'three' |
|
|
|
import Constract from '@/designer/Constract.ts' |
|
|
|
import type { ICursorTool } from '@/designer/model2DEditor/tools/CursorTool.ts' |
|
|
|
|
|
|
|
@ -20,29 +16,66 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
static LINE_NAME = 'measure_line' |
|
|
|
static MAX_DISTANCE = 500 //当相交物体的距离太远时,忽略它
|
|
|
|
|
|
|
|
/** |
|
|
|
* 测量工具所在的视图窗口,从这里可以取到 所有 Three.js 相关的对象. |
|
|
|
* 比如: |
|
|
|
* - viewport.scene 场景 |
|
|
|
* - viewport.renderer 渲染器 |
|
|
|
* - viewport.controls 控制器 |
|
|
|
* - viewport.camera 摄像机 |
|
|
|
* - viewport.raycaster 射线投射器 |
|
|
|
* - viewport.dragControl 拖拽控制器 |
|
|
|
* - viewport.measure 测量工具 |
|
|
|
*/ |
|
|
|
viewport: Viewport |
|
|
|
|
|
|
|
// 当前测绘内容组
|
|
|
|
/** |
|
|
|
* 当前测绘内容组, 所有测量点、线、标签都在这个组中. 但不包括临时点、线 |
|
|
|
*/ |
|
|
|
group: THREE.Group |
|
|
|
/** |
|
|
|
* 是否完成测量 |
|
|
|
*/ |
|
|
|
isCompleted = false |
|
|
|
/** |
|
|
|
* 是否鼠标移动事件 |
|
|
|
*/ |
|
|
|
mouseMoved = false |
|
|
|
|
|
|
|
/** |
|
|
|
* 当前鼠标所在的画布, 对应 viewport.renderer.domElement |
|
|
|
*/ |
|
|
|
canvas: HTMLCanvasElement |
|
|
|
|
|
|
|
// 用于存储临时点
|
|
|
|
protected tempPointMarker?: THREE.Mesh |
|
|
|
/** |
|
|
|
* 用于存储临时点 |
|
|
|
*/ |
|
|
|
tempPointMarker?: THREE.Mesh |
|
|
|
|
|
|
|
// 用于存储临时线条,用于在鼠标移动时绘制线条/区域/角度
|
|
|
|
protected tempLine?: THREE.Line |
|
|
|
/** |
|
|
|
* 临时线条 |
|
|
|
*/ |
|
|
|
tempLine?: THREE.Line |
|
|
|
|
|
|
|
// 用于在鼠标移动时存储临时标签,只有测量距离时才有
|
|
|
|
protected tempLabel?: CSS2DObject |
|
|
|
/** |
|
|
|
* 临时标签对象, 用于在鼠标移动时显示距离 |
|
|
|
*/ |
|
|
|
tempLabel?: CSS2DObject |
|
|
|
|
|
|
|
// 当次绘制点
|
|
|
|
protected pointArray: THREE.Vector3[] = [] |
|
|
|
/** |
|
|
|
* 存储当前测量的点数组, 用于记录所有测量点 |
|
|
|
*/ |
|
|
|
pointArray: THREE.Vector3[] = [] |
|
|
|
|
|
|
|
//保存上次点击时间,以便检测双击事件
|
|
|
|
protected lastClickTime: number = 0 |
|
|
|
/** |
|
|
|
* 保存上次点击时间,以便检测双击事件 |
|
|
|
* @protected |
|
|
|
*/ |
|
|
|
lastClickTime: number = 0 |
|
|
|
|
|
|
|
/** |
|
|
|
* 测量线材质 |
|
|
|
*/ |
|
|
|
static LINE_MATERIAL = new THREE.LineBasicMaterial({ |
|
|
|
color: 0xE63C17, |
|
|
|
linewidth: 2, |
|
|
|
@ -53,20 +86,26 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
depthTest: false |
|
|
|
}) |
|
|
|
|
|
|
|
mode: CursorMode |
|
|
|
|
|
|
|
/** |
|
|
|
* 测量工具初始化 |
|
|
|
*/ |
|
|
|
init(viewport: Viewport) { |
|
|
|
this.viewport = viewport |
|
|
|
const viewerDom = this.viewport.viewerDom |
|
|
|
this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement |
|
|
|
|
|
|
|
// 初始化group
|
|
|
|
this.group = new THREE.Group() |
|
|
|
this.group.name = `${MeasureTool.OBJ_NAME}_group` |
|
|
|
this.group.userData = { |
|
|
|
mode: this.viewport.state.cursorMode |
|
|
|
mode: Constract.CursorModeMeasure |
|
|
|
} |
|
|
|
this.viewport.scene.add(this.group) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 测量工具开始, 监听鼠标事件, 变量初始化等 |
|
|
|
*/ |
|
|
|
start() { |
|
|
|
pdFn = this.mousedown.bind(this) |
|
|
|
this.canvas.addEventListener('pointerdown', pdFn) |
|
|
|
@ -79,9 +118,14 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
this.viewport.viewerDom.style.cursor = 'crosshair' |
|
|
|
this.pointArray = [] |
|
|
|
|
|
|
|
this.mode = this.viewport.state.cursorMode |
|
|
|
|
|
|
|
system.msg('进入测量模式') |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 停止测量模式, 清除所有临时点、线、标签. 停止所有鼠标事件监听 |
|
|
|
*/ |
|
|
|
stop(): void { |
|
|
|
system.msg('退出测量模式') |
|
|
|
|
|
|
|
@ -106,14 +150,22 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
this.tempLabel = undefined |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 销毁测量工具, 当视图窗口被销毁时调用 |
|
|
|
*/ |
|
|
|
destory() { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 鼠标按下事件 |
|
|
|
*/ |
|
|
|
mousedown() { |
|
|
|
this.mouseMoved = false |
|
|
|
} |
|
|
|
|
|
|
|
// 鼠标移动,创建对应的临时点与线
|
|
|
|
/** |
|
|
|
* 鼠标移动,创建对应的临时点与线 |
|
|
|
*/ |
|
|
|
mousemove(e: MouseEvent) { |
|
|
|
if (this.isCompleted) return |
|
|
|
|
|
|
|
@ -146,15 +198,15 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
const geom = line.geometry |
|
|
|
const startPoint = this.pointArray[0] |
|
|
|
const lastPoint = this.pointArray[this.pointArray.length - 1] |
|
|
|
if (this.viewport.state.cursorMode === Constract.CursorModeMeasureArea) { |
|
|
|
if (this.mode === Constract.CursorModeMeasureArea) { |
|
|
|
geom.setFromPoints([lastPoint, point, startPoint]) |
|
|
|
} else { |
|
|
|
geom.setFromPoints([lastPoint, point]) |
|
|
|
} |
|
|
|
|
|
|
|
if (this.viewport.state.cursorMode === Constract.CursorModeMeasure) { |
|
|
|
if (this.mode === Constract.CursorModeMeasure) { |
|
|
|
const dist = p0.distanceTo(point) |
|
|
|
const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}` |
|
|
|
const label = `${numberToString(dist)} ${getUnitString(this.mode)}` |
|
|
|
const position = new THREE.Vector3((point.x + p0.x) / 2, (point.y + p0.y) / 2, (point.z + p0.z) / 2) |
|
|
|
this.addOrUpdateTempLabel(label, position) |
|
|
|
} |
|
|
|
@ -163,6 +215,9 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
// this.viewport.dispatchSignal('sceneGraphChanged')
|
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 鼠标松开事件 |
|
|
|
*/ |
|
|
|
mouseup(e: MouseEvent) { |
|
|
|
// 如果mouseMoved是true,那么它可能在移动,而不是点击
|
|
|
|
if (!this.mouseMoved) { |
|
|
|
@ -179,7 +234,7 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
onMouseClicked(e: MouseEvent) { |
|
|
|
private onMouseClicked(e: MouseEvent) { |
|
|
|
if (this.isCompleted) { |
|
|
|
return |
|
|
|
} |
|
|
|
@ -214,11 +269,13 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
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.tempLabel.userData.pointIndices = [count - 2, count - 1] |
|
|
|
this.group.add(this.tempLabel) |
|
|
|
this.tempLabel = undefined |
|
|
|
|
|
|
|
// 临时线
|
|
|
|
this.tempLine.geometry.setFromPoints([p0, point]) |
|
|
|
this.tempLine.userData.pointIndices = [count - 2, count - 1] |
|
|
|
this.group.add(this.tempLine) |
|
|
|
this.tempLine = undefined |
|
|
|
} |
|
|
|
@ -227,7 +284,7 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Creates THREE.Line |
|
|
|
* 创建测量线 |
|
|
|
*/ |
|
|
|
private createLine(): THREE.Line { |
|
|
|
const geom = new THREE.BufferGeometry() |
|
|
|
@ -258,7 +315,7 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
obj.name = MeasureTool.OBJ_NAME |
|
|
|
|
|
|
|
obj.userData = { |
|
|
|
mode: this.viewport.state.cursorMode, |
|
|
|
mode: this.mode, |
|
|
|
type: Constract.MeasureMarker |
|
|
|
} |
|
|
|
return obj |
|
|
|
@ -278,7 +335,6 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
this.tempLabel.element.innerHTML = label |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* 创建标签 |
|
|
|
*/ |
|
|
|
@ -303,37 +359,60 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
return obj |
|
|
|
} |
|
|
|
|
|
|
|
private draggingIndex: number = -1 |
|
|
|
private connectedLines: THREE.Line[] = [] |
|
|
|
private connectedLabels: CSS2DObject[] = [] |
|
|
|
private originalPosition: THREE.Vector3 = new THREE.Vector3() |
|
|
|
|
|
|
|
/** |
|
|
|
* 拖拽测量点, 重绘 |
|
|
|
* 测量点被 DragControls 拖拽时触发这个方法 |
|
|
|
* description: |
|
|
|
* 这个方法会在拖拽开始时被调用,记录当前点的索引和位置,并创建临时线条用于连接前后点。 |
|
|
|
* 需要找到与这个点相关的所有测量点、线、标签,并将它们添加到临时数组中。 |
|
|
|
* 在拖拽过程中,监听鼠标move方法,更新临时点的位置,并根据当前点的索引更新临时线条的几何体、更新标签位置、重新计算长度等。 |
|
|
|
* @param point 拖拽的点. |
|
|
|
*/ |
|
|
|
redraw(point: THREE.Mesh) { |
|
|
|
// 当次绘制点
|
|
|
|
this.pointArray = []; |
|
|
|
|
|
|
|
(point.parent as THREE.Group).children.forEach(child => { |
|
|
|
switch (child.userData.type) { |
|
|
|
case Constract.MeasureMarker: |
|
|
|
// 当前点正在操作,不加入
|
|
|
|
if (child.uuid !== point.uuid) { |
|
|
|
this.pointArray[child.userData.pointIndex] = child.userData.point |
|
|
|
} else { |
|
|
|
this.tempPointMarker = child as THREE.Mesh |
|
|
|
dragPointStart(point: THREE.Mesh) { |
|
|
|
this.group = point.parent as THREE.Group |
|
|
|
this.isCompleted = false |
|
|
|
|
|
|
|
// 获取当前拖拽点的索引
|
|
|
|
this.draggingIndex = point.userData.pointIndex |
|
|
|
this.originalPosition.copy(point.position) |
|
|
|
|
|
|
|
// 收集相关联的线段和标签
|
|
|
|
this.connectedLines = [] |
|
|
|
this.connectedLabels = [] |
|
|
|
|
|
|
|
// 遍历group查找相关线段和标签
|
|
|
|
this.group.children.forEach(child => { |
|
|
|
// 处理线段
|
|
|
|
if (child.name === MeasureTool.LINE_NAME && child.userData.pointIndices) { |
|
|
|
const [startIdx, endIdx] = child.userData.pointIndices |
|
|
|
if (startIdx === this.draggingIndex || endIdx === this.draggingIndex) { |
|
|
|
this.connectedLines.push(child as THREE.Line) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 处理标签
|
|
|
|
if (child.name === MeasureTool.LABEL_NAME && child.userData.pointIndices) { |
|
|
|
const [startIdx, endIdx] = child.userData.pointIndices |
|
|
|
if (startIdx === this.draggingIndex || endIdx === this.draggingIndex) { |
|
|
|
this.connectedLabels.push(child as CSS2DObject) |
|
|
|
} |
|
|
|
break |
|
|
|
case Constract.MeasureLine: |
|
|
|
this.tempLine = this.createLine() |
|
|
|
this.group.add(this.tempLine) |
|
|
|
break |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
// 重写move事件
|
|
|
|
// 监听move事件
|
|
|
|
pmFn = this.redrawMousemove.bind(this) |
|
|
|
this.canvas.addEventListener('pointermove', pmFn) |
|
|
|
|
|
|
|
this.group = point.parent as THREE.Group |
|
|
|
this.isCompleted = false |
|
|
|
console.log('connectedLines:', this.connectedLines) |
|
|
|
console.log('connectedLabels:', this.connectedLabels) |
|
|
|
|
|
|
|
// 这个方法会在拖拽开始时被调用,记录当前点的索引和位置,
|
|
|
|
// 需要找到与这个点相关的所有测量点、线、标签,并将它们添加到临时数组中。
|
|
|
|
// 并创建临时线条用于连接前后点。
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -341,77 +420,61 @@ export default class MeasureTool implements ICursorTool { |
|
|
|
* 重绘监听鼠标移动 |
|
|
|
*/ |
|
|
|
redrawMousemove(e: MouseEvent) { |
|
|
|
let point = this.viewport.getClosestIntersection(e) |
|
|
|
|
|
|
|
if (!point && this.tempPointMarker) { |
|
|
|
this.tempPointMarker.position.set(this.tempPointMarker.userData.point.x, this.tempPointMarker.userData.point.y, this.tempPointMarker.userData.point.z) |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
if (!point || !this.tempPointMarker) return |
|
|
|
|
|
|
|
// 在鼠标移动时绘制临时点
|
|
|
|
this.tempPointMarker.position.set(point.x, point.y, point.z) |
|
|
|
this.tempPointMarker.userData.point = point |
|
|
|
|
|
|
|
// 当前点的索引
|
|
|
|
const cIndex = this.tempPointMarker.userData.pointIndex |
|
|
|
if (this.draggingIndex === -1) return |
|
|
|
|
|
|
|
let newPosition = this.viewport.getClosestIntersection(e) |
|
|
|
// 更新点数组中的坐标
|
|
|
|
this.pointArray[this.draggingIndex].copy(newPosition) |
|
|
|
|
|
|
|
// 更新所有相关线段
|
|
|
|
this.connectedLines.forEach(line => { |
|
|
|
const [startIdx, endIdx] = line.userData.pointIndices |
|
|
|
const startPoint = this.pointArray[startIdx] |
|
|
|
const endPoint = this.pointArray[endIdx] |
|
|
|
|
|
|
|
// 更新线段几何体
|
|
|
|
const geometry = line.geometry as THREE.BufferGeometry |
|
|
|
geometry.setFromPoints([startPoint, endPoint]) |
|
|
|
geometry.attributes.position.needsUpdate = true |
|
|
|
}) |
|
|
|
|
|
|
|
// 移动时绘制临时线
|
|
|
|
if (this.pointArray.length > 0) { |
|
|
|
const line = this.tempLine || this.createLine() |
|
|
|
const geom = line.geometry |
|
|
|
let startPoint = this.pointArray[cIndex + 1] |
|
|
|
let lastPoint = this.pointArray[cIndex - 1] |
|
|
|
// 更新所有相关标签
|
|
|
|
this.connectedLabels.forEach(label => { |
|
|
|
const [startIdx, endIdx] = label.userData.pointIndices |
|
|
|
const startPoint = this.pointArray[startIdx] |
|
|
|
const endPoint = this.pointArray[endIdx] |
|
|
|
|
|
|
|
// 计算中间位置和距离
|
|
|
|
const midPoint = new THREE.Vector3() |
|
|
|
.addVectors(startPoint, endPoint) |
|
|
|
.multiplyScalar(0.5) |
|
|
|
const distance = startPoint.distanceTo(endPoint) |
|
|
|
|
|
|
|
// 更新标签位置和内容
|
|
|
|
label.position.copy(midPoint) |
|
|
|
label.element.innerHTML = `${numberToString(distance)} ${getUnitString(this.mode)}` |
|
|
|
}) |
|
|
|
|
|
|
|
if (startPoint && lastPoint) { |
|
|
|
geom.setFromPoints([lastPoint, point, startPoint]) |
|
|
|
} else { |
|
|
|
geom.setFromPoints([startPoint || lastPoint, point]) |
|
|
|
} |
|
|
|
// 更新点的可视化位置
|
|
|
|
const pointObj = this.group.children.find(child => |
|
|
|
child.userData.pointIndex === this.draggingIndex |
|
|
|
) as THREE.Mesh |
|
|
|
if (pointObj) { |
|
|
|
pointObj.position.copy(newPosition) |
|
|
|
} |
|
|
|
// 在拖拽过程中,监听鼠标move方法
|
|
|
|
// 更新临时点的位置,并根据当前点的索引更新临时线条的几何体、重新计算长度、更新标签位置等
|
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 重绘完成 |
|
|
|
* 测量点被 DragControls 拖拽完成后触发这个方法 |
|
|
|
*/ |
|
|
|
redrawComplete() { |
|
|
|
if (!this.tempPointMarker) return |
|
|
|
|
|
|
|
const point = this.tempPointMarker.userData.point |
|
|
|
this.pointArray[this.tempPointMarker.userData.pointIndex] = point |
|
|
|
const count = this.pointArray.length |
|
|
|
dragPointComplete() { |
|
|
|
|
|
|
|
// 如果是距离测量,则清除group中已有的label,再重新创建
|
|
|
|
if (this.viewport.state.cursorMode === 'Measure' && 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) |
|
|
|
} |
|
|
|
} |
|
|
|
// 将临时点、线、标签添加到 group 中, 变成正式的测量
|
|
|
|
|
|
|
|
// 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) |
|
|
|
} |
|
|
|
} |
|
|
|
// 删除鼠标事件监听
|
|
|
|
this.canvas.removeEventListener('pointermove', pmFn) |
|
|
|
pmFn = undefined |
|
|
|
} |
|
|
|
} |