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 @@
state.cursorMode = 'normal'">
+ :type="state.cursorMode===Constract.CursorModeNormal?'primary':''"
+ @click="()=>state.cursorMode = Constract.CursorModeNormal">
state.cursorMode = 'selectByRec'">
+ :type="state.cursorMode===Constract.CursorModeSelectByRec?'primary':''"
+ @click="()=>state.cursorMode = Constract.CursorModeSelectByRec">
state.cursorMode = 'ALink'">
+ :type="state.cursorMode===Constract.CursorModeALink?'primary':''"
+ @click="()=>state.cursorMode = Constract.CursorModeALink">
state.cursorMode = 'SLink'">
+ :type="state.cursorMode===Constract.CursorModeSLink?'primary':''"
+ @click="()=>state.cursorMode = Constract.CursorModeSLink">
state.cursorMode = 'Ruler'">
+ :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 "";
}