diff --git a/doc/OBB矩形包围盒API.md b/doc/OBB矩形包围盒API.md index 6f924a6..900066d 100644 --- a/doc/OBB矩形包围盒API.md +++ b/doc/OBB矩形包围盒API.md @@ -59,27 +59,27 @@ deleteItem(id: string) --- -### getItemsByPosition(x:number, z:number): ItemJson[] +### getItemsByPosition(x:number, z:number): string[] -根据位置,获取命中的物品集合 +根据位置,获取命中的物品ID集合 -getItemsByPosition({x:number, z:number}): ItemJson[] +getItemsByPosition({x:number, z:number}): string[] --- -### getItemsByPositionDistance(x, z, distance): {item, distance}[] +### getItemsByPositionDistance(x, z, distance): {id:string, distance:number}[] -getItemsByPositionDistance(x:number, z:number, distance:number): {item:ItemJson, distance:number}[] +getItemsByPositionDistance(x:number, z:number, distance:number): {id:string, distance:number}[] -根据位置,获取周边单位距离内的所有物品集合,及距离 +根据位置,获取周边单位距离内的所有物品ID集合,及他们的距离 --- -### getItemsByRect(x1,y1,x2,y2): ItemJson[] +### getItemsByRect(x1,y1,x2,y2): string[] -根据矩形,获取与矩形有碰撞的所有物品集合 +给出一个选择框(矩形)2个顶点,根据矩形,获取与矩形有碰撞的所有物品集合 getItemsByPosition({x:number, z:number}): ItemJson[] diff --git a/src/core/ItemObbManager.ts b/src/core/ItemObbManager.ts new file mode 100644 index 0000000..a8e9044 --- /dev/null +++ b/src/core/ItemObbManager.ts @@ -0,0 +1,88 @@ +// 开发一个物品 OBB 包围盒管理器 +// 借助 three-mesh-bvh 库来实现 +// 里面所有的物品不用管渲染(不用去到 Scene / 不用管纹理和材质),但期望他最大程度的使用 BufferGeometry 用显卡来运算 + +import * as THREE from 'three' + +/** + * 物品 OBB 包围盒管理器 + * 点位数超过 100,000 个, 基于物品的 OBB 包围盒进行各种查询操作 + * 并可能存在频繁且少量的点位更新/删除操作 + */ +export default class ItemObbManager { + /** + * 添加或更新物品 (根据ID) + */ + addOrUpdate(...items: ItemMetrix[]): void { + } + + /** + * 根据 ID 移除物品 + */ + remove(...ids: string[]): void { + } + + /** + * 根据位置,获取命中到物品OBB包围盒的所有物品ID集合 + * @param x 位置X坐标 + * @param z 位置Z坐标 + */ + getItemsByPosition(x: number, z: number): string[] { + return [] + } + + /** + * 根据位置和距离,获取给定范围内,OBB包围盒碰到的所有物品ID集合 + * @param x 位置X坐标 + * @param z 位置Z坐标 + * @param distance 范围距离 + */ + getItemsByDistance(x: number, z: number, distance: number): string[] { + // Implementation for getting items by distance + return [] + } + + /** + * 给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒与矩形有交集的所有物品ID集合 + */ + getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] { + return [] + } + + /** + * 给定一个矩形区域 (x1,z1)->(x2,z2),获取物品OBB包围盒完全在矩形内的物品ID集合 + */ + getItemsByRectInclude(): string[] { + return [] + } +} + +export interface ItemMetrix { + /** + * 物体ID, 唯一标识 + */ + id: string + + /** + * 变换矩阵, 3x3矩阵, X轴正增长向右, Y轴正增长向屏幕外, Z轴正增长向下。右手坐标系 + */ + tf: [ + /** + * 平移向量 position, 三维坐标 + * [0]=x轴向右, [1]=y轴高度向屏幕外, [2]=z轴向下 + */ + [number, number, number], + + /** + * 旋转向量 rotation, 单位为度 + * [0]=X轴逆向旋转角度, [1]=Y轴逆向旋转角度, [2]=Z轴逆向旋转角度 + * 对应 three.js 应进行"角度"转"弧度"的换算 + */ + [number, number, number], + + /** + * 缩放向量 scale, 三维缩放比例, [0]=X宽度, [1]=Y高度, [2]=Z长度 + */ + [number, number, number], + ] +} diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index c6ec134..4a6eab4 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -599,14 +599,115 @@ export function load3DModule(arrayBuffer: ArrayBuffer | string, ext: string) { } export function getMatrixFromTf(tf: number[][]): THREE.Matrix4 { - const dummy = new THREE.Object3D() - dummy.position.set(tf[0][0], tf[0][1], tf[0][2]) - dummy.rotation.set( - THREE.MathUtils.degToRad(tf[1][0]), - THREE.MathUtils.degToRad(tf[1][1]), - THREE.MathUtils.degToRad(tf[1][2]) + const [pos, rot, scale] = tf + const matrix = new THREE.Matrix4() + matrix.compose( + new THREE.Vector3(pos[0], pos[1], pos[2]), + new THREE.Quaternion().setFromEuler(new THREE.Euler( + THREE.MathUtils.degToRad(rot[0]), + THREE.MathUtils.degToRad(rot[1]), + THREE.MathUtils.degToRad(rot[2]), + 'XYZ' + )), + new THREE.Vector3(scale[0], scale[1], scale[2]) ) - dummy.scale.set(tf[2][0], tf[2][1], tf[2][2]) - dummy.updateMatrix() - return dummy.matrix + return matrix +} + +/** + * 获取物体的 AABBox 包围盒顶点 + * @param matrix 物体的变换矩阵或 ItemJson 对象 + */ +export function getAABBox(matrix: THREE.Matrix4 | ItemJson): THREE.Vector3[] { + if (matrix instanceof THREE.Matrix4) { + + } else { + // item.tf = [[0, 0.1, 0], [0, 0, 0], [1, 1, 1]] + matrix = getMatrixFromTf(matrix.tf) + } + + // 获取 AABB 包围盒 + const box = new THREE.Box3() + .setFromCenterAndSize(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 1, 1)) + .applyMatrix4(matrix) + const min = box.min + const max = box.max + + const corners = [ + new THREE.Vector3(min.x, min.y, min.z), + new THREE.Vector3(max.x, min.y, min.z), + new THREE.Vector3(max.x, max.y, min.z), + new THREE.Vector3(min.x, max.y, min.z), + new THREE.Vector3(min.x, min.y, max.z), + new THREE.Vector3(max.x, min.y, max.z), + new THREE.Vector3(max.x, max.y, max.z), + new THREE.Vector3(min.x, max.y, max.z) + ] + + // 构建边索引(共 12 条边) + const edgePositions: THREE.Vector3[] = [] + const edges = [ + [0, 1], [1, 2], [2, 3], [3, 0], // 侧面 + [4, 5], [5, 6], [6, 7], [7, 4], // 右侧面 + // 上面的点 2,3,6,7 + [3, 2], [3, 7], [7, 6], [6, 2], + // 下面的点: 0,1,4,5 + [0, 1], [1, 5], [5, 4], [4, 0] + ] + edges.forEach(([a, b]) => { + const va = corners[a] + const vb = corners[b] + edgePositions.push(va, vb) + }) + + return edgePositions +} + +/** + * 获取物体的 OBB 包围盒顶点 + * @param matrix 物体的变换矩阵或 ItemJson 对象 + */ +export function getOBBox(matrix: THREE.Matrix4 | ItemJson): THREE.Vector3[] { + if (matrix instanceof THREE.Matrix4) { + + } else { + // item.tf = [[0, 0.1, 0], [0, 0, 0], [1, 1, 1]] + matrix = getMatrixFromTf(matrix.tf) + } + + // 获取 OOBB 包围盒 + const halfSize = new THREE.Vector3().setFromMatrixScale(matrix).multiplyScalar(0.5) + const vertices = [ + new THREE.Vector3(-halfSize.x, -halfSize.y, -halfSize.z), + new THREE.Vector3(halfSize.x, -halfSize.y, -halfSize.z), + new THREE.Vector3(halfSize.x, halfSize.y, -halfSize.z), + new THREE.Vector3(-halfSize.x, halfSize.y, -halfSize.z), + new THREE.Vector3(-halfSize.x, -halfSize.y, halfSize.z), + new THREE.Vector3(halfSize.x, -halfSize.y, halfSize.z), + new THREE.Vector3(halfSize.x, halfSize.y, halfSize.z), + new THREE.Vector3(-halfSize.x, halfSize.y, halfSize.z) + ] + + // 应用旋转 + 平移到每个顶点 + const corners = vertices.map(v => + v.clone().applyMatrix4(matrix) + ) + + // 构建边索引(共 12 条边) + const edgePositions: THREE.Vector3[] = [] + const edges = [ + [0, 1], [1, 2], [2, 3], [3, 0], // 侧面 + [4, 5], [5, 6], [6, 7], [7, 4], // 右侧面 + // 上面的点 2,3,6,7 + [3, 2], [3, 7], [7, 6], [6, 2], + // 下面的点: 0,1,4,5 + [0, 1], [1, 5], [5, 4], [4, 0] + ] + edges.forEach(([a, b]) => { + const va = corners[a] + const vb = corners[b] + edgePositions.push(va, vb) + }) + + return edgePositions } diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index f6c4f2d..74a3af2 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -10,6 +10,7 @@ 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 { getAABBox, getOBBox } from '@/core/ModelUtils.ts' /** * 选择工具,用于在设计器中显示选中对象的包围盒 @@ -248,51 +249,27 @@ export default class SelectInspect implements IControls { // 更新选中对象的包围盒线框 private updateSelectionBox = () => { - const selectedObject = this.viewport.state.selectedObject this.clearSelectionBox() - if (!selectedObject) { + const item = this.viewport.state.selectedItem + if (!item) { return } - this.selectionId = selectedObject.userData?.entityId - // 避免某些蒙皮网格的帧延迟效应(e.g. Michelle.glb) - selectedObject.updateWorldMatrix(false, true) - - let box: THREE.Box3 - if (selectedObject instanceof THREE.Object3D) { - box = new THREE.Box3().setFromObject(selectedObject) - - } else if (selectedObject instanceof PointManageWrap) { - box = selectedObject.createBox3() - } - - const min = box.min - const max = box.max - - const corners = [ - new THREE.Vector3(min.x - Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, min.z - Constract.YELLOW_EXPAND_AMOUNT), - new THREE.Vector3(max.x + Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, min.z - Constract.YELLOW_EXPAND_AMOUNT), - new THREE.Vector3(max.x + Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, max.z + Constract.YELLOW_EXPAND_AMOUNT), - new THREE.Vector3(min.x - Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, max.z + Constract.YELLOW_EXPAND_AMOUNT) - ] - - // 构建矩形边框(4 条边) + // 构造 positions 数组 + const edgePositions = getOBBox(item) // getOBBox(item) // getAABBox(item) const positions = [] - for (let i = 0; i < 4; i++) { - const p1 = corners[i] - const p2 = corners[(i + 1) % 4] + for (let i = 0; i < edgePositions.length; i += 2) { + const p1 = edgePositions[i] + const p2 = edgePositions[i + 1] 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 lineGeom = new LineGeometry().setPositions(new Float32Array(positions)) const selectionBox = new Line2(lineGeom, this.yellowMaterial) selectionBox.computeLineDistances() + selectionBox.scale.set(1, 1, 1) this.selectionBox = selectionBox console.log('selectedItem', this.viewport.state.selectedItem) diff --git a/src/core/manager/InstancePointManager.ts b/src/core/manager/InstancePointManager.ts index e24cef3..b5e441b 100644 --- a/src/core/manager/InstancePointManager.ts +++ b/src/core/manager/InstancePointManager.ts @@ -304,15 +304,66 @@ export class PointManageWrap { return this } - createBox3(): THREE.Box3 { - const instancedMesh = this.manager.blocks[this.blockIndex]?.instancedMesh - + createBox3() { + // 原始代码 ================================= + // const instancedMesh = this.manager.blocks[this.blockIndex]?.instancedMesh + // + // const matrix = new THREE.Matrix4() + // instancedMesh.getMatrixAt(this.meshIndex, matrix) + // // 创建包围盒并应用矩阵 + // const geometry = instancedMesh.geometry + // //@ts-ignore + // const localBox = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) + // return localBox.clone().applyMatrix4(matrix) + // ================================= + + // TF 代码 ================================= + const item = this.manager.viewport.entityManager.findItemById(this.uuid) + + const [pos, rot, scale] = item.tf + // // item.tf = [[8.5, 0.1, 2], [0, 0, 0], [1.5, 0.1, 2]] const matrix = new THREE.Matrix4() - instancedMesh.getMatrixAt(this.meshIndex, matrix) - // 创建包围盒并应用矩阵 - const geometry = instancedMesh.geometry - //@ts-ignore - const localBox = new THREE.Box3().setFromBufferAttribute(geometry.attributes.position) - return localBox.clone().applyMatrix4(matrix) + matrix.compose( + new THREE.Vector3(pos[0], pos[1], pos[2]), + new THREE.Quaternion().setFromEuler(new THREE.Euler( + THREE.MathUtils.degToRad(rot[0]), + THREE.MathUtils.degToRad(rot[1]), + THREE.MathUtils.degToRad(rot[2]), + 'XYZ' + )), + new THREE.Vector3(scale[0], scale[1], scale[2]) + ) + // 获取 AABB 包围盒 ========================== + // const aabb = new THREE.Box3() + // .setFromCenterAndSize(new THREE.Vector3(0, 0, 0), new THREE.Vector3(1, 1, 1)) + // .applyMatrix4(matrix) + // const min = aabb.min + // const max = aabb.max + // return [ + // new THREE.Vector3(min.x - Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, min.z - Constract.YELLOW_EXPAND_AMOUNT), + // new THREE.Vector3(max.x + Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, min.z - Constract.YELLOW_EXPAND_AMOUNT), + // new THREE.Vector3(max.x + Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, max.z + Constract.YELLOW_EXPAND_AMOUNT), + // new THREE.Vector3(min.x - Constract.YELLOW_EXPAND_AMOUNT, max.y + Constract.YELLOW_EXPAND_AMOUNT, max.z + Constract.YELLOW_EXPAND_AMOUNT) + // ] + + // 获取 OOBB 包围盒 + const halfSize = new THREE.Vector3().setFromMatrixScale(matrix).multiplyScalar(0.5) + const vertices = [ + new THREE.Vector3(-halfSize.x, -halfSize.y, -halfSize.z), + new THREE.Vector3(halfSize.x, -halfSize.y, -halfSize.z), + new THREE.Vector3(halfSize.x, halfSize.y, -halfSize.z), + new THREE.Vector3(-halfSize.x, halfSize.y, -halfSize.z), + new THREE.Vector3(-halfSize.x, -halfSize.y, halfSize.z), + new THREE.Vector3(halfSize.x, -halfSize.y, halfSize.z), + new THREE.Vector3(halfSize.x, halfSize.y, halfSize.z), + new THREE.Vector3(-halfSize.x, halfSize.y, halfSize.z) + ] + + // 应用旋转 + 平移到每个顶点 + const verticesWorld = vertices.map(v => + v.clone().applyMatrix4(matrix) + ) + + return verticesWorld } }