diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 64d8e8a..cfdf2f7 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -13,6 +13,7 @@ import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2' import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial' import { Line2 } from 'three/examples/jsm/lines/Line2' import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry' +import LineSegmentManager from '@/core/manager/LineSegmentManager.ts' /** * 计算点集凸包(2D) @@ -696,6 +697,63 @@ export function getAABBox(matrix: THREE.Matrix4 | ItemJson): THREE.Vector3[] { return edgePositions } +export function drawOBBoxUseLineManager(lineId: string, lineManager: LineSegmentManager, matrix: THREE.Matrix4 | ItemJson, padding: number = 0) { +// 定义单位立方体的边对应的顶点索引 + const edges = [ + [0, 1], [1, 2], [2, 3], [3, 0], + [4, 5], [5, 6], [6, 7], [7, 4], + // [0, 4], [1, 5], [2, 6], [3, 7] + [0, 4], [4, 5], + [5, 1], [1, 2], + [2, 6], [6, 7], + [7, 3] + ] + + // 定义单位立方体的8个顶点位置 + const verticesParam = [ + -0.5 - padding, -0.5, 0.5 + padding, + 0.5 + padding, -0.5, 0.5 + padding, + 0.5 + padding, 0.5, 0.5 + padding, + -0.5 - padding, 0.5, 0.5 + padding, + -0.5 - padding, -0.5, -0.5 - padding, + 0.5 + padding, -0.5, -0.5 - padding, + 0.5 + padding, 0.5, -0.5 - padding, + -0.5 - padding, 0.5, -0.5 - padding + ] + + if (!(matrix instanceof THREE.Matrix4)) { + // 从 tf 读取的模型, 需要调整Y轴以使底面位于Y=0 + matrix = getMatrixFromTf(matrix.tf) + for (let i = 1; i < verticesParam.length; i += 3) { + verticesParam[i] = verticesParam[i] + 0.5 + } + } + + const vertices = new Float32Array(verticesParam) + // 将顶点通过变换矩阵转换到世界坐标系中 + const transformedVertices = [] + for (let i = 0; i < vertices.length; i += 3) { + const vec = new THREE.Vector3(vertices[i], vertices[i + 1], vertices[i + 2]) + vec.applyMatrix4(matrix) + transformedVertices.push(vec.x, vec.y, vec.z) + } + + // 合并所有边的顶点信息 + edges.forEach((edge, idx) => { + const start = new THREE.Vector3( + transformedVertices[edge[0] * 3], + transformedVertices[edge[0] * 3 + 1], + transformedVertices[edge[0] * 3 + 2] + ) + const end = new THREE.Vector3( + transformedVertices[edge[1] * 3], + transformedVertices[edge[1] * 3 + 1], + transformedVertices[edge[1] * 3 + 2] + ) + lineManager.createOrUpdateLine(lineId + '_' + idx, start, end) + }) +} + /** * 根据变换矩阵绘制 OBB 包围盒, 注意返回 Line2 对象 * @param matrix 物体的变换矩阵或 ItemJson 对象 diff --git a/src/core/manager/EntityManager.ts b/src/core/manager/EntityManager.ts index d61d1f7..1838b07 100644 --- a/src/core/manager/EntityManager.ts +++ b/src/core/manager/EntityManager.ts @@ -51,6 +51,10 @@ export default class EntityManager { return Array.from(this.___entityMap.values()) } + getEntityMapForFindManager() { + return this.___entityMap + } + dispose() { // 清理所有差量渲染器 for (const renderer of this.diffRenderer.values()) { diff --git a/src/core/manager/ItemFindManager.ts b/src/core/manager/ItemFindManager.ts index 5390e58..451bd8c 100644 --- a/src/core/manager/ItemFindManager.ts +++ b/src/core/manager/ItemFindManager.ts @@ -9,6 +9,7 @@ import type Viewport from '@/core/engine/Viewport.ts' // 主管理器类 export default class ItemFindManager { private items = new Map() + private viewport: Viewport dispose() { this.items.clear() @@ -18,6 +19,7 @@ export default class ItemFindManager { } init(viewport: Viewport) { + this.viewport = viewport } // 添加或更新物品 @@ -40,6 +42,18 @@ export default class ItemFindManager { // 矩形区域查询(有交集) getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { - return [] + // 查找矩形框内所有对象 + const from = new THREE.Vector2(x1, z1) + const to = new THREE.Vector2(x2, z2) + const ids = [] + for (const [id, item] of this.viewport.entityManager.getEntityMapForFindManager()) { + const vector = new THREE.Vector2(item.tf[0][0], item.tf[0][2]) + if (vector.x >= from.x && vector.x <= to.x && + vector.y >= from.y && vector.y <= to.y) { + ids.push(id) + } + } + + return ids } } diff --git a/src/core/manager/LineSegmentManager.ts b/src/core/manager/LineSegmentManager.ts index 92ec5a7..7807ebc 100644 --- a/src/core/manager/LineSegmentManager.ts +++ b/src/core/manager/LineSegmentManager.ts @@ -10,7 +10,7 @@ import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeome */ export default class LineSegmentManager { private readonly viewport: Viewport - private readonly lineGeometry: LineSegmentsGeometry + private lineGeometry: LineSegmentsGeometry private readonly lineMaterial: LineMaterial private readonly lineSegments: LineSegments2 private readonly segments: Map = new Map() @@ -27,7 +27,7 @@ export default class LineSegmentManager { // 创建线段的渲染对象 this.lineSegments = new LineSegments2(this.lineGeometry, this.lineMaterial) this.lineSegments.name = name - this.lineSegments.frustumCulled = false + this.lineSegments.frustumCulled = true this.viewport.scene.add(this.lineSegments) } @@ -124,6 +124,8 @@ export default class LineSegmentManager { // 初始化或调整数组大小 if (!this.positionArray || this.positionArray.length !== positionCount) { this.positionArray = new Float32Array(positionCount) + this.lineGeometry.dispose() // 这是个bug, 线段数量改变时, 渲染总数不更新! 必须重新释放一次 + this.lineGeometry = new LineSegmentsGeometry() } if (!this.colorArray || this.colorArray.length !== colorCount) { @@ -175,13 +177,22 @@ export default class LineSegmentManager { this.colorArray![colorIndex++] = endColor.b }) - // 更新几何体 + // // 更新几何体 + // this.lineGeometry.setPositions(this.positionArray) + // this.lineGeometry.setColors(this.colorArray) + // this.lineSegments.computeLineDistances() + // this.lineSegments.matrixWorldNeedsUpdate = true + // this.lineGeometry.instanceCount = this.segments.size + // + // console.log('draw line geometry', this.segments.size) + // this.needsUpdate = false + this.lineGeometry.setPositions(this.positionArray) this.lineGeometry.setColors(this.colorArray) - // 设置实例计数为可见线段数量 - // this.lineGeometry.instanceCount = Array.from(this.segments.values()).filter(s => s.visible).length - this.needsUpdate = false + this.lineSegments.geometry = this.lineGeometry // 更新引用 + this.lineSegments.computeLineDistances() + this.lineSegments.matrixWorldNeedsUpdate = true } dispose() { @@ -201,6 +212,12 @@ export default class LineSegmentManager { this.colorArray = null } + clear() { + this.segments.clear() + this.positionArray = null + this.colorArray = null + this.needsUpdate = true + } } diff --git a/src/core/manager/SelectManager.ts b/src/core/manager/SelectManager.ts index 1361ee8..721c0ae 100644 --- a/src/core/manager/SelectManager.ts +++ b/src/core/manager/SelectManager.ts @@ -9,7 +9,9 @@ import { getSetter } from '@/core/manager/ModuleManager.ts' import Constract from '@/core/Constract.ts' import type { Object3DLike } from '@/types/ModelTypes.ts' import { PointManageWrap } from '@/core/manager/InstancePointManager.ts' -import { drawOBBox, getAABBox, getOBBox } from '@/core/ModelUtils.ts' +import { drawOBBox, drawOBBoxUseLineManager, getAABBox, getOBBox } from '@/core/ModelUtils.ts' +import InstanceMeshManager from '@/core/manager/InstanceMeshManager.ts' +import LineSegmentManager from '@/core/manager/LineSegmentManager.ts' /** * 选择工具,用于在设计器中显示选中对象的包围盒 @@ -75,24 +77,28 @@ export default class SelectManager { // EventBus.on('multiselectedObjectChanged', this.updateMultiSelectionBoxes) } - redSelectionGroup = new THREE.Group() - updateMultiSelectionBoxes = () => { - const multiSelectedObjects = this.viewport.state.multiSelectedObjects + const multiSelectedItems = this.viewport.state.multiSelectedItems // 为所有多选对象创建包围盒线框 - this.clearRedSelectionBoxes() + this.redSelectionBoxManager.clear() - if (!multiSelectedObjects || multiSelectedObjects.length === 0) { + if (!multiSelectedItems || multiSelectedItems.length === 0) { return } - for (const object of multiSelectedObjects) { - if (object.userData.entityId) { - this.createRedSelectionBox(object) - } + console.log('drawRedSelection', multiSelectedItems.length) + for (const item of multiSelectedItems) { + drawOBBoxUseLineManager(item.id + '_red', this.redSelectionBoxManager, item) } } + get redSelectionBoxManager(): LineSegmentManager { + const name = 'redSelectionBoxManager' + return this.viewport.getOrCreateLineManager(name, () => + new LineSegmentManager(name, this.viewport, this.redMaterial) + ) + } + // 取消红选 cancelMultiSelect() { this.viewport.state.multiSelectedObjects = [] @@ -185,62 +191,6 @@ export default class SelectManager { }) } - // 清除之前的红色包围盒线框 - private clearRedSelectionBoxes() { - if (this.redSelectionGroup.children.length > 0) { - for (const child of this.redSelectionGroup.children) { - this.redSelectionGroup.remove(child) - } - } - this.viewport.scene.remove(this.redSelectionGroup) - this.redSelectionGroup = new THREE.Group() - this.viewport.scene.add(this.redSelectionGroup) - } - - // 创建红选包围盒 - private createRedSelectionBox(object: Object3DLike) { - // 如果对象没有 entityId,则不创建包围盒线框 - if (!object.userData.entityId) { - return - } - let box: THREE.Box3 - if (object instanceof PointManageWrap) { - box = object.createBox3() - - } else if (object instanceof THREE.Object3D) { - box = new THREE.Box3().setFromObject(object) - } - - const min = box.min - const max = box.max - - const corners = [ - new THREE.Vector3(min.x - Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, min.z - Constract.RED_EXPAND_AMOUNT), - new THREE.Vector3(max.x + Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, min.z - Constract.RED_EXPAND_AMOUNT), - new THREE.Vector3(max.x + Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, max.z + Constract.RED_EXPAND_AMOUNT), - new THREE.Vector3(min.x - Constract.RED_EXPAND_AMOUNT, max.y + Constract.RED_EXPAND_AMOUNT, max.z + Constract.RED_EXPAND_AMOUNT) - ] - - // 构建矩形边框(4 条边) - const positions = [] - for (let i = 0; i < 4; i++) { - const p1 = corners[i] - const p2 = corners[(i + 1) % 4] - positions.push(p1.x, p1.y, p1.z) - positions.push(p2.x, p2.y, p2.z) - } - - // 创建几何体 - const lineGeom = new LineGeometry() - const vertices = new Float32Array(positions) - lineGeom.setPositions(vertices) - - const selectionBox = new Line2(lineGeom, this.redMaterial) - selectionBox.computeLineDistances() - - this.redSelectionGroup.add(selectionBox) - } - // 更新选中对象的包围盒线框 private updateSelectionBox = () => { this.clearSelectionBox() @@ -263,8 +213,7 @@ export default class SelectManager { // 销毁选择工具 this.clearSelectionBox() - this.disposeRect() - this.clearRedSelectionBoxes() + this.calcSelectionRectAndDispose() } // 清除当前选中对象的包围盒线框 @@ -278,7 +227,7 @@ export default class SelectManager { private createRectangle = () => { if (this.rectangle !== null) { - this.disposeRect() + this.calcSelectionRectAndDispose() } if (this.recStartPos) { // 创建矩形 @@ -334,7 +283,7 @@ export default class SelectManager { private onMouseMove = (event: MouseEvent) => { if (!this.recStartPos) { - this.disposeRect() + this.calcSelectionRectAndDispose() } // 更新矩形大小或重新生成矩形 const position = this.viewport.getClosestIntersection(event) @@ -344,7 +293,7 @@ export default class SelectManager { private onMouseUp = (event: MouseEvent) => { if (event.button !== 0) return - this.disposeRect() + this.calcSelectionRectAndDispose(event) const clickTime = this.clickTime this.clickTime = null if (Date.now() - clickTime < Constract.MOUSE_CLICK_DELAY) { @@ -352,8 +301,15 @@ export default class SelectManager { const objects: Object3DLike[] = this.viewport.entityManager.getObjectByCanvasMouse(event) if (objects.length > 0 && objects[0]?.userData?.entityId && objects[0]?.userData?.createType !== 'line') { console.log('mouseClick', objects) - const object = objects[0] - this.selectById(object.userData.entityId) + + if (event.ctrlKey) { + // 按住 ctrl 键, 就是添加红选内容 + this.appendOrRemoveRedSelection(objects[0]?.userData?.entityId) + + } else { + const object = objects[0] + this.selectById(object.userData.entityId) + } } else { // 如果没有选中任何对象,清除选中状态 @@ -362,10 +318,20 @@ export default class SelectManager { } } - private disposeRect = () => { - if (this.rectangle !== null) { + private calcSelectionRectAndDispose = (evt?: MouseEvent) => { + if (this.rectangle !== null && evt) { // 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects - this.calcRectangleObjectToState() + // 获取矩形的包围盒 + const box = new THREE.Box3().setFromObject(this.rectangle) + + // 获取盒子的 startX, startZ, endX, endZ + const startX = box.min.x + const startZ = box.min.z + const endX = box.max.x + const endZ = box.max.z + _.defer(() => { + this.calcRedSelection(startX, startZ, endX, endZ, evt.ctrlKey) + }) this.viewport.scene.remove(this.rectangle) this.rectangle.geometry.dispose() this.rectangle = null @@ -374,50 +340,27 @@ export default class SelectManager { this.viewport.controls.enabled = true // 启用控制器 } - private calcRectangleObjectToState = () => { - if (!this.rectangle || !this.recStartPos) return - - // 获取矩形的包围盒 - const box = new THREE.Box3().setFromObject(this.rectangle) - - // 获取盒子的 startX, startZ, endX, endZ - const startX = box.min.x - const startZ = box.min.z - const endX = box.max.x - const endZ = box.max.z + private appendOrRemoveRedSelection(id: string) { + // 如果 id 包含在 this.viewport.state.multiSelectedEntityIds 中,则移除它, 否则 添加它 + const multiSelectedEntityIds = this.viewport.state.multiSelectedEntityIds + const index = _.indexOf(multiSelectedEntityIds, id) + if (index > -1) { + // 如果已经存在,则移除 + multiSelectedEntityIds.splice(index, 1) + } else { + // 否则添加 + multiSelectedEntityIds.push(id) + } + this.multiSelectByIds(multiSelectedEntityIds) + } + private calcRedSelection = (startX, startZ, endX, endZ, isAppend: boolean = false) => { // 查找所有在矩形内的对象 const ids = this.viewport.itemFindManager.getItemsByRect(startX, startZ, endX, endZ) // 清空之前的多选对象 - this.viewport.state.multiSelectedObjects = [] - - // 遍历找到的对象,添加到多选对象中 - const multiSelectedObjects = [] - const multiSelectedItems = [] - const multiSelectedEntityIds = [] - for (const id of ids) { - const object = this.viewport.entityManager.findObjectById(id) - if (object.userData.entityId && object.userData.t) { - const item = this.viewport.entityManager.findItemById(object.userData.entityId) - if (item && item.dt.protected !== true) { - multiSelectedObjects.push(object) - multiSelectedItems.push(item) - multiSelectedEntityIds.push(id) - } - } - } - - // 触发多选对象更新事件 - this.viewport.state.multiSelectedObjects = markRaw(multiSelectedObjects) - this.viewport.state.multiSelectedItems = markRaw(multiSelectedItems) - this.viewport.state.multiSelectedEntityIds = multiSelectedEntityIds - EventBus.dispatch('multiSelectedObjectsChanged', { - viewport: markRaw(this.viewport), - multiSelectedObjects: this.viewport.state.multiSelectedObjects, - multiSelectedItems: this.viewport.state.multiSelectedItems, - multiSelectedEntityIds: this.viewport.state.multiSelectedEntityIds - }) + const idsOrigin = isAppend ? this.viewport.state.multiSelectedEntityIds : [] + this.multiSelectByIds(_.uniq(idsOrigin.concat(ids))) } }