diff --git a/src/designer/Constract.ts b/src/designer/Constract.ts new file mode 100644 index 0000000..444e7ac --- /dev/null +++ b/src/designer/Constract.ts @@ -0,0 +1,23 @@ +export default Object.freeze({ + // 光标相关 + CursorModeNormal: 'normal', + + // 关联相关 + CursorModeALink: 'ALink', + CursorModeSLink: 'SLink', + CursorModePointCallback: 'PointCallback', + CursorModePointAdd: 'PointAdd', + CursorModeLinkAdd: 'LinkAdd', + CursorModeLinkAdd2: 'LinkAdd2', + + // 测量相关的光标模式 + CursorModeMeasure: 'Measure', + CursorModeMeasureArea: 'MeasureArea', + CursorModeMeasureAngle: 'MeasureAngle', + MeasureMarker: 'measure-marker', + MeasureLine: 'measure-line', + MeasureLabel: 'measure-label', + + // 选择模式 + CursorModeSelectByRec: 'selectByRec' +}) \ No newline at end of file diff --git a/src/designer/Viewport.ts b/src/designer/Viewport.ts index eb2f360..6b40795 100644 --- a/src/designer/Viewport.ts +++ b/src/designer/Viewport.ts @@ -9,9 +9,10 @@ import $ from 'jquery' import { reactive, watch } from 'vue' import MouseMoveInspect from '@/designer/model2DEditor/tools/MouseMoveInspect.ts' import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts' -import RulerTool from '@/designer/model2DEditor/tools/RulerTool.ts' +import MeasureTool from '@/designer/model2DEditor/tools/MeasureTool.ts' import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer' import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer' +import type { ICursorTool } from '@/designer/model2DEditor/tools/CursorTool.ts' /** * 编辑器对象 @@ -29,9 +30,10 @@ export default class Viewport { worldModel: WorldModel raycaster: Raycaster dragControl: EsDragControls + measure: MeasureTool animationFrameId: any = null - currentTool: ITool | null = null + currentTool: ICursorTool | null = null tools: ITool[] = [ new MouseMoveInspect() ] @@ -80,7 +82,7 @@ export default class Viewport { * 初始化 THREE 渲染器 */ initThree(viewerDom: HTMLElement, floor: string) { - console.log('init floor', floor) + console.log('viewport on floor', floor) this.state.currentFloor = floor this.viewerDom = viewerDom const rect = viewerDom.getBoundingClientRect() @@ -105,7 +107,7 @@ export default class Viewport { renderer.clearDepth() renderer.shadowMap.enabled = true renderer.toneMapping = THREE.ACESFilmicToneMapping - renderer.setPixelRatio(Math.max(Math.ceil(window.devicePixelRatio), 1)); + renderer.setPixelRatio(Math.max(Math.ceil(window.devicePixelRatio), 1)) renderer.setViewport(0, 0, this.viewerDom.offsetWidth, this.viewerDom.offsetHeight) renderer.setSize(this.viewerDom.offsetWidth, this.viewerDom.offsetHeight) @@ -195,12 +197,12 @@ export default class Viewport { } this.updateGridVisibility() })) - this.watchList.push(watch(() => this.state.cursorMode, (newVal) => { + this.watchList.push(watch(() => this.state.cursorMode, (newVal: CursorMode) => { if (!this.state.isReady) { return } if (this.currentTool) { - this.currentTool.destory() + this.currentTool.stop() this.currentTool = null } if (newVal === 'normal' || !newVal) { @@ -208,9 +210,9 @@ export default class Viewport { return } - if (newVal === 'Ruler' || newVal === 'RulerArea' || newVal === 'RulerAngle') { + if (newVal === 'Measure' || newVal === 'MeasureArea' || newVal === 'MeasureAngle') { // 选择标尺工具 - this.currentTool = new RulerTool() + this.currentTool = this.measure this.dragControl.dragControls.enabled = false } else { @@ -218,7 +220,7 @@ export default class Viewport { } if (this.currentTool) { - this.currentTool.init(this) + this.currentTool.start() } })) @@ -239,6 +241,10 @@ export default class Viewport { // 注册拖拽组件 this.dragControl = new EsDragControls(this) + // 注册测量工具 + this.measure = new MeasureTool() + this.measure.init(this) + this.state.isReady = true } diff --git a/src/designer/model2DEditor/EsDragControls.ts b/src/designer/model2DEditor/EsDragControls.ts index cf0e12d..2d9db26 100644 --- a/src/designer/model2DEditor/EsDragControls.ts +++ b/src/designer/model2DEditor/EsDragControls.ts @@ -1,6 +1,7 @@ import { DragControls } from './DragControls.js' import * as THREE from 'three' import type Viewport from '@/designer/Viewport.ts' +import Constract from '@/designer/Constract.ts' // dragControls 绑定函数 let dragStartFn, dragFn, dragEndFn, clickblankFn @@ -76,11 +77,11 @@ export default class EsDragControls { // 记录拖拽按下的位置和对象 this.onDownPosition = { x: e.e.clientX, y: e.e.clientY } - // switch (e.object.userData.type) { - // case 'measure-marker': - // this.viewport.modules.measure.redraw(e.object) - // break - // } + switch (e.object.userData.type) { + case Constract.MeasureMarker: + this.viewport.measure.redraw(e.object) + break + } } // 拖拽中 @@ -107,11 +108,11 @@ export default class EsDragControls { } } - // switch (e.object.userData.type) { - // case 'measure-marker': - // this.viewport.modules.measure.redrawComplete() - // break - // } + switch (e.object.userData.type) { + case 'measure-marker': + this.viewport.measure.redrawComplete() + break + } } // 点击可拖拽物体之外 diff --git a/src/designer/model2DEditor/Model2DEditor.vue b/src/designer/model2DEditor/Model2DEditor.vue index f3e952e..c4c5999 100644 --- a/src/designer/model2DEditor/Model2DEditor.vue +++ b/src/designer/model2DEditor/Model2DEditor.vue @@ -15,28 +15,28 @@
+ :type="state.cursorMode===Constract.CursorModeNormal?'primary':''" + @click="()=>state.cursorMode = Constract.CursorModeNormal"> + :type="state.cursorMode===Constract.CursorModeSelectByRec?'primary':''" + @click="()=>state.cursorMode = Constract.CursorModeSelectByRec"> + :type="state.cursorMode===Constract.CursorModeALink?'primary':''" + @click="()=>state.cursorMode = Constract.CursorModeALink"> + :type="state.cursorMode===Constract.CursorModeSLink?'primary':''" + @click="()=>state.cursorMode = Constract.CursorModeSLink"> + :type="state.cursorMode===Constract.CursorModeMeasure?'primary':''" + @click="()=>state.cursorMode = Constract.CursorModeMeasure">
{ + destory() { + } + + mousedown() { this.mouseMoved = false } // 鼠标移动,创建对应的临时点与线 - mousemove = (e: MouseEvent) => { + mousemove(e: MouseEvent) { if (this.isCompleted) return this.mouseMoved = true + // 当前鼠标所在的点 const point = this.getClosestIntersection(e) if (!point) { return @@ -134,50 +135,56 @@ export default class RulerTool implements ITool { // 移动时绘制临时线 if (this.pointArray.length > 0) { - const p0 = this.pointArray[this.pointArray.length - 1] // 获取最后一个点 - const line = this.tempLine || this.createLine() + // 获取最后一个点 + const p0 = this.pointArray[this.pointArray.length - 1] + if (!this.tempLine) { + this.tempLine = this.createLine() + this.viewport.scene.add(this.tempLine) + } + + const line = this.tempLine const geom = line.geometry const startPoint = this.pointArray[0] const lastPoint = this.pointArray[this.pointArray.length - 1] - if (this.viewport.state.cursorMode === 'RulerArea') { + if (this.viewport.state.cursorMode === Constract.CursorModeMeasureArea) { geom.setFromPoints([lastPoint, point, startPoint]) } else { geom.setFromPoints([lastPoint, point]) } - if (this.viewport.state.cursorMode === 'Ruler') { + + if (this.viewport.state.cursorMode === Constract.CursorModeMeasure) { 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) => { + mouseup(e: MouseEvent) { // 如果mouseMoved是true,那么它可能在移动,而不是点击 if (!this.mouseMoved) { - // 右键点击表示完成绘图操作 + if (e.button === 2) { + // 右键点击, 完成绘图操作 this.viewport.state.cursorMode = 'normal' - } else if (e.button === 0) { // 左键点击表示添加点 + + } else if (e.button === 0) { + // 左键点击, 添加点 this.onMouseClicked(e) } } } - onMouseClicked = (e: MouseEvent) => { + onMouseClicked(e: MouseEvent) { if (this.isCompleted) { return } + // 获取鼠标点击位置的三维坐标 const point = this.getClosestIntersection(e) if (!point) { return @@ -185,10 +192,12 @@ export default class RulerTool implements ITool { // 双击触发两次点击事件,我们需要避免这里的第二次点击 const now = Date.now() - if (this.lastClickTime && (now - this.lastClickTime < 10)) return - + if (this.lastClickTime && (now - this.lastClickTime < 50)) { + return + } this.lastClickTime = now + // 添加正式点 this.pointArray.push(point) const count = this.pointArray.length @@ -196,22 +205,24 @@ export default class RulerTool implements ITool { 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) + if (this.tempLabel && count > 1) { + // 临时 点/label/line 将作为正式的使用 + const p0 = this.pointArray[count - 2] - // 创建距离测量线时,此处的 临时label 将作为正式的使用,不在this.clearTemp()中清除,故置为undefined - this.tempLabel = undefined - } + this.tempLabel.position.set((p0.x + point.x) / 2, (p0.y + point.y) / 2, (p0.z + point.z) / 2) + this.group.add(this.tempLabel) + this.tempLabel = undefined + + // 临时线 + this.tempLine.geometry.setFromPoints([p0, point]) + this.group.add(this.tempLine) + this.tempLine = undefined } - // this.redrawComplete() // this.viewport.dispatchSignal('sceneGraphChanged') } @@ -220,11 +231,11 @@ export default class RulerTool implements ITool { */ private createLine(): THREE.Line { const geom = new THREE.BufferGeometry() - const obj = new THREE.Line(geom, RulerTool.LINE_MATERIAL) + const obj = new THREE.Line(geom, MeasureTool.LINE_MATERIAL) obj.frustumCulled = false - obj.name = RulerTool.OBJ_NAME + obj.name = MeasureTool.LINE_NAME obj.userData = { - type: 'line' + type: Constract.MeasureLine } return obj } @@ -244,11 +255,11 @@ export default class RulerTool implements ITool { obj.position.set(p.x, p.y, p.z) } - obj.name = RulerTool.OBJ_NAME + obj.name = MeasureTool.OBJ_NAME obj.userData = { mode: this.viewport.state.cursorMode, - type: 'measure-marker' + type: Constract.MeasureMarker } return obj } @@ -264,7 +275,7 @@ export default class RulerTool implements ITool { const intersects = this.viewport.getIntersects(_point) if (intersects && intersects.length > 2) { - if (intersects.length > 0 && intersects[0].distance < RulerTool.MAX_DISTANCE) { + if (intersects.length > 0 && intersects[0].distance < MeasureTool.MAX_DISTANCE) { return new Vector3( intersects[0].point.x, 0.1, @@ -306,14 +317,85 @@ export default class RulerTool implements ITool { div.style.left = '0px' // div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件 const obj = new CSS2DObject(div) - obj.name = RulerTool.LABEL_NAME + obj.name = MeasureTool.LABEL_NAME obj.userData = { - type: 'label' + type: Constract.MeasureLabel } return obj } - // 重绘完成 + + /** + * 拖拽测量点, 重绘 + */ + 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 + } + }) + + // 重写move事件 + pmFn = this.redrawMousemove.bind(this) + this.canvas.addEventListener('pointermove', pmFn) + + this.group = point.parent as THREE.Group + this.isCompleted = false + } + + + /** + * 重绘监听鼠标移动 + */ + redrawMousemove(e: MouseEvent) { + let point = this.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.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] + + if (startPoint && lastPoint) { + geom.setFromPoints([lastPoint, point, startPoint]) + } else { + geom.setFromPoints([startPoint || lastPoint, point]) + } + } + } + + /** + * 重绘完成 + */ redrawComplete() { if (!this.tempPointMarker) return @@ -321,24 +403,21 @@ export default class RulerTool implements ITool { 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) - } + // 如果是距离测量,则清除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) } } diff --git a/src/types/Types.d.ts b/src/types/Types.d.ts index 017a840..98ab9dd 100644 --- a/src/types/Types.d.ts +++ b/src/types/Types.d.ts @@ -6,7 +6,15 @@ type CursorMode = | 'PointAdd' | 'LinkAdd' | 'LinkAdd2' - | 'Ruler' - | 'RulerArea' - | 'RulerAngle' + | 'Measure' + | 'MeasureArea' + | 'MeasureAngle' | 'selectByRec' + +type PointType = + 'measure-marker' + | 'pointMarker' + | 'pointMarker2' + | 'pointMarker3' + | 'pointMarker4' + | 'pointMarker5' \ No newline at end of file diff --git a/src/utils/webutils.ts b/src/utils/webutils.ts index 785a044..4762662 100644 --- a/src/utils/webutils.ts +++ b/src/utils/webutils.ts @@ -26,9 +26,9 @@ export function numberToString(num: number) { * 获取距离、面积或角度的单位字符串 */ export function getUnitString(mode: CursorMode): string { - if (mode === 'Ruler') return "m"; - if (mode === 'RulerArea') return "m²"; - if (mode === 'RulerAngle') return "°"; + if (mode === 'Measure') return "m"; + if (mode === 'MeasureArea') return "m²"; + if (mode === 'MeasureAngle') return "°"; return ""; }