diff --git a/package.json b/package.json index 0570df9..6f35659 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "vite-plugin-vue-devtools": "^7.7.2", "vue-tsc": "^2.2.8", "three": "^0.176.0", - "camera-controls": "2.10.1" + "camera-controls": "2.10.1", + "three-mesh-bvh": "^0.9.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e27d1e..569bd57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -126,6 +126,9 @@ importers: three: specifier: ^0.176.0 version: 0.176.0 + three-mesh-bvh: + specifier: ^0.9.0 + version: 0.9.0(three@0.176.0) typescript: specifier: ~5.8.0 version: 5.8.3 @@ -1490,6 +1493,11 @@ packages: resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==} engines: {node: '>=16'} + three-mesh-bvh@0.9.0: + resolution: {integrity: sha512-xAwZj0hZknpwVsdK5BBJTIAZDjDPZCRzURY1o+z/JHBON/jc2UetK1CzPeQZiiOVSfI4jV2z7sXnnGtgsgnjaA==} + peerDependencies: + three: '>= 0.159.0' + three@0.176.0: resolution: {integrity: sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==} @@ -2978,6 +2986,10 @@ snapshots: dependencies: copy-anything: 3.0.5 + three-mesh-bvh@0.9.0(three@0.176.0): + dependencies: + three: 0.176.0 + three@0.176.0: {} tinyglobby@0.2.13: diff --git a/src/designer/model2DEditor/EsDragControls.ts b/src/designer/model2DEditor/EsDragControls.ts index d74cf90..24cfc12 100644 --- a/src/designer/model2DEditor/EsDragControls.ts +++ b/src/designer/model2DEditor/EsDragControls.ts @@ -9,7 +9,7 @@ import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' let dragStartFn, dragFn, dragEndFn, clickblankFn export default class EsDragControls { - protected _dragObjects: THREE.Object3D[] = [] // 拖拽对象 + _dragObjects: THREE.Object3D[] = [] // 拖拽对象 dragControls: any private onDownPosition: { x: number; y: number } = { x: -1, y: -1 } diff --git a/src/designer/model2DEditor/tools/MouseMoveInspect.ts b/src/designer/model2DEditor/tools/MouseMoveInspect.ts index c95406d..bd03d9a 100644 --- a/src/designer/model2DEditor/tools/MouseMoveInspect.ts +++ b/src/designer/model2DEditor/tools/MouseMoveInspect.ts @@ -1,5 +1,6 @@ import type Viewport from '@/designer/Viewport.ts' import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts' +import * as THREE from 'three' let pmFn, otFn, lvFn @@ -44,6 +45,14 @@ export default class MouseMoveInspect implements ITool { } mouseMove = _.throttle(function(this: MouseMoveInspect, event: MouseEvent) { + + const pointv = new THREE.Vector2() + pointv.x = event.offsetX / this.viewport.renderer.domElement.offsetWidth + pointv.y = event.offsetY / this.viewport.renderer.domElement.offsetHeight + + const mouse = new THREE.Vector2() + mouse.set((pointv.x * 2) - 1, -(pointv.y * 2) + 1) + // 当前鼠标所在的点 const point = this.viewport.getClosestIntersection(event) if (!point) { @@ -56,7 +65,8 @@ export default class MouseMoveInspect implements ITool { window['CurrentMouseInfo'] = { viewport: this.viewport, x: point.x, - z: point.z + z: point.z, + mouse: mouse } }, 1) diff --git a/src/model/ModelUtils.ts b/src/model/ModelUtils.ts index 40b2b39..e309dca 100644 --- a/src/model/ModelUtils.ts +++ b/src/model/ModelUtils.ts @@ -3,7 +3,8 @@ import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' import type { ItemJson } from '@/model/WorldModelType.ts' import { getAllItemTypes, getItemTypeByName } from '@/runtime/DefineItemType.ts' import type Viewport from '@/designer/Viewport.ts' -import type Toolbox from '@/model/itemType/Toolbox.ts' +import { computeBoundsTree, disposeBoundsTree } from 'three-mesh-bvh' +import { Vector2 } from 'three/src/math/Vector2' export function quickCopyByMouse() { // 获取鼠标位置,查看鼠标是否在某个 viewport 的画布上,并取得该 viewport @@ -13,13 +14,20 @@ export function quickCopyByMouse() { return } - // 从当前 mouse.x, mouse.z 获取地图坐标 const viewport: Viewport = currentMouseInfo.viewport - const x = currentMouseInfo.x - const z = currentMouseInfo.z + const point: THREE.Vector2 = currentMouseInfo.mouse - // 如果当前 x,y 命中在某条线上,以当前鼠标为点,插入一个这个线段的起点的相似点,连接两头的点。 + const ray = new THREE.Raycaster() + ray.setFromCamera(point, viewport.camera) + const intersections = ray.intersectObjects(viewport.dragControl._dragObjects, true) + if (intersections.length === 0) { + system.msg('没有找到可复制的对象') + return + } + console.log('intersections:', intersections) + + /* // 如果不在线上,查找0.2米内的有效点 Object3D, 如果有,则以这个点为起点, 延伸同类型的点,并让他们相连 findObject3DByCondition(viewport.scene, object => { // 判断 object 是否是有效的 Object3D, 并且是当前 viewport 的对象 @@ -39,6 +47,68 @@ export function quickCopyByMouse() { } return false }) + */ +} + +/** + * 查找射线周围指定半径内的对象 + */ +export function findObjectsInRadius(viewport: Viewport, + point: THREE.Vector2, + radius: number, + lines: { object: THREE.Object3D, distance: number }[], + points: { object: THREE.Object3D, distance: number }[] +): void { + const ray = new THREE.Raycaster() + ray.setFromCamera(point, viewport.camera) + + viewport.dragControl._dragObjects.forEach(obj => { + if (obj instanceof THREE.Points) { + // 处理点云:遍历每个点 + const distance = distanceToRay(ray, point) + if (distance <= radius) { + points.push({ object: obj, distance }) + } + + } else if (obj instanceof THREE.Line) { + // 处理线段:计算线段到射线的最近距离 + const distance = getLineDistanceToRay(ray, obj) + if (distance <= radius) { + lines.push({ object: obj, distance }) + } + } + }) +} + +/** + * 计算点到射线的最短距离 + */ +function distanceToRay(ray: THREE.Raycaster, point: THREE.Vector2) { + const closestPoint = new THREE.Vector3() + ray.closestPointToPoint(point, closestPoint) + return point.distanceTo(closestPoint) +} + +/** + * 计算线段到射线的最短距离 + */ +function getLineDistanceToRay(ray: THREE.Raycaster, line: THREE.Line) { + const lineStart = new THREE.Vector3() + const lineEnd = new THREE.Vector3() + line.geometry.attributes.position.getXYZ(0, lineStart) + line.geometry.attributes.position.getXYZ(1, lineEnd) + line.localToWorld(lineStart) + line.localToWorld(lineEnd) + + const lineSegment = new THREE.Line3(lineStart, lineEnd) + const closestOnRay = new THREE.Vector3() + const closestOnLine = new THREE.Vector3() + THREE.Line3.prototype.closestPointsRayLine ??= function(ray, line, closestOnRay, closestOnLine) { + // 实现射线与线段最近点计算(需自定义或使用数学库) + } + + lineSegment.closestPointsRayLine(ray, true, closestOnRay, closestOnLine) + return closestOnRay.distanceTo(closestOnLine) } /** diff --git a/src/model/itemType/ItemType.ts b/src/model/itemType/ItemType.ts index aa8dfeb..d0a00b6 100644 --- a/src/model/itemType/ItemType.ts +++ b/src/model/itemType/ItemType.ts @@ -1,6 +1,7 @@ import * as THREE from 'three' import type WorldModel from '@/model/WorldModel.ts' -import type { ItemJson, ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' +import type { ItemTypeDefineOption } from '@/model/itemType/ItemTypeDefine.ts' +import type { ItemJson } from '@/model/WorldModelType.ts' import type Viewport from '@/designer/Viewport.ts' import type Toolbox from '@/model/itemType/Toolbox.ts' diff --git a/src/model/itemType/ItemTypeLine.ts b/src/model/itemType/ItemTypeLine.ts index 1237957..3e8ffeb 100644 --- a/src/model/itemType/ItemTypeLine.ts +++ b/src/model/itemType/ItemTypeLine.ts @@ -1,9 +1,10 @@ import * as THREE from 'three' import ItemType from '@/model/itemType/ItemType.ts' -import type { ItemJson } from '@/model/itemType/ItemTypeDefine.ts' +import type { ItemJson } from '@/model/WorldModelType.ts' import type WorldModel from '@/model/WorldModel.ts' import type Viewport from '@/designer/Viewport.ts' import { findObject3DByCondition, findObject3DById } from '@/model/ModelUtils.ts' +import type { Line2 } from 'three/examples/jsm/lines/Line2.js' let pmFn @@ -18,20 +19,20 @@ export default abstract class ItemTypeLine extends ItemType { abstract createPointBasic(position: THREE.Vector3): THREE.Object3D - abstract createLineBasic(): THREE.Line + abstract createLineBasic(): THREE.Mesh public init(worldModel: WorldModel) { return super.init(worldModel).then(() => { }) } - afterCreateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { + afterCreateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { } - afterUpdateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { + afterUpdateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D): void { } - createLine(scene: THREE.Scene, startPoint: THREE.Object3D, endPoint: THREE.Object3D): THREE.Line { + createLine(scene: THREE.Scene, startPoint: THREE.Object3D, endPoint: THREE.Object3D): THREE.Mesh { const line = this.createLineBasic() const geom = line.geometry geom.setFromPoints([startPoint.position, endPoint.position]) @@ -207,7 +208,7 @@ export default abstract class ItemTypeLine extends ItemType { // 找到 startPoint 与 this.dragPoint 之间的线段 const line = findObject3DByCondition(this.dragViewport.scene, (obj) => { return obj.userData.lineStartId === startPoint.uuid && obj.userData.lineEndId === endPoint.uuid - })[0] as THREE.Line + })[0] as THREE.Mesh if (!line) { // line = this.createLine(this.dragViewport.scene, startPoint, endPoint) diff --git a/src/model/itemType/ToolboxLine.ts b/src/model/itemType/ToolboxLine.ts index a399568..841b8f4 100644 --- a/src/model/itemType/ToolboxLine.ts +++ b/src/model/itemType/ToolboxLine.ts @@ -9,7 +9,7 @@ export default class ToolboxLine extends Toolbox { /** * 临时线条 */ - tempLine?: THREE.Line + tempLine?: THREE.Mesh get itemType(): ItemTypeLine { return this._itemType @@ -19,7 +19,7 @@ export default class ToolboxLine extends Toolbox { return '_measure_temp_point' } - afterMoveTemplateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + afterMoveTemplateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { } stop() { diff --git a/src/model/itemType/measure/Measure.ts b/src/model/itemType/measure/Measure.ts index 38fd467..a8a0324 100644 --- a/src/model/itemType/measure/Measure.ts +++ b/src/model/itemType/measure/Measure.ts @@ -9,6 +9,9 @@ import Viewport from '@/designer/Viewport.ts' import ToolboxLine from '@/model/itemType/ToolboxLine.ts' import MeasureToolbox from '@/model/itemType/measure/MeasureToolbox.ts' import Toolbox from '@/model/itemType/Toolbox.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' export default class Measure extends ItemTypeLine { /** @@ -21,7 +24,7 @@ export default class Measure extends ItemTypeLine { pointMaterial!: Material - lineMaterial!: Material + lineMaterial!: LineMaterial static GROUP_NAME = 'measure-group' static LABEL_NAME = 'measure_label' @@ -31,14 +34,21 @@ export default class Measure extends ItemTypeLine { override init(worldModel: WorldModel): Promise { super.init(worldModel) - this.lineMaterial = new THREE.LineBasicMaterial({ - color: 0xE63C17, - linewidth: 2, - opacity: 0.9, - transparent: true, - side: THREE.DoubleSide, - depthWrite: false, - depthTest: false + // this.lineMaterial = new THREE.LineBasicMaterial({ + // color: 0xE63C17, + // linewidth: 2, + // opacity: 0.9, + // transparent: true, + // side: THREE.DoubleSide, + // depthWrite: false, + // depthTest: false + // }) + this.lineMaterial = new LineMaterial({ + color: 0xE63C17, // 主颜色 + linewidth: 2, // 实际可用的线宽 + vertexColors: true, // 启用顶点颜色 + dashed: false, + alphaToCoverage: true }) this.pointMaterial = new THREE.MeshBasicMaterial({ color: 0x303133, transparent: true, opacity: 0.9 }) @@ -62,6 +72,7 @@ export default class Measure extends ItemTypeLine { createToolbox(viewport: Viewport): ToolboxLine { const toolbox = new MeasureToolbox(this.group) toolbox.init(viewport, this) + //@ts-ignore return toolbox } @@ -105,9 +116,9 @@ export default class Measure extends ItemTypeLine { /** * 创建测量线 */ - createLineBasic(): THREE.Line { - const geom = new THREE.BufferGeometry() - const obj = new THREE.Line(geom, this.lineMaterial) + createLineBasic(): Line2 { + const geom = new LineGeometry() + const obj = new Line2(geom, this.lineMaterial) obj.frustumCulled = false obj.name = Measure.LINE_NAME obj.uuid = THREE.MathUtils.generateUUID() @@ -115,7 +126,7 @@ export default class Measure extends ItemTypeLine { } // 创建完线之后,创建 label - afterCreateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + afterCreateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { super.afterCreateLine(line, startPoint, endPoint) const p0 = startPoint.position @@ -134,7 +145,7 @@ export default class Measure extends ItemTypeLine { this.group.add(labelObj) } - afterUpdateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + afterUpdateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { super.afterUpdateLine(line, startPoint, endPoint) const p0 = startPoint.position diff --git a/src/model/itemType/measure/MeasureToolbox.ts b/src/model/itemType/measure/MeasureToolbox.ts index aa5d135..6f1b3f1 100644 --- a/src/model/itemType/measure/MeasureToolbox.ts +++ b/src/model/itemType/measure/MeasureToolbox.ts @@ -50,7 +50,7 @@ export default class MeasureToolbox extends ToolboxLine { this.measure.group.add(object) } - afterMoveTemplateLine(line: THREE.Line, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { + afterMoveTemplateLine(line: THREE.Mesh, startPoint: THREE.Object3D, endPoint: THREE.Object3D) { super.afterMoveTemplateLine(line, startPoint, endPoint) const p0 = startPoint.position