Browse Source

BaseInteraction startInteraction 完成

master
修宁 7 months ago
parent
commit
5487a6fcdc
  1. 300
      src/core/base/BaseInteraction.ts
  2. 12
      src/core/base/BaseRenderer.ts
  3. 16
      src/core/manager/EntityManager.ts
  4. 101
      src/core/manager/InteractionManager.ts
  5. 5
      src/core/manager/StateManager.ts
  6. 14
      src/modules/measure/MeasureInteraction.ts
  7. 19
      src/modules/measure/MeasureRenderer.ts
  8. 8
      src/modules/measure/index.ts
  9. 2
      src/types/model.d.ts

300
src/core/base/BaseInteraction.ts

@ -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
}
} }

12
src/core/base/BaseRenderer.ts

@ -17,6 +17,12 @@ export default abstract class BaseRenderer {
isUpdating: boolean = false isUpdating: boolean = false
readonly itemTypeName: string
constructor(itemTypeName: string) {
this.itemTypeName = itemTypeName
}
/** /**
* *
* @param viewport * @param viewport
@ -44,6 +50,10 @@ export default abstract class BaseRenderer {
*/ */
abstract createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] abstract createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[]
abstract getDefaultScale(): THREE.Vector3
abstract getDefaultRotation(): THREE.Vector3
/** /**
* 线 * 线
*/ */
@ -122,6 +132,8 @@ export default abstract class BaseRenderer {
this.fillObjectUserDataFromItem(item, ...points) this.fillObjectUserDataFromItem(item, ...points)
this.tempViewport.entityManager.appendObject(item.id, points) this.tempViewport.entityManager.appendObject(item.id, points)
this.appendToScene(...points) this.appendToScene(...points)
return points
} }

16
src/core/manager/EntityManager.ts

@ -130,11 +130,11 @@ export default class EntityManager {
} }
if (typeof originEntity === 'undefined') { if (typeof originEntity === 'undefined') {
renderer.createPoint(entity, option) renderer.createPoint(entity, option as RendererCudOption)
} else { } else {
option.originEntity = _.cloneDeep(originEntity) option.originEntity = _.cloneDeep(originEntity)
renderer.updatePoint(entity, option) renderer.updatePoint(entity, option as RendererCudOption)
} }
} }
@ -152,7 +152,7 @@ export default class EntityManager {
this.removeRelations(id) // 清理关系 this.removeRelations(id) // 清理关系
this.entities.delete(id) // 删除实体 this.entities.delete(id) // 删除实体
this.getDiffRenderer(entity.t).deletePoint(id, option) this.getDiffRenderer(entity.t).deletePoint(id, option as RendererCudOption)
} }
generateLineDiffsForDelete(id: string): void { generateLineDiffsForDelete(id: string): void {
@ -478,6 +478,16 @@ export default class EntityManager {
deleteLineObjectOnly(id: string) { deleteLineObjectOnly(id: string) {
return this.lines.delete(id) return this.lines.delete(id)
} }
/**
* linkStartPointId ItemJson
*/
findItemById(linkStartPointId: string): ItemJson | undefined {
if (!linkStartPointId) {
return
}
return this.entities.get(linkStartPointId)
}
} }
interface LineDiffItem { interface LineDiffItem {

101
src/core/manager/InteractionManager.ts

@ -1,9 +1,16 @@
import * as THREE from 'three'
import type Viewport from '@/core/engine/Viewport.ts' import type Viewport from '@/core/engine/Viewport.ts'
import { watch } from 'vue' import { watch } from 'vue'
import type IControls from '@/core/controls/IControls.ts' import type IControls from '@/core/controls/IControls.ts'
import type BaseInteraction from '@/core/base/BaseInteraction.ts' import type BaseInteraction from '@/core/base/BaseInteraction.ts'
import { getInteraction } from '@/core/manager/ModuleManager.ts' import { getInteraction } from '@/core/manager/ModuleManager.ts'
import * as THREE from 'three'
export interface InteractionOption {
/**
*
*/
startPoint?: THREE.Object3D
}
/** /**
* *
@ -22,29 +29,77 @@ export default class InteractionManager implements IControls {
init(viewport: Viewport) { init(viewport: Viewport) {
this.viewport = viewport this.viewport = viewport
this.viewport.watchList.push(watch(() => this.viewport.state.cursorMode, (newVal: CursorMode) => { this.viewport.watchList.push(watch(() => this.viewport.state.cursorMode,
const state = this.viewport.state (newVal: CursorMode, oldVal: CursorMode) => {
if (newVal === oldVal) return
if (!state.isReady) { this._setActiveInteraction(newVal)
return }))
} }
if (this.currentTool) {
this.currentTool.stop() /**
this.currentTool = null *
} */
if (newVal === 'normal' || !newVal) { startInteraction(itemType: string, option: InteractionOption = {}): void {
this.viewport.dragControl.dragControls.enabled = true this._setActiveInteraction(itemType, option.startPoint)
return }
}
/**
this.currentTool = getInteraction(newVal) * 退
this.viewport.dragControl.dragControls.enabled = false */
exitInteraction() {
this.currentTool.start(this.viewport, this.toolStartObject) if (this.currentTool) {
this.toolStartObject = null this.currentTool.stop()
})) this.currentTool = null
}
this.viewport.state.cursorMode = 'normal'
this.viewport.dragControl.dragControls.enabled = true
this.viewport.viewerDom.style.cursor = ''
system.msg('退出新建模式')
}
private _setActiveInteraction(mode: string, startPoint?: THREE.Object3D): void {
const state = this.viewport.state
if (!state.isReady) return
// 如果已经是 normal 模式,则退出交互
if (mode === 'normal') {
this.exitInteraction()
return
}
// 如果已有相同类型的交互,并且起点一致,无需重复初始化
if (this.currentTool?.itemTypeName === mode && this.toolStartObject === startPoint) {
return
}
// 清理之前的交互
this.exitInteraction()
// 获取新的交互实例
const interaction = getInteraction(mode)
if (!interaction) {
system.msg(`not found '${mode}' interaction!`)
return
}
// 保存参数
if (startPoint) {
this.toolStartObject = startPoint
}
// 初始化交互
this.currentTool = interaction
this.viewport.dragControl.dragControls.enabled = false
this.currentTool.start(this.viewport, { startPoint: this.toolStartObject })
// 更新 UI 状态
this.viewport.viewerDom.style.cursor = 'crosshair'
this.viewport.state.cursorMode = mode
system.msg(`enter [${mode}] interaction`)
} }
destory() { destory(): void {
this.exitInteraction()
} }
} }

5
src/core/manager/StateManager.ts

@ -132,7 +132,7 @@ export default class StateManager {
/** /**
* *
*/ */
beginStateUpdate(): void { beginStateUpdate(option: StateUpdateOption = {}): void {
this.lastStateDict = new Map(this.vdata.items.map(item => [item.id, _.cloneDeep(item)])) this.lastStateDict = new Map(this.vdata.items.map(item => [item.id, _.cloneDeep(item)]))
this.changeTracker.added.length = 0 this.changeTracker.added.length = 0
this.changeTracker.removed.length = 0 this.changeTracker.removed.length = 0
@ -534,3 +534,6 @@ export default class StateManager {
} }
} }
export interface StateUpdateOption {
createFromInteraction?: boolean
}

14
src/modules/measure/MeasureInteraction.ts

@ -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 {
}
} }

19
src/modules/measure/MeasureRenderer.ts

@ -24,9 +24,9 @@ export default class MeasureRenderer extends BaseRenderer {
public useHtmlLabel = false public useHtmlLabel = false
pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 }) readonly pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 })
lineMaterial = new LineMaterial({ readonly lineMaterial = new LineMaterial({
color: 0xE63C17, // 主颜色 color: 0xE63C17, // 主颜色
linewidth: 2, // 实际可用的线宽 linewidth: 2, // 实际可用的线宽
vertexColors: true, // 启用顶点颜色 vertexColors: true, // 启用顶点颜色
@ -34,6 +34,13 @@ export default class MeasureRenderer extends BaseRenderer {
alphaToCoverage: true alphaToCoverage: true
}) })
readonly defaultScale: THREE.Vector3 = new THREE.Vector3(0.25, 0.1, 0.25)
readonly defaultRotation: THREE.Vector3 = new THREE.Vector3(0, 0, 0)
constructor(itemTypeName:string) {
super(itemTypeName)
}
createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] {
const geom = new LineGeometry() const geom = new LineGeometry()
const obj = new Line2(geom, this.lineMaterial) const obj = new Line2(geom, this.lineMaterial)
@ -152,4 +159,12 @@ export default class MeasureRenderer extends BaseRenderer {
return label return label
} }
} }
getDefaultScale(): THREE.Vector3 {
return this.defaultScale
}
getDefaultRotation(): THREE.Vector3 {
return this.defaultRotation
}
} }

8
src/modules/measure/index.ts

@ -4,10 +4,12 @@ import MeasureEntity from './MeasureEntity.ts'
import MeasureMeta from './MeasureMeta.ts' import MeasureMeta from './MeasureMeta.ts'
import MeasureInteraction from './MeasureInteraction.ts' import MeasureInteraction from './MeasureInteraction.ts'
export const ITEM_TYPE_NAME = 'measure'
defineModule({ defineModule({
name: 'measure', name: ITEM_TYPE_NAME,
renderer: new MeasureRenderer(), renderer: new MeasureRenderer(ITEM_TYPE_NAME),
interaction: new MeasureInteraction(), interaction: new MeasureInteraction(ITEM_TYPE_NAME),
meta: MeasureMeta, meta: MeasureMeta,
entity: MeasureEntity entity: MeasureEntity
}) })

2
src/types/model.d.ts

@ -12,7 +12,7 @@ interface InteractionCudOption {
* *
*/ */
interface RendererCudOption { interface RendererCudOption {
// Add any additional options needed for create, update, delete operations createFromInteraction: boolean
} }
/** /**

Loading…
Cancel
Save