6 changed files with 225 additions and 40 deletions
@ -1,5 +1,7 @@ |
|||||
export interface ITool { |
export interface ITool { |
||||
init(viewport: any): void |
init(viewport: any): void |
||||
|
|
||||
destory(): void |
destory(): void |
||||
|
|
||||
|
animate?: () => void; |
||||
} |
} |
||||
@ -0,0 +1,195 @@ |
|||||
|
import * as THREE from 'three' |
||||
|
import type { ITool } from './ITool.ts' |
||||
|
import { watch } from 'vue' |
||||
|
import type Viewport from '@/designer/Viewport.ts' |
||||
|
import { Line2 } from 'three/examples/jsm/lines/Line2.js' |
||||
|
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js' |
||||
|
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js' |
||||
|
|
||||
|
let pdFn, pmFn, puFn |
||||
|
|
||||
|
/** |
||||
|
* 选择工具,用于在设计器中显示选中对象的包围盒 |
||||
|
*/ |
||||
|
export default class SelectInspect implements ITool { |
||||
|
viewport: Viewport |
||||
|
/** |
||||
|
* 线框材质,用于显示选中对象的包围盒 |
||||
|
*/ |
||||
|
material: LineMaterial = new LineMaterial({ color: 0xffff00, linewidth: 2 }) |
||||
|
|
||||
|
/** |
||||
|
* 矩形材质,用于显示鼠标拖拽选择的矩形区域 |
||||
|
*/ |
||||
|
rectMaterial: THREE.MeshBasicMaterial = new THREE.MeshBasicMaterial({ |
||||
|
color: 0x000000, |
||||
|
opacity: 0.5, |
||||
|
transparent: true |
||||
|
}) |
||||
|
|
||||
|
/** |
||||
|
* 当前选中对象的矩形选择框 |
||||
|
*/ |
||||
|
rectangle: THREE.Mesh | null = null |
||||
|
|
||||
|
/** |
||||
|
* 当前选中对象的包围盒线框 |
||||
|
*/ |
||||
|
selectionBox: Line2 |
||||
|
|
||||
|
/** |
||||
|
* 当前鼠标所在的画布, 对应 viewport.renderer.domElement |
||||
|
*/ |
||||
|
canvas: HTMLCanvasElement |
||||
|
|
||||
|
/** |
||||
|
* 鼠标按下时记录的起始位置,用于绘制矩形选择框 |
||||
|
*/ |
||||
|
recStartPos: THREE.Vector3 | null |
||||
|
|
||||
|
constructor() { |
||||
|
} |
||||
|
|
||||
|
init(viewport: Viewport) { |
||||
|
this.viewport = viewport |
||||
|
this.canvas = this.viewport.renderer.domElement as HTMLCanvasElement |
||||
|
|
||||
|
pdFn = this.onMouseDown.bind(this) |
||||
|
this.canvas.addEventListener('pointerdown', pdFn) |
||||
|
pmFn = this.onMouseMove.bind(this) |
||||
|
this.canvas.addEventListener('pointermove', pmFn) |
||||
|
puFn = this.onMouseUp.bind(this) |
||||
|
this.canvas.addEventListener('pointerup', puFn) |
||||
|
|
||||
|
this.viewport.watchList.push(watch(() => this.viewport.state.selectedObject, (selectedObject) => { |
||||
|
this.disposeSelectionBox() |
||||
|
|
||||
|
const expandAmount = 0.2 // 扩展包围盒的大小
|
||||
|
if (selectedObject !== null) { |
||||
|
// 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb)
|
||||
|
selectedObject.updateWorldMatrix(false, true) |
||||
|
|
||||
|
const box = new THREE.Box3().setFromObject(selectedObject) |
||||
|
box.expandByScalar(expandAmount) |
||||
|
|
||||
|
const size = new THREE.Vector3() |
||||
|
box.getSize(size) |
||||
|
|
||||
|
const center = new THREE.Vector3() |
||||
|
box.getCenter(center) |
||||
|
|
||||
|
// 创建包围盒几何体
|
||||
|
const helperGeometry = new THREE.BoxGeometry(size.x, size.y, size.z) |
||||
|
const edgesGeometry = new THREE.EdgesGeometry(helperGeometry) |
||||
|
|
||||
|
// 使用 LineGeometry 包装 edgesGeometry
|
||||
|
const lineGeom = new LineGeometry() |
||||
|
//@ts-ignore
|
||||
|
lineGeom.setPositions(edgesGeometry.attributes.position.array) |
||||
|
|
||||
|
const selectionBox = new Line2(lineGeom, this.material) |
||||
|
selectionBox.computeLineDistances() |
||||
|
selectionBox.position.copy(center) |
||||
|
selectionBox.name = 'selectionBox' |
||||
|
this.selectionBox = selectionBox |
||||
|
|
||||
|
this.viewport.scene.add(selectionBox) |
||||
|
} |
||||
|
})) |
||||
|
} |
||||
|
|
||||
|
destory() { |
||||
|
|
||||
|
this.canvas.removeEventListener('pointerdown', pdFn) |
||||
|
pdFn = undefined |
||||
|
this.canvas.removeEventListener('pointermove', pmFn) |
||||
|
pmFn = undefined |
||||
|
this.canvas.removeEventListener('pointerup', puFn) |
||||
|
puFn = undefined |
||||
|
|
||||
|
// 销毁选择工具
|
||||
|
this.disposeSelectionBox() |
||||
|
this.disposeRect() |
||||
|
} |
||||
|
|
||||
|
disposeSelectionBox() { |
||||
|
if (this.selectionBox) { |
||||
|
this.viewport.scene.remove(this.selectionBox) |
||||
|
this.selectionBox.geometry.dispose() |
||||
|
this.selectionBox = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
createRectangle() { |
||||
|
if (this.rectangle !== null) { |
||||
|
this.disposeRect() |
||||
|
} |
||||
|
if (this.recStartPos) { |
||||
|
// 创建矩形
|
||||
|
this.rectangle = new THREE.Mesh( |
||||
|
new THREE.PlaneGeometry(1, 1), |
||||
|
this.rectMaterial |
||||
|
) |
||||
|
this.rectangle.name = 'selectRectangle' |
||||
|
this.rectangle.rotation.x = -Math.PI / 2 // 关键!让平面正对相机
|
||||
|
this.rectangle.position.set( |
||||
|
this.recStartPos.x, |
||||
|
this.recStartPos.y, |
||||
|
this.recStartPos.z |
||||
|
) |
||||
|
this.viewport.scene.add(this.rectangle) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateRectangle(position: THREE.Vector3) { |
||||
|
if (!this.rectangle || !this.recStartPos) return |
||||
|
// console.log('updateRectangle', this.recStartPos, position)
|
||||
|
|
||||
|
const width = position.x - this.recStartPos.x |
||||
|
const height = position.z - this.recStartPos.z |
||||
|
|
||||
|
const newWidth = Math.abs(width) |
||||
|
const newHeight = Math.abs(height) |
||||
|
|
||||
|
// 清理旧几何体
|
||||
|
this.rectangle.geometry.dispose() |
||||
|
this.rectangle.geometry = new THREE.PlaneGeometry(newWidth, newHeight) |
||||
|
this.rectangle.position.set( |
||||
|
this.recStartPos.x + width / 2, |
||||
|
this.recStartPos.y, |
||||
|
this.recStartPos.z + height / 2 |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
onMouseDown(event: MouseEvent) { |
||||
|
if (event.shiftKey) { |
||||
|
// 记录鼠标按下位置
|
||||
|
this.recStartPos = this.viewport.getClosestIntersection(event) |
||||
|
this.createRectangle() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
onMouseMove(event: MouseEvent) { |
||||
|
if (!this.recStartPos) { |
||||
|
this.disposeRect() |
||||
|
} |
||||
|
// 更新矩形大小或重新生成矩形
|
||||
|
const position = this.viewport.getClosestIntersection(event) |
||||
|
if (!position) return |
||||
|
this.updateRectangle(position) |
||||
|
} |
||||
|
|
||||
|
disposeRect() { |
||||
|
if (this.rectangle !== null) { |
||||
|
this.viewport.scene.remove(this.rectangle) |
||||
|
this.rectangle.geometry.dispose() |
||||
|
this.rectangle = null |
||||
|
} |
||||
|
this.recStartPos = null |
||||
|
} |
||||
|
|
||||
|
onMouseUp(event: MouseEvent) { |
||||
|
this.disposeRect() |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue