You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

200 lines
5.5 KiB

import * as THREE from 'three'
import rbush from 'rbush'
import { OBB } from 'three/examples/jsm/math/OBB'
// import { Octree } from 'three/examples/jsm/math/Octree.js'
// import { QuadTreeNode } from '@/core/QuadTree.ts'
// import { convexHull } from '@/core/ModelUtils.ts'
interface ItemEntry extends rbush.BBox {
id: string
obb: OBB
}
// 主管理器类
export default class ItemFindManager {
private spatialIndex = new rbush<ItemEntry>()
private items = new Map<string, ItemEntry>()
dispose() {
this.spatialIndex.clear()
this.items.clear()
}
constructor() {
}
// 添加或更新物品
addOrUpdate(...items: ItemMetrix[]): void {
for (const item of items) {
const aabb = itemToAABB(item)
const obb = itemToOBB(item)
if (this.items.has(item.id)) {
this.remove(item.id)
}
const entry: ItemEntry = {
id: item.id,
obb,
...aabb
}
this.items.set(item.id, entry)
this.spatialIndex.insert(entry)
}
}
// 移除物品
remove(...ids: string[]): void {
for (const id of ids) {
const entry = this.items.get(id)
if (entry) {
this.spatialIndex.remove(entry)
this.items.delete(id)
}
}
}
// 位置查询
getItemsByPosition(x: number, z: number): string[] {
const candidates = this.spatialIndex.search({
minX: x,
minY: z,
maxX: x,
maxY: z
})
const point = new THREE.Vector3(x, 0, z)
return candidates.filter((item) => pointIntersectsOBB(point, item.obb)).map((item) => item.id)
}
// 距离查询
getItemsByDistance(x: number, z: number, distance: number): string[] {
const sphere = new THREE.Sphere(new THREE.Vector3(x, 0, z), distance)
const candidates = this.spatialIndex.search({
minX: x - distance,
minY: z - distance,
maxX: x + distance,
maxY: z + distance
})
return candidates.filter((item) => sphereIntersectsOBB(sphere, item.obb)).map((item) => item.id)
}
// 矩形区域查询(有交集)
getItemsByRect(x1: number, z1: number, x2: number, z2: number): string[] {
const rect = {
minX: Math.min(x1, x2),
maxX: Math.max(x1, x2),
minY: Math.min(z1, z2),
maxY: Math.max(z1, z2)
}
const candidates = this.spatialIndex.search(rect)
return candidates.filter((item) => rectIntersectsOBB(rect, item.obb)).map((item) => item.id)
}
}
function pointIntersectsOBB(point: THREE.Vector3, obb: OBB): boolean {
const box = new THREE.Box3()
setBox3FromOBB(box, obb)
return box.containsPoint(point)
}
function sphereIntersectsOBB(sphere: THREE.Sphere, obb: OBB): boolean {
const box = new THREE.Box3()
setBox3FromOBB(box, obb)
return box.intersectsSphere(sphere)
}
function rectIntersectsOBB(rect: { minX: number; minY: number; maxX: number; maxY: number }, obb: OBB): boolean {
// 简化判断:用 AABB 投影到 XZ 平面做矩形交叉检测
const aabb = new THREE.Box3()
setBox3FromOBB(aabb, obb)
const aabb2D = {
minX: aabb.min.x,
minY: aabb.min.z,
maxX: aabb.max.x,
maxY: aabb.max.z
}
return !(
rect.maxX < aabb2D.minX ||
rect.minX > aabb2D.maxX ||
rect.maxY < aabb2D.minY ||
rect.minY > aabb2D.maxY
)
}
export function itemToAABB(item: ItemMetrix): { minX: number; minY: number; maxX: number; maxY: number } {
// 假设所有物品都是立方体,尺寸为 scale.x × scale.z
const x = item.tf[0][0]
const z = item.tf[0][2]
const halfWidth = item.tf[2][0] / 2
const halfDepth = item.tf[2][2] / 2
return {
minX: x - halfWidth,
maxX: x + halfWidth,
minY: z - halfDepth,
maxY: z + halfDepth
}
}
export function itemToOBB(item: ItemMetrix): OBB {
const position = new THREE.Vector3(...item.tf[0])
const rotation = new THREE.Euler(
THREE.MathUtils.degToRad(item.tf[1][0]),
THREE.MathUtils.degToRad(item.tf[1][1]),
THREE.MathUtils.degToRad(item.tf[1][2]),
'XYZ'
)
const scale = new THREE.Vector3(...item.tf[2])
const matrix = new THREE.Matrix4()
.makeRotationFromEuler(rotation)
.premultiply(new THREE.Matrix4().makeTranslation(position.x, position.y, position.z))
.premultiply(new THREE.Matrix4().makeScale(scale.x, scale.y, scale.z))
const obb = new OBB(
new THREE.Vector3(),
new THREE.Vector3(0.5, 0.5, 0.5),
new THREE.Matrix3().setFromMatrix4(matrix)
)
return obb
}
function setBox3FromOBB(box: THREE.Box3, obb: OBB): THREE.Box3 {
const center = obb.center
const halfSize = new THREE.Vector3().copy(obb.halfSize)
const rotation = obb.rotation
// 8 个局部顶点
const vertices = [
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, 1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, 1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, -1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, -1, 1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, 1, -1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, 1, -1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(-1, -1, -1)),
new THREE.Vector3().copy(halfSize).multiply(new THREE.Vector3(1, -1, -1))
]
// 应用旋转和平移到每个顶点
const worldVertices = vertices.map((v) => {
return v.applyMatrix3(rotation).add(center)
})
// 构造包围盒
box.min.set(Infinity, Infinity, Infinity)
box.max.set(-Infinity, -Infinity, -Infinity)
for (const v of worldVertices) {
box.expandByPoint(v)
}
return box
}