diff --git a/src/designer/model2DEditor/EsDragControls.ts b/src/designer/model2DEditor/EsDragControls.ts index 2d9db26..70752be 100644 --- a/src/designer/model2DEditor/EsDragControls.ts +++ b/src/designer/model2DEditor/EsDragControls.ts @@ -79,7 +79,7 @@ export default class EsDragControls { switch (e.object.userData.type) { case Constract.MeasureMarker: - this.viewport.measure.redraw(e.object) + this.viewport.measure.dragPointStart(e.object) break } } @@ -109,8 +109,8 @@ export default class EsDragControls { } switch (e.object.userData.type) { - case 'measure-marker': - this.viewport.measure.redrawComplete() + case Constract.MeasureMarker: + this.viewport.measure.dragPointComplete() break } } diff --git a/src/designer/model2DEditor/tools/MeasureTool.ts b/src/designer/model2DEditor/tools/MeasureTool.ts index 5fdb472..ac15179 100644 --- a/src/designer/model2DEditor/tools/MeasureTool.ts +++ b/src/designer/model2DEditor/tools/MeasureTool.ts @@ -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 - } - break - case Constract.MeasureLine: - this.tempLine = this.createLine() - this.group.add(this.tempLine) - break + 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) + } } }) - // 重写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 + dragPointComplete() { - const point = this.tempPointMarker.userData.point - this.pointArray[this.tempPointMarker.userData.pointIndex] = point - const count = this.pointArray.length + // 将临时点、线、标签添加到 group 中, 变成正式的测量 - // 如果是距离测量,则清除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) - } - } - - // 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 } } \ No newline at end of file