【OCCT+ImGUI系列】003-GP-几何平面类gp_Pln

请添加图片描述

在三维几何建模中,平面(Plane)是最基础却又非常关键的几何对象之一。本文将通过一个完整的可交互代码示例,介绍如何使用 OpenCascade 中的 gp_Pln 来构造、可视化一个平面,并计算点到该平面的距离。我们将以 MDPlane003 这一教学类为例,剖析其实现细节与可视化交互逻辑。
请添加图片描述

一、什么是 gp_Pln

gp_Pln 是 OpenCascade 中的基本几何平面类,属于 gp(Geometry Package)模块。它用于表示一个三维空间中的无限大平面,其由一个点(位置)和一个方向(法向量)定义:

gp_Pln(const gp_Pnt& Location, const gp_Dir& Normal);

在这里插入图片描述
图中黄色框表示gp_Pln平面,红线为平面方向,绿色为三个构造点组成的平面。

二、相关功能

该类继承自 BaseSceneVisSceneComponentsTutorialWindow,实现了一个支持交互式构造与可视化平面的场景,包括以下功能:

  • 基于三点构造平面
  • 动态翻转平面方向
  • 计算任意点到该平面的距离
  • 可视化关键辅助图元:构造三角形、法向箭头、点到平面连线

构造平面逻辑

请添加图片描述
关键逻辑在于 CreatePlaneFromThreePoints 方法:

gp_Vec AB(A, B);
gp_Vec AC(A, C);
gp_Vec normal = AB.Crossed(AC);  // 法向量 = AB × AC
gp_Pln plane(A, normal);         // 用点 A 和法向量构造平面

平面可根据用户点击按钮实时构造,并可通过复选框“Flip Plane”反转法向量方向。

平面显示逻辑

平面通过构造 Geom_Plane 并生成可渲染面片:

Handle(Geom_Plane) geomPlane = new Geom_Plane(plane);
BRepBuilderAPI_MakeFace faceMaker(geomPlane->Pln(), -10, 10, -10, 10);

构造结果封装为 AIS_Shape 对象添加到交互上下文中。

可视化辅助线

辅助线如三角形边、法向量箭头通过 DisplayAuxLine 函数显示:

TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(start, end);
Handle(AIS_Shape) lineShape = new AIS_Shape(edge);

颜色、粗细、可交互性都支持定制。

三、点到平面的距离计算

用户可以输入一个点 D,当点击按钮后,会计算点 D 到当前平面的垂直距离,并实时显示:

m_distance = plane.Distance(D);

同时会生成一条蓝色连线辅助观察该距离。

四、代码

#pragma once

#include "pch.h"
#include <GCE2d_MakeLine.hxx> // 确保包含此头
#include <gce_MakePln.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS.hxx> 
#include <gp_Pln.hxx>
#include <Geom_Plane.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <GeomAPI_Interpolate.hxx>
#include <Vector>
#include "BaseScene.h"
#include "VisSceneComponents.h"
#include "TutorialWindow.h"

class MDPlane003 : public BaseScene, public VisSceneComponents, public TutorialWindow {
public:
    MDPlane003() { openTutorialWindow(); }

    void displayScene(const Handle(V3d_View)& view, const Handle(AIS_InteractiveContext)& context) override {
        if (context.IsNull()) return;

        if (!bIsSceneInit) {
            sceneInit(view, context);
            bIsSceneInit = true;
        }
        renderTutorialWindow(context);
    }

    void sceneInit(const Handle(V3d_View)& view, const Handle(AIS_InteractiveContext)& context) override {
        CreatePlaneFromThreePoints(context); // 使用三点构造的平面
    }

    void customInitTutorialWindow(const Handle(AIS_InteractiveContext)& context) override {
        CreatePlaneFromThreePoints(context); // 默认使用三点构造平面
    }

    void renderTutorialContent(const Handle(AIS_InteractiveContext)& context) override {
        if (ImGui::CollapsingHeader("Construct from 3 Points", ImGuiTreeNodeFlags_DefaultOpen)) {
            ImGui::SliderFloat3("Point A", pts.data(), -100.0f, 100.0f);
            ImGui::SliderFloat3("Point B", pts.data() + 3, -100.0f, 100.0f);
            ImGui::SliderFloat3("Point C", pts.data() + 6, -100.0f, 100.0f);

            if (ImGui::Button("Create Plane from 3 Points")) {
                CreatePlaneFromThreePoints(context);
            }

            if (ImGui::Checkbox("Flip Plane", &bFlipPlane)) {
                CreatePlaneFromThreePoints(context);
            }

            ImGui::SliderFloat3("Point D", pointD.data(), -100.0f, 100.0f); // 用于计算点到平面的距离

            if (ImGui::Button("Compute Point to Plane Distance")) {
                ComputePointToPlaneDistance(context);
            }
            ImGui::Text("Distance from point to plane: %.2f", m_distance);
        }
    }

private:
    void ClearAuxShapes(const Handle(AIS_InteractiveContext)& context) {
        for (auto& shape : auxShapes) {
            context->Remove(shape, Standard_False);
        }
        auxShapes.clear();
    }

    Handle(AIS_Shape) DisplayAuxLine(
        const Handle(AIS_InteractiveContext)& context,
        const gp_Pnt& start,
        const gp_Pnt& end,
        Quantity_NameOfColor color = Quantity_NOC_RED,
        Standard_Real width = 2.0) {
        TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(start, end);
        Handle(AIS_Shape) lineShape = new AIS_Shape(edge);
        lineShape->SetColor(color);
        lineShape->SetWidth(width);
        context->Display(lineShape, Standard_False);
        auxShapes.push_back(lineShape);
        return lineShape;
    }

    void CreatePlaneFromThreePoints(const Handle(AIS_InteractiveContext)& context) {
        try {
            gp_Pnt A(pts[0], pts[1], pts[2]);
            gp_Pnt B(pts[3], pts[4], pts[5]);
            gp_Pnt C(pts[6], pts[7], pts[8]);

            // 计算平面的法向量
            gp_Vec AB(A, B);
            gp_Vec AC(A, C);
            gp_Vec normal = AB.Crossed(AC);  // 使用叉积计算法向量

            if (bFlipPlane) {
                normal = normal.Reversed(); // 翻转法向量
            }

            // 创建平面
            gp_Pln plane(A, normal);
            CreateAndDisplayPlane(context, plane.Location(), plane.Axis().Direction(), Quantity_NOC_YELLOW);

            ClearAuxShapes(context);
            DisplayAuxLine(context, A, B, Quantity_NOC_GREEN);
            DisplayAuxLine(context, B, C, Quantity_NOC_GREEN);
            DisplayAuxLine(context, C, A, Quantity_NOC_GREEN);
            gp_Pnt normalTip = plane.Location().Translated(plane.Axis().Direction().XYZ() * 10);
            DisplayAuxLine(context, plane.Location(), normalTip, Quantity_NOC_RED);
            context->Redisplay(tutorialAisShape, Standard_True);
        }
        catch (Standard_Failure const&) {
            ImGui::TextColored(ImVec4(1, 0, 0, 1), "Invalid points for constructing plane.");
        }
    }

    void ComputePointToPlaneDistance(const Handle(AIS_InteractiveContext)& context) {
        try {
            gp_Pnt D(pointD[0], pointD[1], pointD[2]);

            if (tutorialAisShape.IsNull()) {
                ImGui::TextColored(ImVec4(1, 0, 0, 1), "Plane not initialized.");
                return;
            }

            TopoDS_Shape shape = tutorialAisShape->Shape();
            if (shape.ShapeType() != TopAbs_FACE) {
                ImGui::TextColored(ImVec4(1, 0, 0, 1), "Shape is not a face.");
                return;
            }

            TopoDS_Face face = TopoDS::Face(shape);
            Handle(Geom_Surface) surface = BRep_Tool::Surface(face);
            Handle(Geom_Plane) geomPlane = Handle(Geom_Plane)::DownCast(surface);

            if (geomPlane.IsNull()) {
                ImGui::TextColored(ImVec4(1, 0, 0, 1), "Surface is not a plane.");
                return;
            }

            gp_Pln plane = geomPlane->Pln();
            m_distance = plane.Distance(D);

            // 可视化点与平面连线
            ClearAuxShapes(context);
            DisplayAuxLine(context, D, plane.Location(), Quantity_NOC_BLUE, 2.0);

        }
        catch (Standard_Failure const&) {
            ImGui::TextColored(ImVec4(1, 0, 0, 1), "Error in computing distance.");
        }
    }

    void CreateAndDisplayPlane(const Handle(AIS_InteractiveContext)& context,
        const gp_Pnt& origin,
        const gp_Dir& normal,
        Quantity_NameOfColor color) {
        gp_Pln plane(origin, normal);
        Standard_Real size = 20;
        Handle(Geom_Plane) geomPlane = new Geom_Plane(plane);
        BRepBuilderAPI_MakeFace faceMaker(
            geomPlane->Pln(),
            -size / 2, size / 2,
            -size / 2, size / 2);

        if (!tutorialAisShape.IsNull()) {
            context->Remove(tutorialAisShape, Standard_False);
        }
        tutorialAisShape = new AIS_Shape(faceMaker.Shape());
        tutorialAisShape->SetColor(color);
        tutorialAisShape->SetTransparency(0.5);
        context->Display(tutorialAisShape, Standard_True);
    }

private:
    std::array<float, 3> planeOrigin{ 0.0f, 0.0f, 0.0f };
    std::array<float, 3> planeNormal{ 0.0f, 0.0f, 1.0f };
    std::array<float, 9> pts{ 0.0f, 0.0f, 0.0f, 10.0f, 0.0f, 0.0f, 0.0f, 10.0f, 0.0f };
    std::array<float, 3> pointD{ 0.0f, 0.0f, 0.0f }; // 用于输入计算点到平面距离的点
    Handle(AIS_Shape) tutorialAisShape;
    std::vector<Handle(AIS_Shape)> auxShapes;
    bool            bFlipPlane  = false;    // 是否翻转平面
    Standard_Real   m_distance  = 0;        // 点到平面距离
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值