【OCCT+ImGUI系列】013-碰撞检测-包围盒Bnd_Box
一、引言
接下来增加一个碰撞检测相关的专题,主要讲OpenCASCADE与碰撞检测相关的概念。首先开始的概念是包围盒(Bounding Box):在三维建模、碰撞检测、几何布尔运算等场景中,包围盒是高效处理几何体空间关系的关键数据结构。OpenCASCADE 提供的 Bnd_Box
类就是这样一个强大的工具,它支持构建、扩展、查询和变换三维空间中的包围盒。
学习完下面内容可以实现:
- 生成TopoDS_Shape对应的包围盒
- 使用Gap来调整包围盒
- 生成空盒子(Void)或整个空间(WholeSpace)
- 判断两个包围盒是否相交
二、Bnd_Box 的作用与基本概念
Bnd_Box
表示一个三维轴对齐的包围盒(AABB),即其面总是与坐标轴平行。它可以:
- 包围几何对象(如点、线、面、体)
- 判断空间相交或包含关系
- 用作快速粗略碰撞检测的加速结构
特点包括:
- 可以是有限盒子
- 可以在某些方向上是无限延伸的
- 可以表示空盒子(Void)或整个空间(WholeSpace)
- 支持**间隙(Gap)**来考虑数值精度误差
三、Bnd_Box 的创建方式
1. 创建空盒子(默认构造)
Bnd_Box box;
创建后默认是 Void
状态,即为空。
2. 通过两点定义最小包围盒
gp_Pnt pmin(0, 0, 0), pmax(10, 10, 10);
Bnd_Box box(pmin, pmax);
用于表示有限区域。
四、设置特殊状态
1. 空盒子(Void)
box.SetVoid();
表示盒子中不包含任何点。
2. 整个空间(WholeSpace)
box.SetWhole();
表示整个空间的无限盒子,任何点都在其内。
3. 单方向开口
box.OpenXmin();
box.OpenZmax();
表示该方向是无限的,可以与 IsOpenXmin()
等方法配合使用。
五、更新与扩展 Bnd_Box
1. 添加点
box.Add(gp_Pnt(1, 2, 3));
将点纳入包围盒,自动扩展边界。
2. 添加方向(射线)
box.Add(gp_Pnt(0,0,0), gp_Dir(1,0,0)); // 添加从原点出发的正X方向射线
将整个方向扩展为无限。
3. 添加其他盒子
box.Add(otherBox);
合并两个盒子。
4. 设置间隙
box.SetGap(0.1); // 增加 0.1 的容差
所有边界都扩展该值,常用于容错计算。
六、查询与判断
1. 获取边界
Standard_Real xmin, ymin, zmin, xmax, ymax, zmax;
box.Get(xmin, ymin, zmin, xmax, ymax, zmax);
若盒子为空(IsVoid),将抛出异常。
2. 获取角点
gp_Pnt minCorner = box.CornerMin();
gp_Pnt maxCorner = box.CornerMax();
3. 判断状态
box.IsVoid()
box.IsWhole()
box.IsOpen()
box.IsXThin(tol)
,IsThin(tol)
:判断盒子是否厚度很小(用于简化处理)
4. 距离计算
Standard_Real dist = box.Distance(otherBox);
计算两个盒子的最小间距(非负)。
5. 是否相交(是否在外)
if (box.IsOut(gp_Pnt(5, 5, 5))) { ... }
if (!box.IsOut(otherBox)) { ... } // 有交集
七、几何变换与有限部分提取
1. 应用变换
gp_Trsf trsf;
trsf.SetRotation(gp::OX(), M_PI/4);
Bnd_Box rotatedBox = box.Transformed(trsf);
注意:旋转后会包围整个旋转后的对象,可能导致边界变大。
2. 提取有限部分
Bnd_Box finite = box.FinitePart();
用于从无限盒中提取有限部分,仅当存在有限部分时有效。
八、使用建议与注意事项
- Void 状态下不能 Get() 或 CornerMin(),需先判断
IsVoid()
。 - Gap 设定应适度,避免误判碰撞。
- 添加射线或方向后必须明确是否仍保持有限,尤其用于 FCL 或距离判断时。
- Transformed() 不保持最小体积,谨慎用于碰撞预处理。
九、总结
Bnd_Box
是 OpenCASCADE 中简单而高效的空间管理工具,适用于几乎所有涉及空间范围的模块。从基础的点添加到复杂的变换包围,都可使用统一接口处理,为 CAD 几何计算提供良好支撑。
相关概念拓展阅读
- AABB-box碰撞检测
- Bnd_Box官方文档
演示代码
#pragma once#include "pch.h"// OpenCASCADE 几何建模与可视化相关头文件
#include <BRepPrimAPI_MakeBox.hxx> // 创建立方体几何体
#include <BRepBndLib.hxx> // 用于构建 Bnd_Box(包围盒)
#include <Bnd_Box.hxx> // OpenCASCADE 中的轴对齐包围盒类
#include <AIS_Shape.hxx> // 用于可视化 TopoDS_Shape
#include <AIS_TextLabel.hxx> // 文本标签,可选
#include <gp_Trsf.hxx> // 通用几何变换(平移/旋转/缩放等)
#include <gp_Pnt.hxx> // 三维点
#include <gp_Ax1.hxx> // 三维坐标轴(用于旋转)
#include <Precision.hxx> // 精度控制常量// 本地模块
#include "BaseScene.h"
#include "VisSceneComponents.h"
#include "TutorialWindow.h"// 示例场景类:演示如何使用 Bnd_Box 构造包围盒并进行交互
class BndBox013 : public BaseScene, public VisSceneComponents, public TutorialWindow {
public:BndBox013() {// 构造函数:打开教程窗口(ImGui)openTutorialWindow();}// 渲染场景主入口函数void displayScene(const Handle(V3d_View)& view, const Handle(AIS_InteractiveContext)& context) override {// 如果场景尚未初始化,则进行初始化if (!bIsSceneInit) {sceneInit(view, context);bIsSceneInit = true;}// 渲染右侧 ImGui 教程交互窗口renderTutorialWindow(context);}// 教程窗口初始化(无内容)void customInitTutorialWindow(const Handle(AIS_InteractiveContext)& context) override {}// 场景初始化函数:构造两个立方体及其可视化对象,计算包围盒并可视化void sceneInit(const Handle(V3d_View)&, const Handle(AIS_InteractiveContext)& context) override {// 创建两个立方体几何体box1 = BRepPrimAPI_MakeBox(100, 100, 100).Shape();box2 = BRepPrimAPI_MakeBox(100, 100, 100).Shape();// 对 box2 施加平移变换(初始为沿 X 正方向移动 150)trsf2.SetTranslation(gp_Vec(150, 0, 0));box2.Move(TopLoc_Location(trsf2)); // 注意:这会修改原始 box2// 创建并设置可视化对象 aisBox1aisBox1 = new AIS_Shape(box1);aisBox1->SetColor(Quantity_NOC_BLUE1);context->Display(aisBox1, Standard_False);// 创建并设置可视化对象 aisBox2aisBox2 = new AIS_Shape(box2);aisBox2->SetColor(Quantity_NOC_GREEN);context->Display(aisBox2, Standard_False);// 初始化包围盒updateBndBoxes();// 显示包围盒(以彩色线框表示)displayBndBox(context, bndBox1, Quantity_NOC_CYAN1, box1BBox);displayBndBox(context, bndBox2, Quantity_NOC_ORANGE1, box2BBox);}// 教程窗口内容:交互式操作 ImGui 面板void renderTutorialContent(const Handle(AIS_InteractiveContext)& context) override {ImGui::Text("Bnd_Box");// 控制 box2 的平移操作if (ImGui::SliderFloat3("Move Box2 (XYZ)", box2Offset, -200.0f, 200.0f)) {trsf2.SetTranslation(gp_Vec(box2Offset[0], box2Offset[1], box2Offset[2]));box2Moved = box2.Located(TopLoc_Location(trsf2)); // 使用 Located 不修改原始几何体aisBox2->SetShape(box2Moved);context->Redisplay(aisBox2, Standard_False);updateBndBoxes(); // 更新包围盒数据updateBndAIS(context); // 重新可视化包围盒}ImGui::Separator();// 将 bndBox1 扩大 10 个单位if (ImGui::Button("Enlarge Box1 by 10")) {bndBox1.Enlarge(10.0);updateBndAIS(context);}// 设置 Box1 的 Gap 值(间隙),用于逻辑扩展static float gapVal = 0.0f;if (ImGui::SliderFloat("Set Gap for Box1", &gapVal, 0.0f, 20.0f)) {bndBox1.SetGap(gapVal);updateBndAIS(context);}// 设置 Box1 为 Void 状态(空包围盒)if (ImGui::Button("Set Box1 as Void")) {bndBox1.SetVoid();updateBndAIS(context);}ImGui::SameLine();// 设置 Box1 为 WholeSpace(最大包围空间)if (ImGui::Button("Set Box1 as WholeSpace")) {bndBox1.SetWhole();updateBndAIS(context);}ImGui::Separator();// 实时显示 bndBox1 的状态信息ImGui::Text("Box1 Gap: %.2f", bndBox1.GetGap());ImGui::Text("IsVoid: %s | IsWhole: %s", bndBox1.IsVoid() ? "True" : "False", bndBox1.IsWhole() ? "True" : "False");ImGui::Text("OpenXmin: %s | OpenXmax: %s", bndBox1.IsOpenXmin() ? "True" : "False", bndBox1.IsOpenXmax() ? "True" : "False");// 检测两个包围盒是否发生交集static bool showCollisionWindow = false;bool intersect = !bndBox1.IsOut(bndBox2);ImGui::Separator();ImGui::Text("Intersection Result: %s", intersect ? "Intersecting" : "No Intersection");// 如果发生交集,显示碰撞窗口if (intersect && !showCollisionWindow) {showCollisionWindow = true;}// 漂浮的非模态窗口,提示碰撞if (showCollisionWindow) {ImGui::SetNextWindowSize(ImVec2(300, 100), ImGuiCond_FirstUseEver);ImGui::SetNextWindowPos(ImVec2(100, 100), ImGuiCond_FirstUseEver);ImGui::Begin("Collision Warning", &showCollisionWindow, ImGuiWindowFlags_AlwaysAutoResize);ImGui::Text("Collision detected!");if (ImGui::Button("Close") || !intersect) {showCollisionWindow = false;}ImGui::End();}}private:// 几何体与可视化对象TopoDS_Shape box1, box2, box2Moved;Handle(AIS_Shape) aisBox1, aisBox2; // 原始几何的可视化Handle(AIS_Shape) box1BBox, box2BBox; // 包围盒的可视化Handle(AIS_Shape) rotatedBBox; // 预留旋转用包围盒(暂未用)gp_Trsf trsf2; // box2 的几何变换float box2Offset[3] = { 150.0f, 0.0f, 0.0f }; // Box2 的初始偏移量Bnd_Box bndBox1, bndBox2; // 两个几何体的包围盒(用于交集判断等)// 更新两个包围盒:将 box1 和 box2(已变换)添加到 bndBox 中void updateBndBoxes() {bndBox1.SetVoid(); // 重置包围盒bndBox2.SetVoid();BRepBndLib::Add(box1, bndBox1);BRepBndLib::Add(box2Moved.IsNull() ? box2 : box2Moved, bndBox2);}// 可视化包围盒:使用透明线框盒子表示 Bnd_Boxvoid displayBndBox(const Handle(AIS_InteractiveContext)& context, const Bnd_Box& bbox,Quantity_NameOfColor color, Handle(AIS_Shape)& bboxShapeOut) {// 如果为空或无穷大,则不显示if (bbox.IsVoid() || !bbox.HasFinitePart()) return;// 获取包围盒的最小/最大角点gp_Pnt pmin = bbox.CornerMin();gp_Pnt pmax = bbox.CornerMax();// 构造包围盒的形状TopoDS_Shape shape = BRepPrimAPI_MakeBox(pmin, pmax).Shape();// 构造 AIS 可视化对象bboxShapeOut = new AIS_Shape(shape);bboxShapeOut->SetColor(color);bboxShapeOut->SetDisplayMode(AIS_WireFrame); // 显示为线框context->Display(bboxShapeOut, Standard_False);}// 更新包围盒的 AIS 显示(用于动态变化后刷新显示)void updateBndAIS(const Handle(AIS_InteractiveContext)& context) {context->Remove(box1BBox, Standard_False);context->Remove(box2BBox, Standard_False);context->Remove(rotatedBBox, Standard_False);displayBndBox(context, bndBox1, Quantity_NOC_CYAN1, box1BBox);displayBndBox(context, bndBox2, Quantity_NOC_ORANGE1, box2BBox);}
};