|
|
|
@ -6,27 +6,28 @@ import Viewport from '@/designer/Viewport.ts' |
|
|
|
import { numberToString, getUnitString } 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' |
|
|
|
|
|
|
|
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' |
|
|
|
export default class MeasureTool implements ICursorTool { |
|
|
|
static OBJ_NAME = 'measure_' |
|
|
|
static LABEL_NAME = 'measure_label' |
|
|
|
static LINE_NAME = 'measure_line' |
|
|
|
static MAX_DISTANCE = 500 //当相交物体的距离太远时,忽略它
|
|
|
|
|
|
|
|
viewport: Viewport |
|
|
|
|
|
|
|
// 当前测绘内容组
|
|
|
|
group: THREE.Group |
|
|
|
isCompleted = false |
|
|
|
mouseMoved = false |
|
|
|
canvas: HTMLCanvasElement |
|
|
|
|
|
|
|
// 用户在测量时绘制的线的当前实例
|
|
|
|
protected polyline?: THREE.Line |
|
|
|
|
|
|
|
// 用于存储临时点
|
|
|
|
protected tempPointMarker?: THREE.Mesh |
|
|
|
|
|
|
|
@ -36,13 +37,12 @@ export default class RulerTool implements ITool { |
|
|
|
// 用于在鼠标移动时存储临时标签,只有测量距离时才有
|
|
|
|
protected tempLabel?: CSS2DObject |
|
|
|
|
|
|
|
// 存储点
|
|
|
|
// 当次绘制点
|
|
|
|
protected pointArray: THREE.Vector3[] = [] |
|
|
|
|
|
|
|
//保存上次点击时间,以便检测双击事件
|
|
|
|
protected lastClickTime: number = 0 |
|
|
|
|
|
|
|
|
|
|
|
static LINE_MATERIAL = new THREE.LineBasicMaterial({ |
|
|
|
color: 0xE63C17, |
|
|
|
linewidth: 2, |
|
|
|
@ -58,36 +58,32 @@ export default class RulerTool implements ITool { |
|
|
|
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.name = `${MeasureTool.OBJ_NAME}_group` |
|
|
|
this.group.userData = { |
|
|
|
mode: this.viewport.state.cursorMode |
|
|
|
} |
|
|
|
this.viewport.scene.add(this.group) |
|
|
|
} |
|
|
|
|
|
|
|
start() { |
|
|
|
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) |
|
|
|
|
|
|
|
// 测量距离、面积和角度需要折线
|
|
|
|
this.polyline = this.createLine() |
|
|
|
this.group.add(this.polyline) |
|
|
|
this.isCompleted = false |
|
|
|
this.viewport.viewerDom.style.cursor = 'crosshair' |
|
|
|
|
|
|
|
// 当次绘制点
|
|
|
|
this.pointArray = [] |
|
|
|
|
|
|
|
system.msg('进入鼠标测距模式') |
|
|
|
system.msg('进入测量模式') |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
destory(): void { |
|
|
|
system.msg('退出鼠标测距模式') |
|
|
|
stop(): void { |
|
|
|
system.msg('退出测量模式') |
|
|
|
|
|
|
|
const viewerDom = this.viewport.viewerDom |
|
|
|
|
|
|
|
@ -101,6 +97,7 @@ export default class RulerTool implements ITool { |
|
|
|
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) |
|
|
|
@ -109,16 +106,20 @@ export default class RulerTool implements ITool { |
|
|
|
this.tempLabel = undefined |
|
|
|
} |
|
|
|
|
|
|
|
mousedown = () => { |
|
|
|
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) |
|
|
|
} |
|
|
|
} |
|
|
|
|