9 changed files with 425 additions and 52 deletions
@ -1,35 +1,321 @@ |
|||
import * as THREE from 'three' |
|||
import type Viewport from '@/core/engine/Viewport' |
|||
import type { InteractionOption } from '@/core/manager/InteractionManager.ts' |
|||
import { getRenderer } from '@/core/manager/ModuleManager.ts' |
|||
import { Line2 } from 'three/examples/jsm/lines/Line2' |
|||
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry' |
|||
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' |
|||
import { numberToString } from '@/utils/webutils.ts' |
|||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' |
|||
|
|||
|
|||
let pdFn, pmFn, puFn |
|||
|
|||
/** |
|||
* 基本交互控制器基类 |
|||
* 定义了在建模编辑器中物流单元如何响应鼠标和键盘操作 |
|||
*/ |
|||
export default abstract class BaseInteraction { |
|||
protected viewport!: Viewport |
|||
viewport!: Viewport |
|||
canvas: HTMLCanvasElement |
|||
viewerDom: HTMLElement |
|||
|
|||
// 交互选项
|
|||
option: InteractionOption |
|||
|
|||
// 临时标记
|
|||
tempPointMarker?: THREE.Mesh |
|||
tempLine: Line2 | undefined = undefined |
|||
tempLabel: CSS2DObject | undefined = undefined |
|||
|
|||
// 用于判断, 按下之后有没有移动
|
|||
mouseOnlyClick = false |
|||
|
|||
// 上次鼠标移动位置
|
|||
lastMovePosition: THREE.Vector3 | undefined = undefined |
|||
|
|||
// 保存上次点击时间,以便检测双击事件
|
|||
lastClickTime: number = 0 |
|||
|
|||
readonly itemTypeName: string |
|||
|
|||
// 连线起点
|
|||
linkStartPointId: string |
|||
linkStartPointObject: THREE.Object3D |
|||
|
|||
templineMaterial = new LineMaterial({ |
|||
color: 0xE63C17, // 主颜色
|
|||
linewidth: 2, // 实际可用的线宽
|
|||
vertexColors: true, // 启用顶点颜色
|
|||
dashed: false, |
|||
alphaToCoverage: true |
|||
}) |
|||
|
|||
constructor(itemTypeName: string) { |
|||
this.itemTypeName = itemTypeName |
|||
} |
|||
|
|||
/** |
|||
* 开始交互 |
|||
* @param viewport 当前视口 |
|||
* @param startPoint 起点对象(可选) |
|||
*/ |
|||
abstract start(viewport: Viewport, startPoint?: THREE.Object3D): void |
|||
start(viewport: Viewport, option: InteractionOption = {}) { |
|||
this.stop() |
|||
this.viewport = viewport |
|||
this.option = option |
|||
|
|||
this.viewerDom = this.viewport.viewerDom |
|||
this.canvas = this.viewport.renderer.domElement |
|||
if (option.startPoint) { |
|||
this.linkStartPointId = option.startPoint.userData?.entityId |
|||
if (!this.linkStartPointId) { |
|||
this.linkStartPointObject = this.viewport.entityManager.findObjectsById(this.linkStartPointId)?.[0] |
|||
} |
|||
} |
|||
|
|||
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) |
|||
} |
|||
|
|||
/** |
|||
* 停止交互 |
|||
*/ |
|||
abstract stop(): void |
|||
stop() { |
|||
if (this.canvas) { |
|||
this.canvas.removeEventListener('pointerdown', pdFn) |
|||
pdFn = undefined |
|||
this.canvas.removeEventListener('pointermove', pmFn) |
|||
pmFn = undefined |
|||
this.canvas.removeEventListener('pointerup', puFn) |
|||
puFn = undefined |
|||
} |
|||
|
|||
this.linkStartPointId = undefined |
|||
this.linkStartPointObject = undefined |
|||
|
|||
// 清空所有临时点
|
|||
this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker) |
|||
this.tempPointMarker = undefined |
|||
this.tempLine && this.viewport.scene.remove(this.tempLine) |
|||
this.tempLine = undefined |
|||
this.tempLabel && this.viewport.scene.remove(this.tempLabel) |
|||
this.tempLabel = undefined |
|||
|
|||
this.viewerDom = undefined |
|||
this.canvas = undefined |
|||
} |
|||
|
|||
/** |
|||
* 拖拽点开始 |
|||
* @param viewport 当前视口 |
|||
* @param point 拖拽的点 |
|||
*/ |
|||
abstract dragPointStart(viewport: Viewport, point: THREE.Object3D): void |
|||
dragPointStart(viewport: Viewport, point: THREE.Object3D) { |
|||
} |
|||
|
|||
/** |
|||
* 拖拽点完成 |
|||
* @param viewport 当前视口 |
|||
*/ |
|||
abstract dragPointComplete(viewport: Viewport): void |
|||
dragPointComplete(viewport: Viewport) { |
|||
} |
|||
|
|||
mousedown() { |
|||
this.mouseOnlyClick = true |
|||
} |
|||
|
|||
mousemove(e: MouseEvent): THREE.Vector3 | undefined { |
|||
this.mouseOnlyClick = false |
|||
|
|||
// 当前鼠标所在的点
|
|||
const point = this.viewport.getClosestIntersection(e) |
|||
if (!point) { |
|||
return |
|||
} |
|||
|
|||
// 如果按下了 shift 键,则 point 的位置必须位于 startPoint 正上下方,或者正左右方
|
|||
if (this.linkStartPointObject && e.shiftKey) { |
|||
const startPos = this.linkStartPointObject.position |
|||
const dx = Math.abs(point.x - startPos.x) |
|||
const dz = Math.abs(point.z - startPos.z) |
|||
if (dx > dz) { |
|||
point.z = startPos.z |
|||
} else { |
|||
point.x = startPos.x |
|||
} |
|||
} |
|||
|
|||
// 移动时绘制临时线
|
|||
if (this.linkStartPointObject) { |
|||
// 获取最后一个点
|
|||
const p0 = this.linkStartPointObject.position |
|||
const dist = p0.distanceTo(point) |
|||
const label = `${numberToString(dist)} m` |
|||
const position = new THREE.Vector3().addVectors(p0, point).multiplyScalar(0.5) |
|||
|
|||
if (!this.tempLine) { |
|||
this.tempLine = this.createTempLine() |
|||
this.viewport.scene.add(this.tempLine) |
|||
} |
|||
if (!this.tempLabel) { |
|||
this.tempLabel = this.createTempLabel(label) |
|||
this.viewport.scene.add(this.tempLabel) |
|||
} |
|||
|
|||
this.tempLine.geometry.setFromPoints([p0, point]) |
|||
this.tempLabel.position.set(position.x, position.y, position.z) |
|||
this.tempLabel.element.innerHTML = label |
|||
} |
|||
|
|||
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 |
|||
} |
|||
|
|||
/** |
|||
* 创建测量线 |
|||
*/ |
|||
createTempLine(): Line2 { |
|||
const geom = new LineGeometry() |
|||
const obj = new Line2(geom, this.templineMaterial) |
|||
obj.frustumCulled = false |
|||
return obj |
|||
} |
|||
|
|||
/** |
|||
* 鼠标松开事件 |
|||
*/ |
|||
mouseup(e: MouseEvent) { |
|||
if (this.mouseOnlyClick) { |
|||
if (e.button === 2) { |
|||
// 右键点击, 完成绘图操作
|
|||
this.viewport.interactionManager.exitInteraction() |
|||
|
|||
} else if (e.button === 0) { |
|||
// 左键点击, 添加点
|
|||
this.onMouseClicked(e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
onMouseClicked(e: MouseEvent): THREE.Vector3 | undefined { |
|||
// 获取鼠标点击位置的三维坐标
|
|||
const point = this.lastMovePosition |
|||
if (!point) { |
|||
return |
|||
} |
|||
|
|||
// 双击触发两次点击事件,我们需要避免这里的第二次点击
|
|||
const now = Date.now() |
|||
if (this.lastClickTime && (now - this.lastClickTime < 50)) { |
|||
return |
|||
} |
|||
this.lastClickTime = now |
|||
|
|||
const renderer = getRenderer(this.itemTypeName) |
|||
|
|||
const defaultScale = renderer.getDefaultScale() |
|||
const defaultRotation = renderer.getDefaultRotation() |
|||
|
|||
// 添加正式点
|
|||
const itemJson = { |
|||
id: system.createUUID(), |
|||
t: this.itemTypeName, |
|||
v: true, |
|||
tf: [ |
|||
[point.x, point.y, point.z], |
|||
[defaultRotation.x, defaultRotation.y, defaultRotation.z], |
|||
[defaultScale.x, defaultScale.y, defaultScale.z] |
|||
], |
|||
dt: { |
|||
in: [] as string[], |
|||
out: [] as string[], |
|||
center: [] as string[] |
|||
} |
|||
} as ItemJson |
|||
|
|||
// 关联2个点
|
|||
const fromItem = this.viewport.entityManager.findItemById(this.linkStartPointId) |
|||
if (this.linkStartPointId && fromItem) { |
|||
itemJson.dt.center.push(this.linkStartPointId) |
|||
fromItem.dt.center.push(itemJson.id) |
|||
} |
|||
|
|||
// 提交状态管理器
|
|||
const stateManager = this.viewport.stateManager |
|||
stateManager.beginStateUpdate({ createFromInteraction: true }) |
|||
stateManager.vdata.items.push(itemJson) |
|||
stateManager.endStateUpdate() |
|||
|
|||
// 把点加入拖拽控制器
|
|||
// this.viewport.dragControl.setDragObjects(marker, 'push')
|
|||
|
|||
// 更新起始点为新添加的点
|
|||
this.linkStartPointId = itemJson.id |
|||
this.linkStartPointObject = this.viewport.entityManager.findObjectsById(itemJson.id)?.[0] |
|||
|
|||
// 删除临时点
|
|||
this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker) |
|||
this.tempPointMarker = undefined |
|||
|
|||
return point |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 创建临时点标记 |
|||
*/ |
|||
createTempPointMarker(position?: THREE.Vector3): THREE.Mesh { |
|||
const p = position |
|||
const renderer = getRenderer(this.itemTypeName) |
|||
const scale = renderer.getDefaultScale() |
|||
const rotation = renderer.getDefaultRotation() |
|||
|
|||
const geometry = new THREE.BoxGeometry(1, 1, 1) |
|||
const material = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 }) |
|||
const obj = new THREE.Mesh(geometry, material) |
|||
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) |
|||
} |
|||
return obj |
|||
} |
|||
|
|||
/** |
|||
* 创建标签 |
|||
*/ |
|||
createTempLabel(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) |
|||
return obj |
|||
} |
|||
} |
|||
@ -1,18 +1,8 @@ |
|||
import * as THREE from 'three' |
|||
import type Viewport from '@/core/engine/Viewport.ts' |
|||
import BaseInteraction from '@/core/base/BaseInteraction.ts' |
|||
|
|||
export default class MeasureInteraction extends BaseInteraction { |
|||
dragPointComplete(viewport: Viewport): void { |
|||
} |
|||
|
|||
dragPointStart(viewport: Viewport, point: THREE.Object3D): void { |
|||
} |
|||
|
|||
start(viewport: Viewport, startPoint?: THREE.Object3D): void { |
|||
constructor(itemTypeName: string) { |
|||
super(itemTypeName) |
|||
} |
|||
|
|||
stop(): void { |
|||
} |
|||
|
|||
} |
|||
Loading…
Reference in new issue