【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();

用于从无限盒中提取有限部分,仅当存在有限部分时有效。


八、使用建议与注意事项

  1. Void 状态下不能 Get() 或 CornerMin(),需先判断 IsVoid()
  2. Gap 设定应适度,避免误判碰撞。
  3. 添加射线或方向后必须明确是否仍保持有限,尤其用于 FCL 或距离判断时。
  4. Transformed() 不保持最小体积,谨慎用于碰撞预处理。

九、总结

Bnd_Box 是 OpenCASCADE 中简单而高效的空间管理工具,适用于几乎所有涉及空间范围的模块。从基础的点添加到复杂的变换包围,都可使用统一接口处理,为 CAD 几何计算提供良好支撑。


相关概念拓展阅读

演示代码

#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

        // 创建并设置可视化对象 aisBox1
        aisBox1 = new AIS_Shape(box1);
        aisBox1->SetColor(Quantity_NOC_BLUE1);
        context->Display(aisBox1, Standard_False);

        // 创建并设置可视化对象 aisBox2
        aisBox2 = 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_Box
    void 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);
    }
};

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值