9 changed files with 425 additions and 52 deletions
@ -1,35 +1,321 @@ |
|||||
import * as THREE from 'three' |
import * as THREE from 'three' |
||||
import type Viewport from '@/core/engine/Viewport' |
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 { |
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 viewport 当前视口 |
||||
* @param point 拖拽的点 |
* @param point 拖拽的点 |
||||
*/ |
*/ |
||||
abstract dragPointStart(viewport: Viewport, point: THREE.Object3D): void |
dragPointStart(viewport: Viewport, point: THREE.Object3D) { |
||||
|
} |
||||
|
|
||||
/** |
/** |
||||
* 拖拽点完成 |
* 拖拽点完成 |
||||
* @param viewport 当前视口 |
* @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' |
import BaseInteraction from '@/core/base/BaseInteraction.ts' |
||||
|
|
||||
export default class MeasureInteraction extends BaseInteraction { |
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