Browse Source

解决红选问题 redSelection

master
修宁 6 months ago
parent
commit
46de421d83
  1. 58
      src/core/ModelUtils.ts
  2. 4
      src/core/manager/EntityManager.ts
  3. 16
      src/core/manager/ItemFindManager.ts
  4. 29
      src/core/manager/LineSegmentManager.ts
  5. 167
      src/core/manager/SelectManager.ts

58
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

4
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()) {

16
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<string, any>()
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
}
}

29
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<string, LineWrap> = 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
}
}

167
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,22 +77,26 @@ 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)
)
}
// 取消红选
@ -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)
if (event.ctrlKey) {
// 按住 ctrl 键, 就是添加红选内容
this.appendOrRemoveRedSelection(objects[0]?.userData?.entityId)
} else {
const object = objects[0]
this.selectById(object.userData.entityId)
}
} else {
// 如果没有选中任何对象,清除选中状态
@ -362,21 +318,9 @@ export default class SelectManager {
}
}
private disposeRect = () => {
if (this.rectangle !== null) {
private calcSelectionRectAndDispose = (evt?: MouseEvent) => {
if (this.rectangle !== null && evt) {
// 查找在这个矩形内的所有有效业务对象,并将他们添加进 viewport.state.multiSelectedObjects
this.calcRectangleObjectToState()
this.viewport.scene.remove(this.rectangle)
this.rectangle.geometry.dispose()
this.rectangle = null
}
this.recStartPos = null
this.viewport.controls.enabled = true // 启用控制器
}
private calcRectangleObjectToState = () => {
if (!this.rectangle || !this.recStartPos) return
// 获取矩形的包围盒
const box = new THREE.Box3().setFromObject(this.rectangle)
@ -385,39 +329,38 @@ export default class SelectManager {
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
}
this.recStartPos = null
this.viewport.controls.enabled = true // 启用控制器
}
// 查找所有在矩形内的对象
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)
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)
}
// 触发多选对象更新事件
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
})
private calcRedSelection = (startX, startZ, endX, endZ, isAppend: boolean = false) => {
// 查找所有在矩形内的对象
const ids = this.viewport.itemFindManager.getItemsByRect(startX, startZ, endX, endZ)
// 清空之前的多选对象
const idsOrigin = isAppend ? this.viewport.state.multiSelectedEntityIds : []
this.multiSelectByIds(_.uniq(idsOrigin.concat(ids)))
}
}

Loading…
Cancel
Save