6 changed files with 225 additions and 40 deletions
@ -1,5 +1,7 @@ |
|||
export interface ITool { |
|||
export interface ITool { |
|||
init(viewport: any): 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