1. Three.js包围盒详解

1.1. 什么是包围盒(Bounding Box)

1.1.1. 基本概念

包围盒是用于描述3D对象空间范围的简化几何形状,通常是一个与坐标轴对齐的长方体(Axis-Aligned Bounding Box, AABB)。

1.1.2. 主要用途

1.2. 包围盒类型

1.2.1. Box3 - 轴对齐包围盒(AABB)

// 创建包围盒
const box = new THREE.Box3();

// 从对象创建
const mesh = new THREE.Mesh(geometry, material);
const box = new THREE.Box3().setFromObject(mesh);

// 手动设置
box.set(minPoint, maxPoint);

1.2.2. Sphere - 包围球

const sphere = new THREE.Sphere(center, radius);
sphere.setFromPoints(points); // 从点集计算

1.3. 核心方法详解

1.3.1. 创建与设置

// 1. 从物体创建
const box = new THREE.Box3().setFromObject(object);

// 2. 从几何体创建
const geometry = new THREE.BoxGeometry(1, 1, 1);
const box = new THREE.Box3().setFromBufferAttribute(
    geometry.attributes.position
);

// 3. 从点集创建
const points = [new THREE.Vector3(0,0,0), new THREE.Vector3(1,1,1)];
const box = new THREE.Box3().setFromPoints(points);

1.3.2. 重要属性

box.min; // Vector3 - 最小点坐标
box.max; // Vector3 - 最大点坐标
box.isEmpty(); // 布尔值 - 是否为空包围盒

1.3.3. 几何变换

// 应用矩阵变换
box.applyMatrix4(matrix);

// 平移
box.translate(offset);

// 扩展(合并包围盒)
box.union(otherBox);

// 相交
box.intersect(otherBox);

1.3.4. 查询与检测

// 包含检测
box.containsPoint(point); // 是否包含点
box.containsBox(otherBox); // 是否包含另一个包围盒

// 相交检测
box.intersectsBox(otherBox); // 与包围盒相交
box.intersectsSphere(sphere); // 与球体相交
box.intersectsPlane(plane); // 与平面相交

// 获取尺寸和中心
box.getSize(targetVector); // 获取尺寸
box.getCenter(targetVector); // 获取中心点

1.4. 实际应用示例

1.4.1. 可视化包围盒

function createBoundingBoxHelper(object) {
    const box = new THREE.Box3().setFromObject(object);

    // 创建可视化辅助线
    const helper = new THREE.Box3Helper(box, 0xffff00);
    scene.add(helper);

    return helper;
}

// 或者使用 BoxHelper(已废弃,推荐Box3Helper)
const boxHelper = new THREE.BoxHelper(object, 0xffff00);
scene.add(boxHelper);

// 更新包围盒
function updateBoundingBox() {
    box.setFromObject(object);
    boxHelper.update();
}

1.4.2. 碰撞检测

function checkCollision(object1, object2) {
    const box1 = new THREE.Box3().setFromObject(object1);
    const box2 = new THREE.Box3().setFromObject(object2);

    return box1.intersectsBox(box2);
}

// 带精度控制的碰撞检测
function preciseCollision(mesh1, mesh2) {
    const box1 = mesh1.geometry.boundingBox.clone();
    const box2 = mesh2.geometry.boundingBox.clone();

    // 应用世界变换
    box1.applyMatrix4(mesh1.matrixWorld);
    box2.applyMatrix4(mesh2.matrixWorld);

    return box1.intersectsBox(box2);
}

1.4.3. 视锥体剔除优化

class ObjectManager {
    constructor() {
        this.objects = [];
        this.frustum = new THREE.Frustum();
        this.cameraMatrix = new THREE.Matrix4();
    }

    updateVisibility(camera) {
        this.cameraMatrix.multiplyMatrices(
            camera.projectionMatrix,
            camera.matrixWorldInverse
        );
        this.frustum.setFromProjectionMatrix(this.cameraMatrix);

        this.objects.forEach(obj => {
            const box = new THREE.Box3().setFromObject(obj);
            obj.visible = this.frustum.intersectsBox(box);
        });
    }
}

1.4.4. 射线拾取优化

class OptimizedRaycaster {
    constructor() {
        this.raycaster = new THREE.Raycaster();
        this.boundingBoxes = new Map();
    }

    // 预计算包围盒
    precomputeBoxes(objects) {
        objects.forEach(obj => {
            this.boundingBoxes.set(
                obj.uuid,
                new THREE.Box3().setFromObject(obj)
            );
        });
    }

    // 优化后的相交检测
    intersectObjects(mouse, camera, objects) {
        this.raycaster.setFromCamera(mouse, camera);

        const intersected = [];

        // 先进行包围盒快速检测
        objects.forEach(obj => {
            const box = this.boundingBoxes.get(obj.uuid);
            if (box && this.raycaster.ray.intersectsBox(box)) {
                intersected.push(obj);
            }
        });

        // 对通过包围盒检测的对象进行精确检测
        return this.raycaster.intersectObjects(intersected, true);
    }
}

1.5. 性能优化技巧

1.5.1. 包围盒缓存

class BoundingBoxCache {
    constructor() {
        this.cache = new WeakMap();
    }

    getBox(object) {
        if (!this.cache.has(object)) {
            const box = new THREE.Box3().setFromObject(object);
            this.cache.set(object, box);
            return box;
        }
        return this.cache.get(object);
    }

    invalidate(object) {
        this.cache.delete(object);
    }
}

1.5.2. 层次包围盒(BVH)

// 创建层次结构
function createBVH(objects) {
    const bvh = [];

    // 每8个对象创建一个父包围盒
    for (let i = 0; i < objects.length; i += 8) {
        const group = objects.slice(i, i + 8);
        const groupBox = new THREE.Box3();

        group.forEach(obj => {
            const box = new THREE.Box3().setFromObject(obj);
            groupBox.union(box);
        });

        bvh.push({
            box: groupBox,
            objects: group
        });
    }

    return bvh;
}

1.5.3. 动态更新策略

class DynamicBoundingBox {
    constructor(object, updateThreshold = 0.1) {
        this.object = object;
        this.updateThreshold = updateThreshold;
        this.lastPosition = object.position.clone();
        this.lastRotation = object.rotation.clone();
        this.lastScale = object.scale.clone();
        this.box = new THREE.Box3().setFromObject(object);
    }

    needsUpdate() {
        return (
            this.lastPosition.distanceTo(this.object.position) > this.updateThreshold ||
            this.lastRotation.angleTo(this.object.rotation) > this.updateThreshold ||
            this.lastScale.distanceTo(this.object.scale) > this.updateThreshold
        );
    }

    update() {
        if (this.needsUpdate()) {
            this.box.setFromObject(this.object);
            this.lastPosition.copy(this.object.position);
            this.lastRotation.copy(this.object.rotation);
            this.lastScale.copy(this.object.scale);
            return true;
        }
        return false;
    }
}

1.6. 常见问题与解决方案

1.6.1. 包围盒不准确

// 问题:网格变形或蒙皮动画导致包围盒不准
// 解决方案:重新计算顶点位置
function updateSkinnedBoundingBox(mesh) {
    const geometry = mesh.geometry;
    const position = geometry.attributes.position;
    const skinIndex = geometry.attributes.skinIndex;
    const skinWeight = geometry.attributes.skinWeight;

    // 更新蒙皮顶点
    mesh.updateMatrixWorld(true);

    // 重新计算包围盒
    geometry.computeBoundingBox();
}

1.6.2. 包围盒更新性能

// 使用脏标记优化
class OptimizedObject {
    constructor() {
        this._boundingBoxDirty = true;
        this._boundingBox = new THREE.Box3();
    }

    get boundingBox() {
        if (this._boundingBoxDirty) {
            this._boundingBox.setFromObject(this);
            this._boundingBoxDirty = false;
        }
        return this._boundingBox;
    }

    markDirty() {
        this._boundingBoxDirty = true;
    }
}

1.6.3. 内存管理

// 避免内存泄漏
class BoundingBoxManager {
    constructor() {
        this.boxes = new Map();
        this.disposed = new WeakSet();
    }

    // 清理未使用的包围盒
    cleanup() {
        for (const [obj, box] of this.boxes) {
            if (this.disposed.has(obj) || !obj.parent) {
                box.set(new THREE.Vector3(), new THREE.Vector3());
                this.boxes.delete(obj);
            }
        }
    }
}

1.7. 最佳实践

  1. 适时计算:避免每帧重新计算静态物体的包围盒
  2. 合理缓存:对频繁使用的包围盒进行缓存
  3. 层次结构:复杂场景使用BVH优化
  4. 精度控制:根据需求选择合适的精度级别
  5. 内存管理:及时清理不再使用的包围盒

1.8. 扩展应用

1.8.1. 八叉树空间划分

class OctreeNode {
    constructor(boundary, capacity = 4) {
        this.boundary = boundary; // Box3
        this.capacity = capacity;
        this.objects = [];
        this.divided = false;
        this.children = [];
    }

    insert(object) {
        const box = new THREE.Box3().setFromObject(object);

        if (!this.boundary.containsBox(box)) {
            return false;
        }

        if (this.objects.length < this.capacity) {
            this.objects.push(object);
            return true;
        }

        if (!this.divided) {
            this.subdivide();
        }

        return this.children.some(child => child.insert(object));
    }

    subdivide() {
        const size = this.boundary.getSize(new THREE.Vector3());
        const halfSize = size.clone().multiplyScalar(0.5);
        const center = this.boundary.getCenter(new THREE.Vector3());

        // 创建8个子节点
        for (let x = -1; x <= 1; x += 2) {
            for (let y = -1; y <= 1; y += 2) {
                for (let z = -1; z <= 1; z += 2) {
                    const childMin = new THREE.Vector3(
                        center.x + x * halfSize.x * 0.5,
                        center.y + y * halfSize.y * 0.5,
                        center.z + z * halfSize.z * 0.5
                    );
                    const childMax = childMin.clone().add(halfSize);
                    const childBox = new THREE.Box3(childMin, childMax);

                    this.children.push(new OctreeNode(childBox, this.capacity));
                }
            }
        }

        this.divided = true;
    }
}

通过合理使用包围盒技术,可以显著提升Three.js应用的性能,特别是在处理大量物体或复杂场景时。