diff --git a/src/designer/Viewport.ts b/src/designer/Viewport.ts index 95a956a..adaf4aa 100644 --- a/src/designer/Viewport.ts +++ b/src/designer/Viewport.ts @@ -23,6 +23,8 @@ 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' +import { getAllItemTypes } from '@/runtime/DefineItemType.ts' +import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' /** * 编辑器对象 @@ -81,7 +83,7 @@ export default class Viewport { } dispatchSignal(signal: string, data?: any) { - console.log('signal', signal, data) + // console.log('signal', signal, data) } /** @@ -246,6 +248,10 @@ export default class Viewport { this.measure = new MeasureTool() this.measure.init(this) + _.forEach(getAllItemTypes(), (itemType: ItemTypeDefineOption) => { + itemType.clazz.afterAddViewport(this) + }) + this.state.isReady = true } diff --git a/src/designer/model2DEditor/EsDragControls.ts b/src/designer/model2DEditor/EsDragControls.ts index 70752be..2e404eb 100644 --- a/src/designer/model2DEditor/EsDragControls.ts +++ b/src/designer/model2DEditor/EsDragControls.ts @@ -2,6 +2,8 @@ import { DragControls } from './DragControls.js' import * as THREE from 'three' import type Viewport from '@/designer/Viewport.ts' import Constract from '@/designer/Constract.ts' +import { getItemTypeByName } from '@/runtime/DefineItemType.ts' +import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' // dragControls 绑定函数 let dragStartFn, dragFn, dragEndFn, clickblankFn @@ -77,11 +79,17 @@ export default class EsDragControls { // 记录拖拽按下的位置和对象 this.onDownPosition = { x: e.e.clientX, y: e.e.clientY } - switch (e.object.userData.type) { - case Constract.MeasureMarker: - this.viewport.measure.dragPointStart(e.object) - break + if (e.object.userData?.type) { + const itemType: ItemTypeDefineOption = getItemTypeByName(e.object.userData.type) + if (itemType?.clazz) { + itemType.clazz.dragPointStart(this.viewport, e.object, e.e) + } } + // switch (e.object.userData.type) { + // case Constract.MeasureMarker: + // this.viewport.measure.dragPointStart(e.object) + // break + // } } // 拖拽中 @@ -108,11 +116,17 @@ export default class EsDragControls { } } - switch (e.object.userData.type) { - case Constract.MeasureMarker: - this.viewport.measure.dragPointComplete() - break + if (e.object.userData?.type) { + const itemType: ItemTypeDefineOption = getItemTypeByName(e.object.userData.type) + if (itemType?.clazz) { + itemType.clazz.dragPointComplete(this.viewport) + } } + // switch (e.object.userData.type) { + // 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 ac15179..f71e8d9 100644 --- a/src/designer/model2DEditor/tools/MeasureTool.ts +++ b/src/designer/model2DEditor/tools/MeasureTool.ts @@ -4,6 +4,9 @@ import { getUnitString, numberToString } from '@/utils/webutils' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import Constract from '@/designer/Constract.ts' import type { ICursorTool } from '@/designer/model2DEditor/tools/CursorTool.ts' +import type Measure from '@/model/itemType/measure/Measure.ts' +import { getItemTypeByName } from '@/runtime/DefineItemType.ts' +import { LABEL_NAME, LINE_NAME } from '@/model/itemType/measure/Measure.ts' let pdFn, pmFn, puFn, kdFn @@ -11,11 +14,6 @@ let pdFn, pmFn, puFn, kdFn * 用于在 threejs 中创建一系列的 object_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 //当相交物体的距离太远时,忽略它 - /** * 测量工具所在的视图窗口,从这里可以取到 所有 Three.js 相关的对象. * 比如: @@ -32,7 +30,8 @@ export default class MeasureTool implements ICursorTool { /** * 当前测绘内容组, 所有测量点、线、标签都在这个组中. 但不包括临时点、线 */ - group: THREE.Group + measure: Measure + /** * 是否完成测量 */ @@ -88,6 +87,11 @@ export default class MeasureTool implements ICursorTool { mode: CursorMode + private draggingIndex: number = -1 + private connectedLines: THREE.Line[] = [] + private connectedLabels: CSS2DObject[] = [] + private originalPosition: THREE.Vector3 = new THREE.Vector3() + /** * 测量工具初始化 */ @@ -95,12 +99,7 @@ export default class MeasureTool implements ICursorTool { this.viewport = viewport this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement - this.group = new THREE.Group() - this.group.name = `${MeasureTool.OBJ_NAME}_group` - this.group.userData = { - mode: Constract.CursorModeMeasure - } - this.viewport.scene.add(this.group) + this.measure = getItemTypeByName('measure')!.clazz } /** @@ -204,12 +203,10 @@ export default class MeasureTool implements ICursorTool { geom.setFromPoints([lastPoint, point]) } - if (this.mode === Constract.CursorModeMeasure) { - const dist = p0.distanceTo(point) - 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) - } + const dist = p0.distanceTo(point) + 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) } // this.viewport.dispatchSignal('sceneGraphChanged') @@ -259,7 +256,7 @@ export default class MeasureTool implements ICursorTool { const marker = this.createPointMarker(point) marker.userData.point = point marker.userData.pointIndex = count - 1 - this.group.add(marker) + this.measure.group.add(marker) // 把点加入拖拽控制器 this.viewport.dragControl.setDragObjects([marker], 'push') @@ -270,13 +267,13 @@ export default class MeasureTool implements ICursorTool { 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.measure.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.measure.group.add(this.tempLine) this.tempLine = undefined } @@ -327,154 +324,12 @@ export default class MeasureTool implements ICursorTool { */ addOrUpdateTempLabel(label: string, position: THREE.Vector3) { if (!this.tempLabel) { - this.tempLabel = this.createLabel(label) - console.log('addOrUpdateTempLabel', label, position) + this.tempLabel = this.measure.createLabel(label) this.viewport.scene.add(this.tempLabel) } this.tempLabel.position.set(position.x, position.y, position.z) this.tempLabel.element.innerHTML = label } - /** - * 创建标签 - */ - createLabel(text: string): CSS2DObject { - const div = document.createElement('div') - div.className = 'css2dObjectLabel' - div.innerHTML = text - div.style.padding = '5px 8px' - div.style.color = '#fff' - div.style.fontSize = '14px' - div.style.position = 'absolute' - div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)' - div.style.borderRadius = '12px' - div.style.top = '0px' - div.style.left = '0px' - // div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件 - const obj = new CSS2DObject(div) - obj.name = MeasureTool.LABEL_NAME - obj.userData = { - type: Constract.MeasureLabel - } - 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 拖拽的点. - */ - 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事件 - pmFn = this.redrawMousemove.bind(this) - this.canvas.addEventListener('pointermove', pmFn) - - console.log('connectedLines:', this.connectedLines) - console.log('connectedLabels:', this.connectedLabels) - - // 这个方法会在拖拽开始时被调用,记录当前点的索引和位置, - // 需要找到与这个点相关的所有测量点、线、标签,并将它们添加到临时数组中。 - // 并创建临时线条用于连接前后点。 - } - - /** - * 重绘监听鼠标移动 - */ - redrawMousemove(e: MouseEvent) { - 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 - }) - - // 更新所有相关标签 - 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)}` - }) - - // 更新点的可视化位置 - const pointObj = this.group.children.find(child => - child.userData.pointIndex === this.draggingIndex - ) as THREE.Mesh - if (pointObj) { - pointObj.position.copy(newPosition) - } - // 在拖拽过程中,监听鼠标move方法 - // 更新临时点的位置,并根据当前点的索引更新临时线条的几何体、重新计算长度、更新标签位置等 - } - - /** - * 测量点被 DragControls 拖拽完成后触发这个方法 - */ - dragPointComplete() { - - // 将临时点、线、标签添加到 group 中, 变成正式的测量 - - // 删除鼠标事件监听 - this.canvas.removeEventListener('pointermove', pmFn) - pmFn = undefined - } } \ No newline at end of file diff --git a/src/model/ModelUtils.ts b/src/model/ModelUtils.ts index 6a1fd48..e36b824 100644 --- a/src/model/ModelUtils.ts +++ b/src/model/ModelUtils.ts @@ -2,6 +2,41 @@ import * as THREE from 'three' import type { ItemJson, ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' import { getAllItemTypes, getItemTypeByName } from '@/runtime/DefineItemType.ts' +/** + * 在给定的场景中查找具有指定 uuid 的 Object3D 对象 + */ +export function findObject3DById(scene: THREE.Object3D, uuid: string): THREE.Object3D | undefined { + const rets = findObject3DByCondition(scene, object => object.uuid === uuid) + if (rets.length > 0) { + return rets[0] + } + return undefined +} + +/** + * 在给定场景中查找满足特定条件的 Object3D 对象集合 + */ +export function findObject3DByCondition(scene: THREE.Object3D, condition: (object: THREE.Object3D) => boolean): THREE.Object3D[] { + const foundObjects: THREE.Object3D[] = [] + + // 定义一个内部递归函数来遍历每个节点及其子节点 + function traverse(obj: THREE.Object3D) { + if (condition(obj)) { + foundObjects.push(obj) + } + + // 遍历当前对象的所有子对象 + for (let i = 0; i < obj.children.length; i++) { + traverse(obj.children[i]) + } + } + + // 开始从场景根节点进行遍历 + traverse(scene) + + return foundObjects +} + export function loadSceneFromJson(scene: THREE.Scene, items: ItemJson[]) { console.time('loadSceneFromJson') const object3ds = loadObject3DFromJson(items) diff --git a/src/model/example1.js b/src/model/example1.js index 89a9500..9f10289 100644 --- a/src/model/example1.js +++ b/src/model/example1.js @@ -43,7 +43,7 @@ export default { t: 'measure', a: 'ln', l: '测量2', c: '#ff0000', tf: [[-9.0, 0, 3], [0, 0, 0], [0.25, 0.1, 0.25]], dt: { - link: ['p3'] + link: ['p3', 'p4'] } }, { @@ -53,6 +53,14 @@ export default { dt: { link: [] } + }, + { + id: 'p4', + t: 'measure', a: 'ln', l: '测量3', c: '#ff0000', + tf: [[-9.0, 0, 8], [0, 0, 0], [0.25, 0.1, 0.25]], + dt: { + link: [] + } } ] } diff --git a/src/model/itemType/ItemTypeBase.ts b/src/model/itemType/ItemTypeBase.ts index c042687..b8a9a01 100644 --- a/src/model/itemType/ItemTypeBase.ts +++ b/src/model/itemType/ItemTypeBase.ts @@ -2,8 +2,15 @@ import { type Object3D } from 'three' import type WorldModel from '@/model/WorldModel.ts' import type { ItemJson, ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' import * as THREE from 'three' +import type Viewport from '@/designer/Viewport.ts' export default abstract class ItemTypeBase { + + /** + * 所有点的数组 + */ + pointArray: THREE.Object3D[] = [] + name: string option: ItemTypeDefineOption worldModel: WorldModel @@ -26,4 +33,21 @@ export default abstract class ItemTypeBase { */ afterAddScene(scene: THREE.Scene, objects: THREE.Object3D[]): void { } + + /** + * 添加到 viewport 后的回调 + */ + afterAddViewport(viewport: Viewport): void { + viewport.dragControl.setDragObjects(this.pointArray, 'push') + } + + /** + * EsDragControls 开始拖拽某个点时的回调 + */ + abstract dragPointStart(viewport: Viewport, point: THREE.Mesh) + + /** + * EsDragControls 拖拽完成(放开鼠标)时的回调 + */ + abstract dragPointComplete(viewport: Viewport) } \ No newline at end of file diff --git a/src/model/itemType/ItemTypeLineBase.ts b/src/model/itemType/ItemTypeLineBase.ts index d3d9e1c..7973471 100644 --- a/src/model/itemType/ItemTypeLineBase.ts +++ b/src/model/itemType/ItemTypeLineBase.ts @@ -2,40 +2,79 @@ import * as THREE from 'three' import ItemTypeBase from '@/model/itemType/ItemTypeBase.ts' import type { ItemJson } from '@/model/itemType/ItemTypeDefine.ts' import type WorldModel from '@/model/WorldModel.ts' +import type Viewport from '@/designer/Viewport.ts' +import { findObject3DByCondition, findObject3DById } from '@/model/ModelUtils.ts' + +let pmFn /** * ILineType 接口定义了线类型的基本方法 - * 用于创建点和线 + * 用于创建点和线, 以及处理拖拽事件, 以及交互事件 */ export default abstract class ItemTypeLineBase extends ItemTypeBase { + private relationPoints: THREE.Mesh[] = [] + private dragViewport: Viewport | undefined + private dragPoint: THREE.Mesh | undefined - /** - * 所有点的数组 - */ - pointArray: THREE.Object3D[] = [] + abstract createPointBasic(position: THREE.Vector3): THREE.Object3D - /** - * 所有连接线的数组 - */ - linkArray: THREE.Object3D[] = [] + abstract createLineBasic(): THREE.Line public init(worldModel: WorldModel) { return super.init(worldModel).then(() => { - // 初始化方法,子类可以重写 - this.pointArray.length = 0 - this.linkArray.length = 0 }) } afterLoadGroup(group: THREE.Group): void { } - afterLoadLine(line: THREE.Line): void { + afterCreateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { } afterLoadPoint(point: THREE.Object3D): void { } + afterUpdatePoint(point: THREE.Object3D, updatedLines: THREE.Object3D[]): void { + } + + afterUpdateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { + } + + createLine(scene: THREE.Scene, startPoint: THREE.Object3D, endPoint: THREE.Object3D): THREE.Line { + const line = this.createLineBasic() + const geom = line.geometry + geom.setFromPoints([startPoint.position, endPoint.position]) + + if (!line.userData) { + line.userData = {} + } + line.userData.lineStartId = startPoint.uuid + line.userData.lineEndId = endPoint.uuid + + if (startPoint.parent) { + startPoint.parent.add(line) + } else { + scene.add(line) + } + + if (!startPoint.userData.lines) { + startPoint.userData.lines = [] + } + startPoint.userData.lines.push(line.uuid) + + if (!endPoint.userData.lines) { + endPoint.userData.lines = [] + } + endPoint.userData.lines.push(line.uuid) + + this.afterCreateLine(line, startPoint, endPoint) + + return line + } + + /** + * 所有点数据加载完成后,添加进场景之后,需要根据 link 数组创建连接线 + */ afterAddScene(scene: THREE.Scene, objects: THREE.Object3D[]) { super.afterAddScene(scene, objects) @@ -49,28 +88,43 @@ export default abstract class ItemTypeLineBase extends ItemTypeBase { for (let j = 0; j < linkArray.length; j++) { const linkId = linkArray[j] // 在 pointArray 中查找对应的点 - const endPoint = this.pointArray.find(p => p.uuid === linkId) + const endPoint = findObject3DById(scene, linkId) if (!endPoint) { console.warn('not found link point uuid=${}', linkId) continue } - const line = this.createLine() - const geom = line.geometry - geom.setFromPoints([startPoint.position, endPoint.position]) - - if (startPoint.parent) { - startPoint.parent.add(line) - } else { - scene.add(line) - } - - this.afterLoadLine(line) - this.linkArray.push(line) + const line = this.createLine(scene, startPoint, endPoint) } } } + createPoint(position: THREE.Vector3, item: ItemJson): THREE.Object3D { + const point = this.createPointBasic(position) + point.name = item.name + point.uuid = item.id || THREE.MathUtils.generateUUID() + point.userData = _.cloneDeep(item.dt) || {} + point.userData.type = item.t + point.userData.actionType = item.a + point.userData.label = item.l + point.userData.color = item.c + + point.rotation.set( + THREE.MathUtils.degToRad(item.tf[1][0]), + THREE.MathUtils.degToRad(item.tf[1][1]), + THREE.MathUtils.degToRad(item.tf[1][2]) + ) + + point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2]) + this.pointArray.push(point) + + this.afterLoadPoint(point) + return point + } + + /** + * 从 Json 某个 item 加载数据 + */ override loadFromJson(item: ItemJson): undefined | THREE.Object3D { if (item.a === 'gp') { // gp 是为了分组而存在的 @@ -95,32 +149,97 @@ export default abstract class ItemTypeLineBase extends ItemTypeBase { item.tf[0][2] ) - const point = this.createPoint(position) - point.name = item.name - point.uuid = item.id || THREE.MathUtils.generateUUID() - point.userData = _.cloneDeep(item.dt) || {} - point.userData.type = item.t - point.userData.actionType = item.a - point.userData.label = item.l - point.userData.color = item.c - - point.rotation.set( - THREE.MathUtils.degToRad(item.tf[1][0]), - THREE.MathUtils.degToRad(item.tf[1][1]), - THREE.MathUtils.degToRad(item.tf[1][2]) - ) - - point.scale.set(item.tf[2][0], item.tf[2][1], item.tf[2][2]) - this.pointArray.push(point) - - this.afterLoadPoint(point) - return point + return this.createPoint(position, item) } console.error('ItemTypeLineBase.loadFromJson: Unsupported', item) } - abstract createPoint(position: THREE.Vector3): THREE.Object3D - abstract createLine(): THREE.Line + /** + * 被 DragControls 拖拽时触发这个方法 + */ + dragPointStart(viewport: Viewport, point: THREE.Mesh) { + console.log('dragPoint:', point) + this.dragViewport = viewport + this.dragPoint = point + const canvas = viewport.renderer.domElement + + // 收集相关联的线段和标签 + const relationPoints = new Set() + + // 找到与 point 有关联的节点, 无论是 a->b 还是 b->a 都需要收集 + findObject3DByCondition(viewport.scene, (targetObj: THREE.Object3D) => { + if (targetObj.uuid === point.uuid) { + return false + } + + // 无论 a->b 还是 b->a 都需要收集 + if (_.includes(targetObj.userData?.link, point.uuid) || _.includes(point.userData?.link, targetObj.uuid)) { + relationPoints.add(targetObj) + } + }) + + //@ts-ignore + this.relationPoints = Array.from(relationPoints) + + // 监听move事件 + pmFn = this.redrawMousemove.bind(this) + canvas.addEventListener('pointermove', pmFn) + } + + /** + * DragControls 拖拽过程中鼠标移动时触发这个方法 + */ + redrawMousemove(e: MouseEvent) { + if (!this.dragViewport || !this.dragPoint) return + + const updateLines: THREE.Object3D[] = [] + // 更新所有相关线段 + _.forEach(this.relationPoints, (targetPoint, idx) => { + if (targetPoint.uuid === this.dragPoint.uuid) { + return + } + + // 判断谁是起点,谁是终点 + let startPoint: THREE.Object3D + let endPoint: THREE.Object3D + if (_.includes(targetPoint.userData?.link, this.dragPoint.uuid)) { + startPoint = targetPoint + endPoint = this.dragPoint + + } else { + startPoint = this.dragPoint + endPoint = targetPoint + } + + // 找到 startPoint 与 this.dragPoint 之间的线段 + let line = findObject3DByCondition(this.dragViewport.scene, (obj) => { + return obj.userData.lineStartId === startPoint.uuid && obj.userData.lineEndId === endPoint.uuid + })[0] as THREE.Line + + if (!line) { + line = this.createLine(this.dragViewport.scene, startPoint, endPoint) + } + + // 更新线段几何体 + const geometry = line.geometry as THREE.BufferGeometry + geometry.setFromPoints([startPoint.position, endPoint.position]) + geometry.attributes.position.needsUpdate = true + this.afterUpdateLine(line, startPoint, endPoint) + + updateLines.push(line) + }) + + this.afterUpdatePoint(this.dragPoint, updateLines) + } + + /** + * DragControls 拖拽完成后触发这个方法 + */ + dragPointComplete(viewport: Viewport) { + // 删除鼠标事件监听 + viewport.renderer.domElement.removeEventListener('pointermove', pmFn) + pmFn = undefined + } } \ No newline at end of file diff --git a/src/model/itemType/measure/Measure.ts b/src/model/itemType/measure/Measure.ts index 5119f50..a8fcb7e 100644 --- a/src/model/itemType/measure/Measure.ts +++ b/src/model/itemType/measure/Measure.ts @@ -2,6 +2,10 @@ import * as THREE from 'three' import ItemTypeLineBase from '@/model/itemType/ItemTypeLineBase.ts' import { Material, Object3D, Scene } from 'three' import WorldModel from '@/model/WorldModel.ts' +import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' +import Constract from '@/designer/Constract.ts' +import { numberToString } from '@/utils/webutils.ts' +import { findObject3DById } from '@/model/ModelUtils.ts' export const GROUP_NAME = 'measure-group' export const POINT_NAME = 'measure_point' @@ -39,11 +43,6 @@ export default class Measure extends ItemTypeLineBase { return Promise.resolve() } - // 创建完线之后,创建 label - afterLoadLine(line: THREE.Line) { - super.afterLoadLine(line) - } - afterLoadGroup(group: THREE.Group) { super.afterLoadGroup(group) @@ -67,7 +66,7 @@ export default class Measure extends ItemTypeLineBase { /** * 创建测量点 */ - createPoint(position?: THREE.Vector3): THREE.Object3D { + createPointBasic(position?: THREE.Vector3): THREE.Object3D { const p = position const scale = 0.25 @@ -84,7 +83,7 @@ export default class Measure extends ItemTypeLineBase { /** * 创建测量线 */ - createLine(): THREE.Line { + createLineBasic(): THREE.Line { const geom = new THREE.BufferGeometry() const obj = new THREE.Line(geom, this.lineMaterial) obj.frustumCulled = false @@ -92,4 +91,72 @@ export default class Measure extends ItemTypeLineBase { obj.uuid = THREE.MathUtils.generateUUID() return obj } + + // 创建完线之后,创建 label + afterCreateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + super.afterCreateLine(line, startPoint, endPoint) + + const p0 = startPoint.position + const p1 = endPoint.position + + const dist = p0.distanceTo(p1) + const label = `${numberToString(dist)} m` + + const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5) + const labelObj = this.createLabel(label) + labelObj.position.set(position.x, 0.1, position.z) + labelObj.element.innerHTML = label + + line.userData.labelId = labelObj.uuid + this.group.add(labelObj) + } + + afterUpdateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + super.afterUpdateLine(line, startPoint, endPoint) + + const p0 = startPoint.position + const p1 = endPoint.position + + const dist = p0.distanceTo(p1) + const label = `${numberToString(dist)} 米` + const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5) + + const labelObj: CSS2DObject = findObject3DById(this.group, line.userData.labelId) as CSS2DObject + if (labelObj) { + labelObj.position.set(position.x, 0.1, position.z) + labelObj.element.innerHTML = label + + } else { + // 如果没有找到,则创建新的标签 + const newLabelObj = this.createLabel(label) + newLabelObj.position.set(position.x, position.y, position.z) + newLabelObj.element.innerHTML = label + line.userData.labelId = labelObj.uuid + this.group.add(newLabelObj) + } + } + + /** + * 创建标签 + */ + createLabel(text: string): CSS2DObject { + const div = document.createElement('div') + div.className = 'css2dObjectLabel' + div.innerHTML = text + div.style.padding = '5px 8px' + div.style.color = '#fff' + div.style.fontSize = '14px' + div.style.position = 'absolute' + div.style.backgroundColor = 'rgba(25, 25, 25, 0.3)' + div.style.borderRadius = '12px' + div.style.top = '0px' + div.style.left = '0px' + // div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件 + const obj = new CSS2DObject(div) + obj.name = LABEL_NAME + obj.userData = { + type: Constract.MeasureLabel + } + return obj + } } \ No newline at end of file