import * as THREE from "three"; import {OrbitControls} from "three/examples/jsm/controls/OrbitControls.js"; import {stopRender, renderLoop, render} from "./drawShare" import {DragControls} from './DragControls.js'; let cpmFn, cpdFn, cpuFn; function throttle(func, wait) { let timer: NodeJS.Timeout | null = null; return function (e) { if (timer) return; timer = setTimeout(function () { func(e); timer = null; }, wait) } } /** * three中绘制矩形 * @param {THREE.Scene} scene 场景 **/ export class DrawRect { static RectShapeMaterial = new THREE.MeshBasicMaterial({ transparent: true, opacity: 0.1, color: 0xccebff, side: THREE.FrontSide }); static LineMaterial = new THREE.LineBasicMaterial({ color: 0x15FF00, linewidth: 10, linecap: 'round', linejoin: 'round' }) static HoverLineMaterial = new THREE.LineBasicMaterial({ color: 0xff0015, linewidth: 10, linecap: 'round', linejoin: 'round' }) private canvas: HTMLCanvasElement; private scene: THREE.Scene; private camera: THREE.OrthographicCamera; private controls: OrbitControls | undefined; private signal: (args: { type: 'add' | 'remove' | 'dispatch', name: string, data?: any }) => void; private middleObject: any; private readonly group: THREE.Group; private readonly dragObjects: THREE.Group[] = []; private readonly dragControls: DragControls; private isDrag: boolean = false; private tempModelUuid: string | undefined; private tempRect: THREE.Group | undefined; private downPoint: THREE.Vector2 = new THREE.Vector2(); constructor(canvas: HTMLCanvasElement, scene: THREE.Scene, camera: THREE.OrthographicCamera, signal, middleObject) { this.canvas = canvas; this.scene = scene; this.camera = camera; this.signal = signal; this.middleObject = middleObject; middleObject.markList || (middleObject.markList = []); // 矩形标记数组 middleObject.selectRectIndex || (middleObject.selectRectIndex = -1); // 当前选中的矩形下标 middleObject.selectRect = {}; // 当前选中的矩形数据 middleObject.hoverRectIndex = -1; // 当前鼠标经过的矩形的下标 this.group = new THREE.Group(); this.group.name = "cadDrawRect"; this.scene.add(this.group); this.dragControls = new DragControls(this.dragObjects, camera, canvas); this.dragControls.addEventListener("hoveron", (event) => { if(this.isDrag) return; this.isDrag = true; this.controls && (this.controls.enabled = false); this.group.children.forEach((child, index) => { if (child.userData.rect.modelUuid !== event.object.userData.rect.modelUuid) return; this.middleObject.hoverRectIndex = index; child.children.forEach((c:any) => { if(c.isLine){ c.material = DrawRect.HoverLineMaterial; } }) }); }); this.dragControls.addEventListener("dragstart", () => { // 有上一次选中 if(this.middleObject.selectRectIndex !== -1){ this.group.children[this.middleObject.selectRectIndex]?.children.forEach((c:any) => { if(c.isLine){ c.material = DrawRect.LineMaterial; } }) // 两次选中同一个则取消 if(this.middleObject.selectRectIndex === this.middleObject.hoverRectIndex){ this.middleObject.selectRectIndex = -1; this.middleObject.selectRect = {}; this.signal({ type: "dispatch", name: "objectFocusByUuid", data: [undefined] }) return; } } this.middleObject.selectRectIndex = this.middleObject.hoverRectIndex; this.middleObject.selectRect = this.group.children[this.middleObject.selectRectIndex]?.userData.rect; this.signal({ type: "dispatch", name: "objectFocusByUuid", data: [this.middleObject.selectRect.modelUuid] }) }); this.dragControls.addEventListener("drag", throttle(render,50)); this.dragControls.addEventListener("dragend", (event) => { if (!event.object) return; const rectItem = event.object.userData.rect; rectItem.x += event.object.position.x; rectItem.y += event.object.position.y; this.signal({ type: "dispatch", name: "drawingMarkDone", data: ["update", rectItem] }) }); this.dragControls.addEventListener("hoveroff", () => { if(!this.isDrag) return; this.isDrag = false; this.controls && (this.controls.enabled = true); // 经过的模型未被选择 if(this.middleObject.selectRectIndex !== this.middleObject.hoverRectIndex){ this.group.children[this.middleObject.hoverRectIndex].children.forEach((c:any) => { if(c.isLine){ c.material = DrawRect.LineMaterial; } }) } this.middleObject.hoverRectIndex = -1; render(); }); this.dragControls.enabled = true; cpmFn = throttle(this.onpointermove.bind(this), 16); cpdFn = this.onpointerdown.bind(this); cpuFn = this.onpointerup.bind(this); this.init(); } init() { // 若list长度不为0, 则显示已标记框 if (this.middleObject.markList.length !== 0) { this.middleObject.markList.forEach((item: IDrawingMark) => { const rectObject = this.draw(item); this.dragObjects.push(rectObject); }); } } setList({list}) { this.middleObject.markList = list; this.init(); } // 设置controls setControls(controls: OrbitControls) { this.controls = controls; } // 设置选中 setSelect({modelUuid}){ // 先去除上一次选中 this.group.children[this.middleObject.selectRectIndex]?.children.forEach((c:any) => { if(c.isLine){ c.material = DrawRect.LineMaterial; } }) this.middleObject.selectRectIndex = -1; this.middleObject.selectRect = {}; if(modelUuid === undefined) return; this.group.children.forEach((child, index) => { if (child.userData.rect?.modelUuid !== modelUuid) return; this.middleObject.selectRectIndex = index; this.middleObject.selectRect = child.userData.rect; child.children.forEach((c:any) => { if(c.isLine){ c.material = DrawRect.HoverLineMaterial; } }) }); } // 还原rect restoreRect({rect}) { this.group.children.forEach((child, index) => { if (child.userData.rect.modelUuid !== rect.modelUuid) return; const re = JSON.parse(JSON.stringify(child.userData.rect)); this.middleObject.markList.splice(index, 1, re); this.group.remove(child); const rectObject = this.draw(re); this.dragObjects.splice(this.dragObjects.findIndex(item => item.userData.rect.modelUuid === rect.modelUuid), 1, rectObject); }) } /** * 准备开始画矩形标记框 */ addRect({modelUuid}) { if (!this.controls) return; this.tempModelUuid = modelUuid; this.controls.enabled = false; this.dragControls.enabled = false; this.canvas.style.cursor = "crosshair"; stopRender(); this.canvas.addEventListener("pointerdown", cpdFn); } // 删除当前选中的模型 deleteRect({modelUuid}){ this.group.children.forEach((child, index) => { if (child.userData.rect.modelUuid !== modelUuid) return; this.middleObject.markList.splice(index, 1); this.group.remove(child); this.dragObjects.splice(this.dragObjects.findIndex(item => item.userData.rect.modelUuid === modelUuid), 1); }) } // 鼠标按下事件 onpointerdown(event: PointerEvent) { event.stopPropagation(); if (this.isDrag || event.button !== 0) return; const wp = this.screenToScenePosition(event); this.downPoint = new THREE.Vector2(wp.x, wp.y); this.canvas.addEventListener("pointermove", cpmFn); this.canvas.addEventListener("pointerup", cpuFn); } // 鼠标移动事件 onpointermove(event: PointerEvent) { event.stopPropagation(); if (this.isDrag) return; const wp = this.screenToScenePosition(event); this.tempRect && this.group.remove(this.tempRect); const item: IDrawingMark = { x: this.downPoint.x, y: this.downPoint.y, w: wp.x - this.downPoint.x, h: wp.y - this.downPoint.y, modelUuid: this.tempModelUuid }; this.tempRect = this.draw(item); } // 鼠标抬起事件 onpointerup(event: PointerEvent) { event.stopPropagation(); if (this.isDrag) return; const wp = this.screenToScenePosition(event); const item: IDrawingMark = { x: this.downPoint.x, y: this.downPoint.y, w: wp.x - this.downPoint.x, h: wp.y - this.downPoint.y, modelUuid: this.tempModelUuid }; this.drawDone(item) this.signal({ type: "dispatch", name: "drawingMarkDone", data: ["add", item] }) this.canvas.style.cursor = "default"; this.tempModelUuid = undefined; this.canvas.removeEventListener("pointermove", cpmFn); this.canvas.removeEventListener("pointerup", cpuFn); } // 取消设置名称去除对应绘制的方法 clearTemp() { this.tempRect && this.group.remove(this.tempRect); this.exit(); } // 名称设置完成后调用的绘制结束方法 drawDone(rect:IDrawingMark) { this.tempRect && this.group.remove(this.tempRect); this.middleObject.markList.push(rect); const rectObject = this.draw(rect); this.dragObjects.push(rectObject); this.exit(); } exit() { this.controls && (this.controls.enabled = true); this.dragControls.enabled = true; renderLoop(); this.canvas.removeEventListener("pointerdown", cpdFn); } // 绘制 draw(item: IDrawingMark) { /* 第二种方式 */ const g = new THREE.Group(); g.name = "mark-rect" const rectShape = new THREE.Shape(); rectShape.moveTo(item.x, item.y); rectShape.lineTo(item.x + item.w, item.y); rectShape.lineTo(item.x + item.w, item.y + item.h); rectShape.lineTo(item.x, item.y + item.h); rectShape.lineTo(item.x, item.y); const geometry = new THREE.ShapeGeometry(rectShape); const material = DrawRect.RectShapeMaterial; const mesh = new THREE.Mesh(geometry, material); g.add(mesh); //绘制边框线 const lineGeom = new THREE.EdgesGeometry(geometry) const lineMaterial = DrawRect.LineMaterial; const line = new THREE.LineSegments(lineGeom, lineMaterial) line.scale.copy(mesh.scale) line.rotation.copy(mesh.rotation) line.position.copy(mesh.position) g.add(line) g.userData.rect = item; this.group.add(g); // helpRender(); render(); return g; } screenToScenePosition(event) { const offsetX = event.clientX - this.canvas.offsetLeft; const offsetY = event.clientY - this.canvas.offsetTop; const screenPosition = new THREE.Vector3(offsetX, offsetY, 0); //const worldPosition = new THREE.Vector3(); screenPosition.x = (screenPosition.x / this.canvas.clientWidth) * 2 - 1; screenPosition.y = -(screenPosition.y / this.canvas.clientHeight) * 2 + 1; screenPosition.z = 0.5; let p = screenPosition.unproject(this.camera); return new THREE.Vector2(p.x, p.y); // screenPosition.sub(this.camera.position).normalize(); // const distance = -this.camera.position.z / screenPosition.z; // worldPosition.copy(this.camera.position).add(screenPosition.multiplyScalar(distance)); // // return worldPosition; } }