Three.js-硬要自学系列36之专项学习包围盒

理解包围盒

想象一下

  1. 我们有一个形状非常复杂的物体:比如一个张牙舞爪的雕塑、一个毛绒玩具熊、或者你自己组装的乐高模型。

  2. 想要把它装进快递盒里寄走

    • 我们不会为它量身定制一个完全贴合每一个凸起和凹陷的盒子(那太费劲、成本太高)。
    • 我们会找一个刚好能把这个物体完全装进去的、形状规则的标准纸箱
    • 这个纸箱可能是一个方方正正的矩形纸箱 (对应 Box3),或者一个圆滚滚的球 (对应 Sphere)。
  3. 这个标准纸箱,就是包围盒!

包围盒的两种类型

  • 轴对齐包围盒:Box3 (Axis-Aligned Bounding Box - AABB)

    • 像什么? 一个方方正正的、边平行于世界坐标轴(X, Y, Z轴)的矩形盒子
    • 怎么来的? 计算机找到物体所有顶点在 X, Y, Z 三个方向上的最大值(max)和最小值(min),用这六个值就确定了一个"最小最大"盒子。
    • 优点:计算超级快! 因为它的边和坐标轴平行,判断两个AABB是否相交、点是否在盒内等操作非常高效。
    • 缺点:不够紧贴物体。 如果物体是旋转的或者细长的(比如一根斜着放的长棍),这个盒子会比实际物体大很多,浪费空间(不够精确)。
    • Three.js 中的类:THREE.Box3
  • 包围球:Sphere (Bounding Sphere)

    • 像什么? 一个球体
    • 怎么来的? 计算机先找到物体的中心点(几何中心或边界盒中心),然后找到离这个中心最远的顶点,这段距离就是球的半径。
    • 优点:旋转不变性。 无论物体怎么旋转,包围球的大小和位置(中心点)基本不变。判断两个球是否相交(距离小于半径之和)也很快。
    • 缺点:可能更不紧贴。 对于形状很不均匀的物体(比如一个十字架、一个L形),球体内部会有很多空白区域,比AABB包裹得更松。
    • Three.js 中的类:THREE.Sphere

我们为什么需要包围盒

  • 碰撞检测(初级): 快速判断两个物体是否"有可能"相撞。先检查它们的包围盒(AABB或球)是否相交。如果包围盒都不相交,那物体肯定没撞上,就不用进行更复杂的(计算量大的)精确碰撞检测了。
  • 视锥体裁剪: 决定物体是否在相机视野范围内。相机视野也是一个空间区域(一个平截头体)。如果物体的包围盒完全在这个区域外面,那整个物体就不用渲染了,大大节省性能。
  • 射线拾取(优化): 当用鼠标点击屏幕时,会发射一条射线。先判断射线是否与物体的包围盒相交。如果不相交,那肯定没点到这个物体,就不用检查射线和物体所有三角面的精确交点了。
  • 空间分割算法的基础: 像八叉树(Octree)、BVH(Bounding Volume Hierarchy)这些高级空间管理算法,都需要用包围盒(AABB或球)作为节点来组织场景中的物体。
  • 获取物体尺寸和位置: 方便地获取物体在3D空间中的大致尺寸(Box3size)和位置范围(Box3min/maxSpherecenter)。

案例 - 设置模型位于包围盒内的

效果如下

创建一个Box3立方体对象,其参数为立方体的最小和最大坐标

js 复制代码
const box3 = new THREE.Box3(
    new THREE.Vector3(-1, 0, -1),
    new THREE.Vector3(1, 1, 1)
);

然后创建一个Mesh对象,设置Mesh对象的位置,使其位于Box3对象的最小坐标处

js 复制代码
const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshNormalMaterial()
);
scene.add(mesh);
mesh.position.set(box3.min.x, 0 , box3.min.z);

案例 - 获取大小

效果如图

通过getSize方法,可以获取包围盒的宽度,高度,和深度,这里将获取到的值,用来设置Mesh的缩放

js 复制代码
const v_min = new THREE.Vector3(0, 0, 0);
const x = 1 / 12 * 4;  
const y = 1 / 12 * 2;
const z = 8;
const v_max = new THREE.Vector3(x, y, z);
const box3 = new THREE.Box3(v_min, v_max);
const v_size = new THREE.Vector3();
box3.getSize(v_size);
console.log(v_size);

const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(1,1,1),
    new THREE.MeshNormalMaterial()
);
scene.add(mesh);
mesh.scale.copy(v_size);

案例 - 设置位置

如图

在场景中添加一个立方体, 通过computeBoundingBox计算几何体的包围盒, 其计算结果保存在geometry.boundingBox

js 复制代码
const mesh = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshNormalMaterial()
);
mesh.geometry.computeBoundingBox(); 
const s = new THREE.Vector3(); 
const box3 = mesh.geometry.boundingBox; 
box3.getSize(s); 

mesh.position.y = s.y / 2; 
scene.add(mesh);

案例 - helper辅助器

three.js 中提供了一个方便观察包围盒的辅助器,通过Box3Helper创建

js 复制代码
const min = new THREE.Vector3(-0.5, -0.75, -1.125);
const max = new THREE.Vector3(0.125, 0.25, 0.5);
const box3 = new THREE.Box3(min, max);

const box3Helper = new THREE.Box3Helper(box3, 'deepskyblue');
scene.add(box3Helper);

案例 - 从对象中设置Box3

效果如下

需要了解的API

setFromObject

其作用是快速计算出这个物体在当前状态下所占用的最小矩形空间 (轴对齐包围盒,AABB),并保存到 Box3 实例中, 它可以作用在任何Object3D上,包括 Mesh(网格)、Group(群组)、Camera(相机)等。如果是 Group,会递归计算其所有子物体合并后的总包围盒。

js 复制代码
const box3 = new THREE.Box3();
const box3Helper = new THREE.Box3Helper(box3, 'deepskyblue');
scene.add(box3Helper);

const group = new THREE.Group();
let i=0;
const len = 3;
while(i<len){
    const mesh = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial() );
    mesh.position.x = -2.5 + 5 * Math.random();
    mesh.position.y = -2.5 + 5 * Math.random();
    mesh.position.z = -2.5 + 5 * Math.random();
    group.add(mesh);
    i++;
}
scene.add(group);
box3.setFromObject(group); 

案例 - intersects相交

效果图图

从图中可以看出,与包围盒相交的立方体为橙黄,反之为粉红,这里要接触到一个新的API

需要了解的API

intersectsBox 用于 快速检测两个 3D 物体是否可能碰撞 的数学方法,通过判断它们的"包围盒"是否重叠来实现高性能碰撞初筛。

包围盒相交需同时满足以下三个条件:

坐标轴 重叠条件(公式) 现实比喻
X轴 box1.min.x ≤ box2.max.xbox1.max.x ≥ box2.min.x 两辆车在车道上横向重叠
Y轴 box1.min.y ≤ box2.max.ybox1.max.y ≥ box2.min.y 无人机与鸟高度层重叠
Z轴 box1.min.z ≤ box2.max.zbox1.max.z ≥ box2.min.z 两艘船前后位置重叠
js 复制代码
const box3 = new THREE.Box3(
    new THREE.Vector3(-2, -2, -2),
    new THREE.Vector3(2, 2, 2)
);
const box3Helper = new THREE.Box3Helper(box3, 'deepskyblue');
scene.add(box3Helper);

const group = new THREE.Group();
let i=0;
const len = 40;
while(i<len){
   const material = new THREE.MeshBasicMaterial({
    color: 'deeppink',
    transparent: true,
    opacity: 0.5
   }) 
   const mesh = new THREE.Mesh(
        new THREE.BoxGeometry(0.5, 0.5, 0.5),
        material
    );
    mesh.position.x = -4 + 8 * Math.random();
    mesh.position.y = -4 + 8 * Math.random();
    mesh.position.z = -4 + 8 * Math.random();

    const mesh_b3 = new THREE.Box3();
    mesh_b3.setFromObject(mesh); // 从对象中设置Box3
    if(mesh_b3.intersectsBox(box3)) { // 判断是否相交
        mesh.material.color = new THREE.Color('orange');
    }
    group.add(mesh);
    i++;
}
scene.add(group);

案例 - 随机边界

在包围盒内生成随机位置立方体,看效果

js 复制代码
const setAxis = function(mesh, box3, axis, per){
    const meshSize = new THREE.Vector3();
    mesh.geometry.computeBoundingBox();  // 计算立方体边界盒的大小
    mesh.geometry.boundingBox.getSize(meshSize);
    const boxSize = new THREE.Vector3();
    box3.getSize(boxSize);
    mesh.position[axis] = box3.min[axis] + meshSize[axis] / 2  + ( boxSize[axis] - meshSize[axis] ) * per;
};

const rnd = function () {
    return THREE.MathUtils.seededRandom();  
}

const min = new THREE.Vector3(-1.0, -1.0, -1.0);
const max = new THREE.Vector3(1.0, 1.0, 1.0);
const box3 = new THREE.Box3(min, max);
const box3Helper = new THREE.Box3Helper(box3, 'deepskyblue');
scene.add(box3Helper);

let i = 0; 
const len = 60;
while(i < len){
    const w = 0.5 + 0.75 * rnd(),  // 立方体宽
    h = 0.5 + 0.75 * rnd(),
    d = 0.5 + 0.75 * rnd();
    const mesh = new THREE.Mesh( 
        new THREE.BoxGeometry(w, h, d), 
        new THREE.MeshNormalMaterial({
            transparent: true,
            opacity: 0.25
        }) 
    );
    setAxis(mesh, box3, 'x', rnd());
    setAxis(mesh, box3, 'y', rnd());
    setAxis(mesh, box3, 'z', rnd());
    scene.add(mesh);
    i++;
};
相关推荐
EnCi Zheng20 分钟前
M5-markconv自定义CSS样式指南 [特殊字符]
前端·css·python
kyriewen24 分钟前
你的网页慢,用户不说直接走——前端性能监控教你“读心术”
前端·性能优化·监控
广州华水科技24 分钟前
北斗GNSS变形监测在大坝安全监测中的应用与优势分析
前端
前端老石人36 分钟前
前端开发中的 URL 完全指南
开发语言·前端·javascript·css·html
CAE虚拟与现实36 分钟前
五一假期闲来无事,来个前段、后端的说明吧
前端·后端·vtk·three.js·前后端
Sarvartha1 小时前
三目运算符
linux·服务器·前端
晓晨的博客1 小时前
ROS1录制的bag包转换为ROS2格式
前端·chrome
Wect1 小时前
LeetCode 72. 编辑距离:动态规划经典题解
前端·算法·typescript
donecoding1 小时前
别再让 pnpm 跟着 nvm 跑了!独立安装终极指南
前端·node.js·前端工程化
GISer_Jing1 小时前
AI全栈转型_TS后端学习路线
前端·人工智能·后端·学习