13 changed files with 540 additions and 70 deletions
@ -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() { |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue