diff --git a/src/designer/Constract.ts b/src/designer/Constract.ts index 444e7ac..8a10162 100644 --- a/src/designer/Constract.ts +++ b/src/designer/Constract.ts @@ -11,12 +11,7 @@ export default Object.freeze({ CursorModeLinkAdd2: 'LinkAdd2', // 测量相关的光标模式 - CursorModeMeasure: 'Measure', - CursorModeMeasureArea: 'MeasureArea', - CursorModeMeasureAngle: 'MeasureAngle', - MeasureMarker: 'measure-marker', - MeasureLine: 'measure-line', - MeasureLabel: 'measure-label', + CursorModeMeasure: 'measure', // 选择模式 CursorModeSelectByRec: 'selectByRec' diff --git a/src/designer/Viewport.ts b/src/designer/Viewport.ts index 6d212a9..04ddaa0 100644 --- a/src/designer/Viewport.ts +++ b/src/designer/Viewport.ts @@ -1,16 +1,6 @@ import _ from 'lodash' import * as THREE from 'three' -import { - AxesHelper, - GridHelper, - Mesh, - type Object3D, - OrthographicCamera, - Raycaster, - Scene, - Vector3, - WebGLRenderer -} from 'three' +import { GridHelper, OrthographicCamera, Raycaster, Scene, Vector3, WebGLRenderer } from 'three' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import EsDragControls from './model2DEditor/EsDragControls' import Stats from 'three/examples/jsm/libs/stats.module' @@ -19,12 +9,11 @@ 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 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' +import type ItemTypeBaseToolbox from '@/model/itemType/ItemTypeBaseToolbox.ts' /** * 编辑器对象 @@ -42,13 +31,13 @@ export default class Viewport { worldModel: WorldModel raycaster: Raycaster dragControl: EsDragControls - measure: MeasureTool animationFrameId: any = null - currentTool: ICursorTool | null = null + currentTool: ItemTypeBaseToolbox | null = null tools: ITool[] = [ new MouseMoveInspect() ] + toolbox: Record = {} /** * 监听窗口大小变化 @@ -213,9 +202,10 @@ export default class Viewport { return } - if (newVal === 'Measure' || newVal === 'MeasureArea' || newVal === 'MeasureAngle') { + const currentTool = this.toolbox[newVal] + if (currentTool) { // 选择标尺工具 - this.currentTool = this.measure + this.currentTool = currentTool this.dragControl.dragControls.enabled = false } else { @@ -244,10 +234,6 @@ export default class Viewport { // 注册拖拽组件 this.dragControl = new EsDragControls(this) - // 注册测量工具 - this.measure = new MeasureTool() - this.measure.init(this) - _.forEach(getAllItemTypes(), (itemType: ItemTypeDefineOption) => { itemType.clazz.afterAddViewport(this) }) diff --git a/src/designer/model2DEditor/EsDragControls.ts b/src/designer/model2DEditor/EsDragControls.ts index 2e404eb..d74cf90 100644 --- a/src/designer/model2DEditor/EsDragControls.ts +++ b/src/designer/model2DEditor/EsDragControls.ts @@ -82,7 +82,7 @@ export default class EsDragControls { 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) + itemType.clazz.dragPointStart(this.viewport, e.object) } } // switch (e.object.userData.type) { diff --git a/src/designer/model2DEditor/tools/MeasureTool.ts b/src/designer/model2DEditor/tools/MeasureTool.ts index b78c56c..901db58 100644 --- a/src/designer/model2DEditor/tools/MeasureTool.ts +++ b/src/designer/model2DEditor/tools/MeasureTool.ts @@ -6,7 +6,7 @@ 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' +import { LINE_NAME } from '@/model/itemType/measure/Measure.ts' import type { ItemJson } from '@/model/itemType/ItemTypeDefine.ts' let pdFn, pmFn, puFn, kdFn diff --git a/src/model/ModelUtils.ts b/src/model/ModelUtils.ts index e36b824..55c1177 100644 --- a/src/model/ModelUtils.ts +++ b/src/model/ModelUtils.ts @@ -39,20 +39,27 @@ export function findObject3DByCondition(scene: THREE.Object3D, condition: (objec export function loadSceneFromJson(scene: THREE.Scene, items: ItemJson[]) { console.time('loadSceneFromJson') - const object3ds = loadObject3DFromJson(items) - // 通知所有加载的对象, 模型加载完成 + const object3ds: THREE.Object3D[] = [] + + // beforeLoad 通知所有加载的对象, 模型加载开始 + getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { + const ret = itemType.clazz.beforeLoad() + Array.isArray(ret) && object3ds.push(...ret) + }) + + const loads = loadObject3DFromJson(items) + Array.isArray(loads) && object3ds.push(...loads) + + // afterLoadComplete 通知所有加载的对象, 模型加载完成 getAllItemTypes().forEach((itemType: ItemTypeDefineOption) => { const ret = itemType.clazz.afterLoadComplete(object3ds) - if (ret && Array.isArray(ret)) { - // 如果返回值是数组,则合并到 object3ds 中 - object3ds.push(...ret) - } + Array.isArray(ret) && object3ds.push(...ret) }) scene.add(...object3ds) - // 通知所有加载的对象, 模型加载完成 + // afterAddScene 通知所有加载的对象, 模型加载完成 getAllItemTypes().forEach(itemType => { itemType.clazz.afterAddScene(scene, object3ds) }) diff --git a/src/model/WorldModel.ts b/src/model/WorldModel.ts index c8b1b85..7f2a530 100644 --- a/src/model/WorldModel.ts +++ b/src/model/WorldModel.ts @@ -42,10 +42,15 @@ export default class WorldModel { } loadFloorToScene(scene: THREE.Scene, levelCode: string) { - const floor = _.find(this.data.items, r => r.name === levelCode && r.t === 'floor') + let floor = _.find(this.data.items, r => r.name === levelCode && r.t === 'floor') if (!floor) { - console.warn(`未找到楼层数据: ${levelCode}`) - return [] + console.info(`新建楼层: ${levelCode}`) + + if (!_.isArray(this.data.items)) { + this.data.items = [] + } + floor = { name: levelCode, t: 'floor', items: [] } + this.data.items.push(floor) } loadSceneFromJson(scene, floor.items) diff --git a/src/model/itemType/ItemTypeBase.ts b/src/model/itemType/ItemTypeBase.ts index b8a9a01..e921386 100644 --- a/src/model/itemType/ItemTypeBase.ts +++ b/src/model/itemType/ItemTypeBase.ts @@ -3,6 +3,7 @@ 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' +import type ItemTypeBaseToolbox from '@/model/itemType/ItemTypeBaseToolbox.ts' export default abstract class ItemTypeBase { @@ -24,6 +25,27 @@ export default abstract class ItemTypeBase { abstract loadFromJson(item: ItemJson): undefined | THREE.Object3D + abstract createToolbox(viewport: Viewport): ItemTypeBaseToolbox + + abstract getDefaultScale(): THREE.Vector3 + + abstract getDefaultRotation(): THREE.Vector3 + + abstract createPoint(position: THREE.Vector3, itemJson: ItemJson): THREE.Object3D + + beforeLoad(): THREE.Object3D[] { + return [] + } + + afterLoadPoint(point: THREE.Object3D): void { + } + + afterUpdatePoint(point: THREE.Object3D, updatedLines: THREE.Object3D[]): void { + } + + afterLoadGroup(group: THREE.Group): void { + } + afterLoadComplete(objects: THREE.Object3D[]): THREE.Object3D[] { return [] } @@ -39,6 +61,8 @@ export default abstract class ItemTypeBase { */ afterAddViewport(viewport: Viewport): void { viewport.dragControl.setDragObjects(this.pointArray, 'push') + const toolbox = this.createToolbox(viewport) + viewport.toolbox[this.name] = toolbox } /** diff --git a/src/model/itemType/ItemTypeBaseToolbox.ts b/src/model/itemType/ItemTypeBaseToolbox.ts new file mode 100644 index 0000000..eb14d7d --- /dev/null +++ b/src/model/itemType/ItemTypeBaseToolbox.ts @@ -0,0 +1,286 @@ +import * as THREE from 'three' +import type Viewport from '@/designer/Viewport.ts' +import type ItemTypeBase from '@/model/itemType/ItemTypeBase.ts' +import type { ItemJson } from '@/model/itemType/ItemTypeDefine.ts' + +let pdFn, pmFn, puFn + +/** + * 单元类型工具箱 + */ +export default abstract class ItemTypeBaseToolbox { + /** + * 测量工具所在的视图窗口,从这里可以取到 所有 Three.js 相关的对象. + * 比如: + * - viewport.scene 场景 + * - viewport.renderer 渲染器 + * - viewport.controls 控制器 + * - viewport.camera 摄像机 + * - viewport.raycaster 射线投射器 + * - viewport.dragControl 拖拽控制器 + * - viewport.measure 测量工具 + */ + viewport: Viewport + + /** + * 是否完成工具箱操作 + */ + isCompleted = false + + /** + * 是否鼠标移动事件 + */ + mouseMoved = false + + /** + * 当前鼠标所在的画布, 对应 viewport.renderer.domElement + */ + canvas: HTMLCanvasElement + + /** + * 用于存储临时点 + */ + tempPointMarker?: THREE.Mesh + + /** + * 测量起始点 + */ + startPoint?: THREE.Object3D = undefined + + /** + * 保存上次点击时间,以便检测双击事件 + * @protected + */ + lastClickTime: number = 0 + + /** + * 上次鼠标移动位置 + */ + lastMovePosition: THREE.Vector3 | undefined = undefined + + _itemType: any + + get itemType(): ItemTypeBase { + return this._itemType + } + + mode: CursorMode + + static TMP_TYPE = '_TMP' + + /** + * 获取临时点的名称 + */ + abstract getTempPointName(): string + + addToScene(object: THREE.Object3D) { + this.viewport.scene.add(object) + } + + /** + * 单元工具初始化 + */ + init(viewport: any, itemType: ItemTypeBase): void { + this.viewport = viewport + this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement + this._itemType = itemType + } + + + /** + * 测量工具开始, 监听鼠标事件, 变量初始化等 + */ + start(startPoint?: THREE.Object3D) { + 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.isCompleted = false + this.viewport.viewerDom.style.cursor = 'crosshair' + + this.mode = this.viewport.state.cursorMode + this.startPoint = startPoint + + system.msg('新建 [' + this.itemType.name + '] 模式') + } + + + /** + * 停止测量模式, 清除所有临时点、线、标签. 停止所有鼠标事件监听 + */ + stop(): void { + system.msg('退出新建模式') + + const viewerDom = this.viewport.viewerDom + + this.isCompleted = true + viewerDom.style.cursor = '' + + this.canvas.removeEventListener('pointerdown', pdFn) + pdFn = undefined + this.canvas.removeEventListener('pointermove', pmFn) + pmFn = undefined + this.canvas.removeEventListener('pointerup', puFn) + puFn = undefined + + // 清空所有临时点 + this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker) + this.tempPointMarker = undefined + } + + + /** + * 创建临时点标记 + */ + createTempPointMarker(position?: THREE.Vector3): THREE.Mesh { + const p = position + const scale = this.itemType.getDefaultScale() + const rotation = this.itemType.getDefaultRotation() + + const tt = new THREE.BoxGeometry(1, 1, 1) + const t2 = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 }) + const obj = new THREE.Mesh(tt, t2) + obj.scale.set(scale.x, scale.y, scale.x) + obj.rotation.set( + THREE.MathUtils.degToRad(rotation.x), + THREE.MathUtils.degToRad(rotation.y), + THREE.MathUtils.degToRad(rotation.z) + ) + if (p) { + obj.position.set(p.x, p.y, p.z) + } + + obj.name = this.getTempPointName() + obj.userData = { + mode: this.mode, + type: ItemTypeBaseToolbox.TMP_TYPE + } + return obj + } + + /** + * 鼠标按下事件 + */ + mousedown() { + this.mouseMoved = false + } + + /** + * 鼠标移动,创建对应的临时点与线 + */ + mousemove(e: MouseEvent): THREE.Vector3 | undefined { + if (this.isCompleted) return + + this.mouseMoved = true + + // 当前鼠标所在的点 + const point = this.viewport.getClosestIntersection(e) + if (!point) { + return + } + + this.lastMovePosition = point + + // 在鼠标移动时绘制临时点 + if (this.tempPointMarker) { + this.tempPointMarker.position.set(point.x, point.y, point.z) + } else { + this.tempPointMarker = this.createTempPointMarker(point) + this.viewport.scene.add(this.tempPointMarker) + } + + // this.viewport.dispatchSignal('sceneGraphChanged') + return point + } + + /** + * 鼠标松开事件 + */ + mouseup(e: MouseEvent) { + // 如果mouseMoved是true,那么它可能在移动,而不是点击 + if (!this.mouseMoved) { + + if (e.button === 2) { + // 右键点击, 完成绘图操作 + this.viewport.state.cursorMode = 'normal' + + + } else if (e.button === 0) { + // 左键点击, 添加点 + this.onMouseClicked(e) + } + } + } + + onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined { + if (this.isCompleted) { + return + } + + // 获取鼠标点击位置的三维坐标 + const point = this.lastMovePosition + if (!point) { + return + } + + // 双击触发两次点击事件,我们需要避免这里的第二次点击 + const now = Date.now() + if (this.lastClickTime && (now - this.lastClickTime < 50)) { + return + } + this.lastClickTime = now + + const defaultScale = this.itemType.getDefaultScale() + const defaultRotation = this.itemType.getDefaultRotation() + + // 添加正式点 + const itemJson = { + t: this.itemType.name, + a: 'ln', + tf: [ + [point.x, point.y, point.z], + [defaultRotation.x, defaultRotation.y, defaultRotation.z], + [defaultScale.x, defaultScale.y, defaultScale.z] + ], + dt: { + link: [] as string[] + } + } as ItemJson + const marker = this.itemType.createPoint(point, itemJson) + this.addToScene(marker) + + // 把点加入拖拽控制器 + this.viewport.dragControl.setDragObjects([marker], 'push') + + if (this.startPoint) { + this.afterAddPoint(this.startPoint, marker) + } + + // 更新起始点为新添加的点 + this.startPoint = marker + + // 删除临时线 + this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker) + this.tempPointMarker = undefined + + return point + } + + /** + * 当用户点击某个点,从临时点转换为正式点后调用 + * 子类可以重写此方法来处理添加点后的逻辑 + */ + afterAddPoint(startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { + // 默认实现不做任何操作 + // 子类可以重写此方法来处理添加点后的逻辑 + } + + /** + * 销毁测量工具, 当视图窗口被销毁时调用 + */ + destory() { + } +} \ No newline at end of file diff --git a/src/model/itemType/ItemTypeLineBase.ts b/src/model/itemType/ItemTypeLineBase.ts index 9b7e194..2f49dd6 100644 --- a/src/model/itemType/ItemTypeLineBase.ts +++ b/src/model/itemType/ItemTypeLineBase.ts @@ -16,10 +16,6 @@ export default abstract class ItemTypeLineBase extends ItemTypeBase { private dragViewport: Viewport | undefined private dragPoint: THREE.Mesh | undefined - defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.1, 0.25) - defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) - defaultY = 0.1 - abstract createPointBasic(position: THREE.Vector3): THREE.Object3D abstract createLineBasic(): THREE.Line @@ -29,18 +25,9 @@ export default abstract class ItemTypeLineBase extends ItemTypeBase { }) } - afterLoadGroup(group: THREE.Group): 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 { } @@ -218,12 +205,14 @@ export default abstract class ItemTypeLineBase extends ItemTypeBase { } // 找到 startPoint 与 this.dragPoint 之间的线段 - let line = findObject3DByCondition(this.dragViewport.scene, (obj) => { + const 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) + // line = this.createLine(this.dragViewport.scene, startPoint, endPoint) + console.warn('Line not found between points:', startPoint.uuid, endPoint.uuid) + debugger } // 更新线段几何体 diff --git a/src/model/itemType/ItemTypeLineToolbox.ts b/src/model/itemType/ItemTypeLineToolbox.ts new file mode 100644 index 0000000..c6d783e --- /dev/null +++ b/src/model/itemType/ItemTypeLineToolbox.ts @@ -0,0 +1,74 @@ +import * as THREE from 'three' +import type { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' +import ItemTypeBaseToolbox from '@/model/itemType/ItemTypeBaseToolbox.ts' +import type ItemTypeLineBase from '@/model/itemType/ItemTypeLineBase.ts' + +let pdFn, pmFn, puFn + +/** + * 线条工具箱 + */ +export default class ItemTypeLineToolbox extends ItemTypeBaseToolbox { + /** + * 临时线条 + */ + tempLine?: THREE.Line + + get itemType(): ItemTypeLineBase { + return this._itemType + } + + getTempPointName(): string { + return '_measure_temp_point' + } + + afterMoveTemplateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + } + + stop() { + super.stop() + + this.tempLine && this.viewport.scene.remove(this.tempLine) + this.tempLine = undefined + } + + afterAddPoint(startPoint: THREE.Object3D, point: THREE.Object3D) { + // 如果起始点存在,则将新点添加到起始点的链接中 + startPoint.userData.link.push(point.uuid) + this.itemType.createLine(this.viewport.scene, this.startPoint, point) + } + + mousemove(e: MouseEvent): THREE.Vector3 | undefined { + const point = super.mousemove(e) + if (!point) { + return + } + + // 移动时绘制临时线 + if (this.startPoint) { + // 获取最后一个点 + if (!this.tempLine) { + this.tempLine = this.itemType.createLineBasic() + this.viewport.scene.add(this.tempLine) + } + + const p0 = this.startPoint.position + const line = this.tempLine + const geom = line.geometry + geom.setFromPoints([p0, point]) + + this.afterMoveTemplateLine(line, this.startPoint, this.tempPointMarker) + } + } + + onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined { + const r = super.onMouseClicked(e) + if (!r) { + return + } + + this.tempLine && this.viewport.scene.remove(this.tempLine) + this.tempLine = undefined + return r + } +} \ No newline at end of file diff --git a/src/model/itemType/measure/Measure.ts b/src/model/itemType/measure/Measure.ts index 1c97e5e..91ca50a 100644 --- a/src/model/itemType/measure/Measure.ts +++ b/src/model/itemType/measure/Measure.ts @@ -1,16 +1,14 @@ import * as THREE from 'three' +import { Material } 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' -export const LABEL_NAME = 'measure_label' -export const LINE_NAME = 'measure_line' +import Viewport from '@/designer/Viewport.ts' +import ItemTypeLineToolbox from '@/model/itemType/ItemTypeLineToolbox.ts' +import MeasureToolbox from '@/model/itemType/measure/MeasureToolbox.ts' +import ItemTypeBaseToolbox from '@/model/itemType/ItemTypeBaseToolbox.ts' export default class Measure extends ItemTypeLineBase { /** @@ -18,10 +16,18 @@ export default class Measure extends ItemTypeLineBase { */ group: THREE.Group + defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.1, 0.25) + defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0) + pointMaterial!: Material lineMaterial!: Material + static GROUP_NAME = 'measure-group' + static LABEL_NAME = 'measure_label' + static POINT_NAME = 'measure_point' + static LINE_NAME = 'measure_line' + override init(worldModel: WorldModel): Promise { super.init(worldModel) @@ -37,12 +43,27 @@ export default class Measure extends ItemTypeLineBase { this.pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 }) - this.group = new THREE.Group() - this.group.name = GROUP_NAME - return Promise.resolve() } + getDefaultScale(): THREE.Vector3 { + return this.defaultScale + } + + getDefaultRotation(): THREE.Vector3 { + return this.defaultRotation + } + + beforeLoad(): THREE.Object3D[] { + this.group = null + return [] + } + + createToolbox(viewport: Viewport): ItemTypeLineToolbox { + const toolbox = new MeasureToolbox(this.group) + toolbox.init(viewport, this) + return toolbox + } afterLoadGroup(group: THREE.Group) { super.afterLoadGroup(group) @@ -53,11 +74,12 @@ export default class Measure extends ItemTypeLineBase { this.group = group } + afterLoadComplete(objects: THREE.Object3D[]): THREE.Object3D[] { // 如果没有 group,则创建一个新的 group if (!this.group) { this.group = new THREE.Group() - this.group.name = GROUP_NAME + this.group.name = Measure.GROUP_NAME } return [this.group] @@ -76,7 +98,7 @@ export default class Measure extends ItemTypeLineBase { if (p) { obj.position.set(p.x, p.y, p.z) } - obj.name = POINT_NAME + obj.name = Measure.POINT_NAME return obj } @@ -87,7 +109,7 @@ export default class Measure extends ItemTypeLineBase { const geom = new THREE.BufferGeometry() const obj = new THREE.Line(geom, this.lineMaterial) obj.frustumCulled = false - obj.name = LINE_NAME + obj.name = Measure.LINE_NAME obj.uuid = THREE.MathUtils.generateUUID() return obj } @@ -104,6 +126,7 @@ export default class Measure extends ItemTypeLineBase { const position = new THREE.Vector3().addVectors(p0, p1).multiplyScalar(0.5) const labelObj = this.createLabel(label) + labelObj.name = Measure.LABEL_NAME labelObj.position.set(position.x, position.y, position.z) labelObj.element.innerHTML = label @@ -153,9 +176,9 @@ export default class Measure extends ItemTypeLineBase { div.style.left = '0px' // div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件 const obj = new CSS2DObject(div) - obj.name = LABEL_NAME + obj.name = MeasureToolbox.TMP_LABEL_NAME obj.userData = { - type: Constract.MeasureLabel + type: ItemTypeBaseToolbox.TMP_TYPE } return obj } diff --git a/src/model/itemType/measure/MeasureToolbox.ts b/src/model/itemType/measure/MeasureToolbox.ts new file mode 100644 index 0000000..59394a6 --- /dev/null +++ b/src/model/itemType/measure/MeasureToolbox.ts @@ -0,0 +1,81 @@ +import * as THREE from 'three' +import ItemTypeLineToolbox from '@/model/itemType/ItemTypeLineToolbox.ts' +import { numberToString } from '@/utils/webutils.ts' +import type Measure from './Measure.ts' +import type { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' +import ItemTypeBaseToolbox from '@/model/itemType/ItemTypeBaseToolbox.ts' + +/** + * 测量工具箱,用于处理测量相关的操作 + */ +export default class MeasureToolbox extends ItemTypeLineToolbox { + + group: THREE.Group + + /** + * 临时标签对象, 用于在鼠标移动时显示距离 + */ + tempLabel?: CSS2DObject + + static TMP_LABEL_NAME = '_measure_temp_label' + + constructor(group: THREE.Group) { + super() + this.group = group + } + + stop() { + super.stop() + + // 清除临时标签 + this.tempLabel && this.viewport.scene.remove(this.tempLabel) + this.tempLabel = undefined + } + + onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined { + const r = super.onMouseClicked(e) + if (!r) { + return + } + + this.tempLabel && this.viewport.scene.remove(this.tempLabel) + this.tempLabel = undefined + } + + get measure(): Measure { + return this._itemType + } + + addToScene(object: THREE.Object3D) { + this.measure.group.add(object) + } + + afterMoveTemplateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + super.afterMoveTemplateLine(line, startPoint, endPoint) + + const p0 = startPoint.position + const point = endPoint.position + + const dist = p0.distanceTo(point) + const label = `${numberToString(dist)} m` + const position = new THREE.Vector3().addVectors(p0, point).multiplyScalar(0.5) + this.addOrUpdateTempLabel(label, position) + } + + /** + * 添加或更新临时标签和位置 + */ + addOrUpdateTempLabel(label: string, position: THREE.Vector3) { + if (!this.tempLabel) { + this.tempLabel = this.measure.createLabel(label) + this.tempLabel.name = MeasureToolbox.TMP_LABEL_NAME + this.tempLabel.userData = { + mode: this.mode, + type: ItemTypeBaseToolbox.TMP_TYPE + } + this.viewport.scene.add(this.tempLabel) + } + this.tempLabel.position.set(position.x, position.y, position.z) + this.tempLabel.element.innerHTML = label + } +} \ No newline at end of file diff --git a/src/runtime/DefineItemType.ts b/src/runtime/DefineItemType.ts index adfc34b..662cb09 100644 --- a/src/runtime/DefineItemType.ts +++ b/src/runtime/DefineItemType.ts @@ -1,5 +1,5 @@ import _ from 'lodash' -import type { ItemTypeDefineOption } from '@/model/itemTypeDefine/ItemTypeDefine.ts' +import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' const itemTypes: Record = {} window['itemTypes'] = itemTypes