简介

BGL (Basic Geometry Library) 是一个关于三维数据(点云,网格)处理的基础几何库. 它包含了三维数据处理最基础的数据结构。


使用简介

1. 头文件: 所有的头文件都在include文件夹里, 设置好头文件包含路径, 在使用api的源文件里包含BGL.h

2. 库: 设置好对应版本动态链接库的路径. 如果是Windows平台,需要在预处理里定义宏BGL_DLL_EXPORT,WIN32


IPointCloud

点云数据结构接口. Geometry++点云算法都是基于IPointCloud接口来调用的.

用法:继承这个接口类,实现其成员函数.

优点:可以很方便的在已有的程序中使用Geometry++提供的点云算法,只需要实现这个接口类,就可以调用所有点云相关的算法

用法说明:

  • 假设用户表示点云数据的类为MyPointCloudData, 则可以定义一个类MyPointCloud
  •     class MyPointCloud : public IPointCloud
        {
            MyPointCloudData* mData;
            MyPointCloud(MyPointCloudData* data) : mData(data) 
            {}
            virtual Int GetPointCount() const
            { 
                mData->GetPointCloud(); 
            }
            virtual Vector3 GetPointCoord() const 
            { 
                mData->GetPointCoord(); 
            }
            virtual void SetPointCoord(Int pid, const Vector& coord) 
            { 
                mData->SetPointCoord(pid, coord[0], coord[1], coord[2]); 
            }
            // 其它成员函数类似
        };
    
        MyPointCloud pointCloud(myPointCloudData); // 用自己的点云数据初始化MyPointCloud
        ErrorCode res = ConsolidatePointCloud::LaplaceSmooth(pointCloud, 0.2, 5); // 调用点云算法API来修改自己的点云数据
        res = ConsolidatePointCloud::CalculatePointCloudNormal(pointCloud);
        // 其它点云算法API的具体用法可以参考相应模块的介绍
    
  • 构造点云:通过InsertPoint不断的往点云里加点.
  • 点云顶点的存储格式是线性的,获取方便,但是删除会存在一些效率问题. IPointCloud提供了SwapPoint函数把需要删除的元素交换到尾部,然后再通过PopbackPoints删除尾部元素.
  • Clear函数负责清除Point Coordinate, Point Normal, Point Color. 回到构造类初始化时的状态.
  • HasNormal, HasColor函数主要用意: 有时候点云创建后没有法线或者颜色信息,IPointCloud提供这些函数查询点云是否有可靠的法线或者颜色信息. SetHasNormal, SetHasColor函数可以设置点云是否有法线或者颜色信息

  • IGridPointCloud

    有序点云的数据结构接口,它继承于IPointCloud。

    有序点云是一个方阵,如图所示。点云按照方阵一行一行的,从左上角到右下角排列。

    Grid Point Cloud

    优点:相比IPointCloud,IGridPointCloud多了点云的位置连接关系信息。相关的计算可以更加快速。

    用法说明:

  • 构造有序点云:首先通过InitGrid初始化有序点云,设置长宽。初始化的点云格点默认为invalid。然后通过SetGridCoord往点云里加点。与IPointCloud不同的是,它不能通过InsertPoint函数来加点,因为这个函数没有格点坐标信息。
  • RemoveOuterBlankGrids:移出掉四周无效的点,并重新设置点云的长宽。如下图所示,原始点云变为了红色框内缩小长宽的有序点云。
  • RemoveOuterBlankGrids
  • SetGridCoord:为格点设置坐标。也可以通过此函数来向点云里加点,因为格点默认是invalid,设置坐标以后就valid
  • SetGridNormal:为格点设置法线信息。注意,此格点必须是valid
  • GetGridPointId:获取格点对应的有效格点的索引。若格点无效,则返回-1
  • GetPointGridId:获取有效格点对应的格点坐标
  • 导入导出:可以使用gbg和gtg格式的文件存储有序点云。

  • PointCloud

    PointCloud是IPointCloud接口类和IGridPointCloud接口类的一个实现,用户可以直接使用它来表示点云数据

    这是PointCloud的一个实现示例:

        Int PointCloud::GetPointCount() const
        {
            return mCoordList.size();
        }
    
        Vector3 PointCloud::GetPointCoord(Int pid) const
        {
            return mCoordList.at(pid);
        }
    
        void PointCloud::SetPointCoord(Int pid, const Vector3& coord)
        {
            mCoordList.at(pid) = coord;
        }
    
        bool PointCloud::HasNormal() const
        {
            return mHasNormal;
        }
        
        void PointCloud::SetHasNormal(bool hasNormal)
        {
            mHasNormal = hasNormal;
            if (mHasNormal && mNormalList.size() < mCoordList.size())
            {
                mNormalList.resize(mCoordList.size());
            }
        }
    
        Vector3 PointCloud::GetPointNormal(Int pid) const
        {
            if (mHasNormal)
            {
                return mNormalList.at(pid);
            }
            else
            {
                return Vector3(0, 0, 0);
            }
        }
    
        void PointCloud::SetPointNormal(Int pid, const Vector3& normal)
        {
            mNormalList.at(pid) = normal;
        }
    
        bool PointCloud::HasColor(void) const
        {
            return mHasColor;
        }
    
        void PointCloud::SetHasColor(bool has)
        {
            mHasColor = has;
            if (mHasColor && mColorList.size() < mCoordList.size())
            {
                mColorList.resize(mCoordList.size());
            }
        }
    
        Vector3 PointCloud::GetPointColor(Int pid) const
        {
            if (mHasColor)
            {
                return mColorList.at(pid);
            }
            else
            {
                return mDefaultColor;
            }
        }
    
        void PointCloud::SetPointColor(Int pid, const Vector3& color)
        {
            mColorList.at(pid) = color;
        }
    
        void PointCloud::ReservePoint(Int pointCount)
        {
            mCoordList.reserve(pointCount);
            if (mHasNormal)
            {
                mNormalList.reserve(pointCount);
            }
            if (mHasColor)
            {
                mColorList.reserve(pointCount);
            }
        }
    
        Int PointCloud::InsertPoint(const Vector3& coord)
        {
            mCoordList.push_back(coord);
            if (mHasNormal)
            {
                mNormalList.resize(mCoordList.size());
            }
            if (mHasColor)
            {
                mColorList.resize(mCoordList.size());
            }
            if (mIsGrid)
            {
                ClearGridTopologyInfo();
            }
            return mCoordList.size() - 1;
        }
    
        Int PointCloud::InsertPoint(const Vector3& coord, const Vector3& normal)
        {
            mCoordList.push_back(coord);
            mNormalList.push_back(normal);
            if (mHasColor)
            {
                mColorList.resize(mCoordList.size());
            }
            if (mIsGrid)
            {
                ClearGridTopologyInfo();
            }
            return mCoordList.size() - 1;
        }
    
        void PointCloud::SwapPoint(Int pointId0, Int pointId1)
        {
            Vector3 tempCoord = mCoordList.at(pointId0);
            mCoordList.at(pointId0) = mCoordList.at(pointId1);
            mCoordList.at(pointId1) = tempCoord;
            if (mHasNormal)
            {
                Vector3 tempNormal = mNormalList.at(pointId0);
                mNormalList.at(pointId0) = mNormalList.at(pointId1);
                mNormalList.at(pointId1) = tempNormal;
            }
            if (mHasColor)
            {
                Vector3 tempColor = mColorList.at(pointId0);
                mColorList.at(pointId0) = mColorList.at(pointId1);
                mColorList.at(pointId1) = tempColor;
            }
            if (mIsGrid)
            {
                Int gridIndex0 = mPoint2GridMap.at(pointId0);
                Int gridIndex1 = mPoint2GridMap.at(pointId1);
                mGrid2PointMap.at(gridIndex0) = pointId1;
                mGrid2PointMap.at(gridIndex1) = pointId0;
                mPoint2GridMap.at(pointId0) = gridIndex1;
                mPoint2GridMap.at(pointId1) = gridIndex0;
            }
        }
    
        void PointCloud::PopbackPoints(Int popCount)
        {
            Int pointCount = mCoordList.size();
            mCoordList.erase(mCoordList.begin() + pointCount - popCount, mCoordList.end());
            if (mHasNormal)
            {
                mNormalList.erase(mNormalList.begin() + pointCount - popCount, mNormalList.end());
            }
            if (mHasColor)
            {
                mColorList.erase(mColorList.begin() + pointCount - popCount, mColorList.end());
            }
            if (mIsGrid)
            {
                for (Int pid = 1; pid <= popCount; pid++)
                {
                    mGrid2PointMap.at(mPoint2GridMap.at(pointCount - pid)) = -1;
                }
                mPoint2GridMap.erase(mPoint2GridMap.begin() + pointCount - popCount, mPoint2GridMap.end());
            }
        }
    
        void PointCloud::InitGrid(Int width, Int height)
        {
            mGridWidth = width;
            mGridHeight = height;
            std::vector().swap(mPoint2GridMap);
            std::vector().swap(mGrid2PointMap);
            mGrid2PointMap.resize(width * height, -1);
            mIsGrid = true;
            std::vector().swap(mCoordList);
            std::vector().swap(mNormalList);
            std::vector().swap(mColorList);
        }
    
        bool PointCloud::IsGridValid(Int wid, Int hid) const
        {
            return (mGrid2PointMap.at(hid * mGridWidth + wid) != -1);
        }
    
        void PointCloud::InvalidateGrid(Int wid, Int hid)
        {
            Int gridIndex = hid * mGridWidth + wid;
            if (mGrid2PointMap.at(gridIndex) != -1)
            {
                SwapPoint(mGrid2PointMap.at(gridIndex), mPoint2GridMap.size() - 1);
                PopbackPoints(1);
            }
        }
    
        Int PointCloud::GetGridPointId(Int wid, Int hid) const
        {
            return mGrid2PointMap.at(hid * mGridWidth + wid);
        }
    
        void PointCloud::GetPointGridId(Int pointId, Int& wid, Int& hid) const
        {
            wid = mPoint2GridMap.at(pointId) % mGridWidth;
            hid = mPoint2GridMap.at(pointId) / mGridWidth;
        }
    
        Vector3 PointCloud::GetGridCoord(Int wid, Int hid) const
        {
            return mCoordList.at(mGrid2PointMap.at(hid * mGridWidth + wid));
        }
    
        void PointCloud::SetGridCoord(Int wid, Int hid, const Vector3& coord)
        {
            Int gridIndex = hid * mGridWidth + wid;
            if (mGrid2PointMap.at(gridIndex) == -1)
            {
                mGrid2PointMap.at(gridIndex) = mPoint2GridMap.size();
                mPoint2GridMap.push_back(gridIndex);
                mCoordList.push_back(coord);
                if (mHasNormal)
                {
                    mNormalList.resize(mCoordList.size());
                }
                if (mHasColor)
                {
                    mColorList.resize(mCoordList.size());
                }
            }
            else
            {
                mCoordList.at(mGrid2PointMap.at(gridIndex)) = coord;
            }
        }
        
        Vector3 PointCloud::GetGridNormal(Int wid, Int hid) const
        {
            if (mHasNormal)
            {
                return mNormalList.at(mGrid2PointMap.at(hid * mGridWidth + wid));
            }
            else
            {
                return Vector3(0, 0, 0);
            }
        }
    
        void PointCloud::SetGridNormal(Int wid, Int hid, const Vector3& normal)
        {
            mNormalList.at(mGrid2PointMap.at(hid * mGridWidth + wid)) = normal;
        }
    
        Vector3 PointCloud::GetGridColor(Int wid, Int hid) const
        {
            if (mHasColor)
            {
                return mColorList.at(mGrid2PointMap.at(hid * mGridWidth + wid));
            }
            else
            {
                return mDefaultColor;
            }
        }
    
        void PointCloud::SetGridColor(Int wid, Int hid, const Vector3& color)
        {
            mColorList.at(mGrid2PointMap.at(hid * mGridWidth + wid)) = color;
        }
    
        bool PointCloud::HasGridTopology(void) const
        {
            return mIsGrid;
        }
    

    注意事项:

  • UnifyCoords的典型使用场景:在多帧点云使用过程中,如果对第一帧点云应用了前一个UnifyCoords,为了保持之后帧的点云做同样的平移缩放变换,可以用前一个UnifyCoords的返回值来设置这个UnifyCoords,使得每一帧的点云做同样的变换
  • 有序点云在调用了InsertPoint之后,会变成无序点云

  • 点云导入导出
    PointCloud* Parser::ImportPointCloud(std::string fileName);
    

    目前支持的文件格式:obj, asc. 用户也可以导入其它格式的点云数据, 只需要自己写一个Parser, 然后用导入的数据创建点云类(例如PointCloud)即可使用了

    fileName: 格式为path/xxx.obj, path/xxx.asc, path可以是绝对路径,也可以相对路径

    返回值: 如果导入失败则返回NULL


    void Parser::ExportPointCloud(std::string fileName, const IPointCloud* pointCloud);
    

    目前支持的文件格式:obj, asc, ply.

    fileName: 格式为path/xxx.obj, path/xxx.asc, path可以是绝对路径,也可以相对路径


    void Parser::ExportGridPointCloud(std::string fileName, const IGridPointCloud* pointCloud);
    

    目前支持的文件格式:gbg(二进制), gtg(文本).

    fileName: 格式为path/xxx.gbg, path/xxx.gtg, path可以是绝对路径,也可以相对路径


    ITriMesh

    三角网格数据结构接口. Geometry++网格算法都是基于ITriMesh接口来调用的.

    用法:继承这个接口类,实现其成员函数.

    优点:可以很方便的在已有的程序中使用Geometry++提供的网格算法,只需要实现这个接口类,就可以调用所有网格相关的算法

    用法说明:

  • 假设用户表示三角网格数据的类为MyTriMeshData, 则可以定义一个类MyTriMesh:
  •     class MyTriMesh : public ITriMesh
        {
            MyTriMeshData* mData;
            MyTriMesh(MyTriMeshData* data) : mData(data) 
            {}
            virtual Int GetVertecCount() const 
            { 
                return mData->GetVertecCount(); 
            }
            virtual Vector3 GetVertexCoord(Int vid) const 
            { 
                return mData->GetVertexCoord(); 
            }
            virtual void SetVertexCoord(Int vid, const Vector3& coord) 
            { 
                mData->SetVertexCoord(vid, coord[0], coord[1], coord[2]); 
            }
            virtual Int InsertVertex(const Vector3& coord) 
            { 
                mData->InsertVertex(coord); 
                return insertVertexId; 
            }
            // 其它成员函数类似
        };
    
        MyTriMesh triMesh(myTriMeshData); // 用自己的三角网格数据初始化MyTriMesh
        ErrorCode res = ConsolidateMesh::LaplaceSmooth(triMesh, 0.2, 5, true); // 调用网格算法API来修改自己的网格数据
        res = ConsolidateMesh::MakeTriMeshManifold(triMesh);
        // 其它网格算法API的具体用法可以参考相应模块的介绍
    
  • 构建网格示例:
  •     for (GPP::Int vid = 0; vid < vertexCount; vid++)
        {
            triMesh->InsertVertex(vertexCoord[vid]);
        }
        for (GPP::Int fid = 0; fid < faceCount; fid++)
        {
            triMesh->InsertTriangle(triangleIndex[fid][0], triangleIndex[fid][1], triangleIndex[fid][2]);
        }
        triMesh->UpdateNormal();
    
  • 三角网格顶点和面的存储格式是线性的,获取方便,但是删除会存在一些效率问题. ITriMesh提供了SwapVertex(SwapTriangles) 函数把需要删除的元素交换到尾部,然后再通过PopbackVertices(PopbackTriangles)删除尾部元素.
  • 网格拓扑改变后,顶点和三角片所对应的属性如何相应的改变:这里的拓扑改变是指Swap**, Popback**函数。TriMesh在调用这类函数之后,顶点或三角片的顺序发生了变化。如果我们能知道变化后和变化前,顶点和三角片的对应,则可以其对应的属性就可以做相应的更新了。一般有两种思路:1. 属性属于网格的成员变量,则可以在Swap**, Popback**里做相应的属性更新,具体可以参考TriMesh的实现;2. 属性独立于网格存在,则用户可以在网格类里添加顶点和三角片的Id信息,并在Swap**, Popback**里做相应的Id更新,这样就可以得到改变后顶点或三角片与之前的对应信息了,下面是一个示例:
  •     void MyTriMesh::SwapVertex(Int vertexId0, Int vertexId1)
        {
            Int tempId = mVertexIds.at(vertexId0);
            mVertexIds.at(vertexId0) = mVertexIds.at(vertexId1);
            mVertexIds.at(vertexId1) = tempId;
        }
    
        void MyTriMesh::PopbackVertices(Int popCount)
        {
            mVertexIds.erase(mVertexIds.begin() + mVertexIds.size() - popCount, mVertexIds.end());
        }
    
        void MyTriMesh::SwapTriangles(Int fid0, Int fid1)
        {
            Int tempId = mTriangleIds.at(fid0);
            mTriangleIds.at(fid0) = mTriangleIds.at(fid1);
            mTriangleIds.at(fid1) = tempId;
        }
    
        void MyTriMesh::PopbackTriangles(Int popCount)
        {
            mTriangleIds.erase(mTriangleIds.begin() + mTriangleIds.size() - popCount, mTriangleIds.end());
        }
    
  • Clear函数负责清除Vertex Coordinate, Vertex Normal, Triangle Index, Triangle Normal. 回到构造类初始化时的状态.
  • UpdateNormal负责重新计算网格的顶点和三角面的法线信息.

  • TriMesh

    ITriMesh接口类的一个实现,用户可以直接使用它来表示网格数据

    这是TriMesh的一个实现示例:

        Int TriMesh::GetVertexCount() const
        {
            return mVertexCoordList.size();
        }
    
        Vector3 TriMesh::GetVertexCoord(Int vid) const
        {
            return mVertexCoordList.at(vid);
        }
    
        void TriMesh::SetVertexCoord(Int vid, const Vector3& coord)
        {
            mVertexCoordList.at(vid) = coord;
        }
    
        Vector3 TriMesh::GetVertexNormal(Int vid) const
        {
            return mVertexNormalList.at(vid);
        }
    
        void TriMesh::SetVertexNormal(Int vid, const Vector3& normal)
        {
            if (mVertexNormalList.empty())
            {
                mVertexNormalList.resize(mVertexCoordList.size());
            }
            mVertexNormalList.at(vid) = normal;
        }
    
        bool TriMesh::HasVertexNormal(void) const
        {
            return mHasVertexNormal;
        }
    
        Int TriMesh::GetTriangleCount() const
        {
            return mTriangleList.size();
        }
    
        void TriMesh::GetTriangleVertexIds(Int fid, Int vertexIds[3]) const
        {
            vertexIds[0] = mTriangleList.at(fid)->mIndex[0];
            vertexIds[1] = mTriangleList.at(fid)->mIndex[1]; 
            vertexIds[2] = mTriangleList.at(fid)->mIndex[2]; 
        }
    
        void TriMesh::SetTriangleVertexIds(Int fid, Int vertexId0, Int vertexId1, Int vertexId2)
        {
            mTriangleList.at(fid)->mIndex[0] = vertexId0;
            mTriangleList.at(fid)->mIndex[1] = vertexId1;
            mTriangleList.at(fid)->mIndex[2] = vertexId2;
        }
    
        Vector3 TriMesh::GetTriangleNormal(Int fid) const
        {
            return mTriangleNormalList.at(fid);
        }
    
        void TriMesh::SetTriangleNormal(Int fid, const Vector3& normal)
        {
            if (mTriangleNormalList.empty())
            {
                mTriangleNormalList.resize(mTriangleList.size());
            }
            mTriangleNormalList.at(fid) = normal;
        }
    
        bool TriMesh::HasTriangleNormal(void) const
        {
            return mHasTriangleNormal;
        }
    
        Vector3 TriMesh::GetVertexColor(Int vid) const
        {
            if (mHasVertexColor)
            {
                return mVertexColorList.at(vid);
            }
            else
            {
                return mDefaultColor;
            }
        }
    
        void TriMesh::SetVertexColor(Int vid, const Vector3& color)
        {
            mVertexColorList.at(vid) = color;
        }
    
        Vector3 TriMesh::GetVertexTexcoord(Int vid) const
        {
            return mVertexTexCoordList.at(vid);
        }
    
        void TriMesh::SetVertexTexcoord(Int vid, const Vector3& texcoord)
        {
            mVertexTexCoordList.at(vid) = texcoord;
        }
    
        Vector3 TriMesh::GetTriangleColor(Int fid, Int localVid) const
        {
            if (mHasTriangleColor)
            {
                return mTriangleColorList.at(fid * 3 + localVid);
            }
            else
            {
                return mDefaultColor;
            }
        }
        
        void TriMesh::SetTriangleColor(Int fid, Int localVid, const Vector3& color)
        {
            mTriangleColorList.at(fid * 3 + localVid) = color;
        }
    
        Vector3 TriMesh::GetTriangleTexcoord(Int fid, Int localVid) const
        {
            return mTriangleTexCoordList.at(fid * 3 + localVid);
        }
    
        void TriMesh::SetTriangleTexcoord(Int fid, Int localVid, const Vector3& texcoord)
        {
            mTriangleTexCoordList.at(fid * 3 + localVid) = texcoord;
        }
    
        Int TriMesh::InsertVertex(const Vector3& coord)
        {
            mVertexCoordList.push_back(coord);
            if (mHasVertexNormal)
            {
                mVertexNormalList.push_back(Vector3(0, 0, 0));
            }
            if (mHasVertexColor)
            {
                mVertexColorList.push_back(Vector3(0, 0, 0));
            }
            if (mHasVertexTexCoord)
            {
                mVertexTexCoordList.push_back(Vector3(0, 0, 0));
            }
            return (mVertexCoordList.size() - 1);
        }
        
        Int TriMesh::InsertVertex(const Vector3& coord, const Vector3& normal)
        {
            mVertexCoordList.push_back(coord);
            mVertexNormalList.push_back(normal);
            if (mHasVertexColor)
            {
                mVertexColorList.push_back(Vector3(0, 0, 0));
            }
            if (mHasVertexTexCoord)
            {
                mVertexTexCoordList.push_back(Vector3(0, 0, 0));
            }
            return (mVertexCoordList.size() - 1);
        }
    
        void TriMesh::SetHasVertexColor(bool has)
        {
            mHasVertexColor = has;
            if (mHasVertexColor && mVertexColorList.size() < mVertexCoordList.size())
            {
                mVertexColorList.resize(mVertexCoordList.size());
            }
        }
    
        bool TriMesh::HasVertexColor() const
        {
            return mHasVertexColor;
        }
    
        void TriMesh::SetHasVertexTexCoord(bool has)
        {
            mHasVertexTexCoord = has;
            if (mHasVertexTexCoord && mVertexTexCoordList.size() < mVertexCoordList.size())
            {
                mVertexTexCoordList.resize(mVertexCoordList.size());
            }
        }
    
        bool TriMesh::HasVertexTexCoord(void) const
        {
            return mHasVertexTexCoord;
        }
    
        void TriMesh::SetHasTriangleColor(bool has)
        {
            mHasTriangleColor = has;
            if (mHasTriangleColor && mTriangleColorList.size() < mTriangleList.size() * 3)
            {
                mTriangleColorList.resize(mTriangleList.size() * 3);
            }
        }
        
        bool TriMesh::HasTriangleColor(void) const
        {
            return (mHasTriangleColor && (mTriangleColorList.size() == mTriangleList.size() * 3));
        }
    
        void TriMesh::SetHasTriangleTexCoord(bool has)
        {
            mHasTriangleTexCoord = has;
            if (mHasTriangleTexCoord && mTriangleTexCoordList.size() < mTriangleList.size() * 3)
            {
                mTriangleTexCoordList.resize(mTriangleList.size() * 3);
            }
        }
    
        bool TriMesh::HasTriangleTexCoord(void) const
        {
            return mHasTriangleTexCoord;
        }
    
        Int TriMesh::InsertTriangle(Int vertexId0, Int vertexId1, Int vertexId2)
        {
            TriangleInfo* triangleInfo = new TriangleInfo(vertexId0, vertexId1, vertexId2);
            mTriangleList.push_back(triangleInfo);
            if (mHasTriangleNormal)
            {
                mTriangleNormalList.push_back(Vector3(0, 0, 0));
            }
            if (mHasTriangleTexCoord)
            {
                mTriangleTexCoordList.resize(mTriangleTexCoordList.size() + 3);
            }
            if (mHasTriangleColor)
            {
                mTriangleColorList.resize(mTriangleColorList.size() + 3);
            }
            return (mTriangleList.size() - 1);
        }
    
        void TriMesh::SwapVertex(Int vertexId0, Int vertexId1)
        {
            Vector3 temp = mVertexCoordList.at(vertexId0);
            mVertexCoordList.at(vertexId0) = mVertexCoordList.at(vertexId1);
            mVertexCoordList.at(vertexId1) = temp;
            if (mHasVertexNormal)
            {
                Vector3 normalTemp = mVertexNormalList.at(vertexId0);
                mVertexNormalList.at(vertexId0) = mVertexNormalList.at(vertexId1);
                mVertexNormalList.at(vertexId1) = normalTemp;
            }
            if (mHasVertexColor)
            {
                Vector3 tempColor = mVertexColorList.at(vertexId0);
                mVertexColorList.at(vertexId0) = mVertexColorList.at(vertexId1);
                mVertexColorList.at(vertexId1) = tempColor;
            }
            if (mHasVertexTexCoord)
            {
                Vector3 tempCoord = mVertexTexCoordList.at(vertexId0);
                mVertexTexCoordList.at(vertexId0) = mVertexTexCoordList.at(vertexId1);
                mVertexTexCoordList.at(vertexId1) = tempCoord;
            }
        }
    
        void TriMesh::PopbackVertices(Int popCount)
        {
            Int vertexCount = mVertexCoordList.size();
            mVertexCoordList.erase(mVertexCoordList.begin() + vertexCount - popCount, mVertexCoordList.end());
            if (mHasVertexNormal)
            {
                mVertexNormalList.erase(mVertexNormalList.begin() + vertexCount - popCount, mVertexNormalList.end());
            }
            if (mHasVertexColor)
            {
                mVertexColorList.erase(mVertexColorList.begin() + vertexCount - popCount, mVertexColorList.end());
            }
            if (mHasVertexTexCoord)
            {
                mVertexTexCoordList.erase(mVertexTexCoordList.begin() + vertexCount - popCount, mVertexTexCoordList.end());
            }
        }
    
        void TriMesh::SwapTriangles(Int fid0, Int fid1)
        {
            TriangleInfo* temp = mTriangleList.at(fid0);
            mTriangleList.at(fid0) = mTriangleList.at(fid1);
            mTriangleList.at(fid1) = temp;
            if (mHasTriangleNormal)
            {
                Vector3 normalTemp = mTriangleNormalList.at(fid0);
                mTriangleNormalList.at(fid0) = mTriangleNormalList.at(fid1);
                mTriangleNormalList.at(fid1) = normalTemp;
            }
            if (mHasTriangleTexCoord)
            {
                Int baseIndex0 = fid0 * 3;
                Int baseIndex1 = fid1 * 3;
                for (Int localId = 0; localId < 3; localId++)
                {
                    Vector3 tempCoord = mTriangleTexCoordList.at(baseIndex0 + localId);
                    mTriangleTexCoordList.at(baseIndex0 + localId) = mTriangleTexCoordList.at(baseIndex1 + localId);
                    mTriangleTexCoordList.at(baseIndex1 + localId) = tempCoord;
                }
            }
            if (mHasTriangleColor)
            {
                Int baseIndex0 = fid0 * 3;
                Int baseIndex1 = fid1 * 3;
                for (Int localId = 0; localId < 3; localId++)
                {
                    Vector3 tempCoord = mTriangleColorList.at(baseIndex0 + localId);
                    mTriangleColorList.at(baseIndex0 + localId) = mTriangleColorList.at(baseIndex1 + localId);
                    mTriangleColorList.at(baseIndex1 + localId) = tempCoord;
                }
            }
        }
    
        void TriMesh::PopbackTriangles(Int popCount)
        {
            Int faceCount = mTriangleList.size();
            for (Int fid = 0; fid < popCount; fid++)
            {
                GPPFREEPOINTER(mTriangleList.at(faceCount - 1 - fid));
            }
            mTriangleList.erase(mTriangleList.begin() + mTriangleList.size() - popCount, mTriangleList.end());
            if (mHasTriangleNormal)
            {
                mTriangleNormalList.erase(mTriangleNormalList.begin() + mTriangleNormalList.size() - popCount, mTriangleNormalList.end());
            }
            if (mHasTriangleTexCoord)
            {
                mTriangleTexCoordList.erase(mTriangleTexCoordList.begin() + mTriangleTexCoordList.size() - popCount * 3, mTriangleTexCoordList.end());
            }
            if (mHasTriangleColor)
            {
                mTriangleColorList.erase(mTriangleColorList.begin() + mTriangleColorList.size() - popCount * 3, mTriangleColorList.end());
            }
        }
    

    注意事项:

  • 如果导入的网格是STL格式,请使用FuseVertex建立网格的连接关系. 顶点位置相同的点会被处理为一个顶点. STL格式不能表示带缝的网格,因为它没有拓扑信息, 属于Triangle Soup. 建议使用OBJ格式的网格
  • UnifyCoords使得TriMesh的顶点坐标会被平移和均匀缩放到范围(-bboxSize, bboxSize)以内

  • 半边结构
    class HalfMesh

    HalfMesh类是网格半边结构的一个实现,用户可以直接使用它来表示网格数据. 半边结构的介绍可以参考这个网页

    Half Edge Structure
    图示:半边数据结构示例. 左边红点为内点,右边红点为边界点

    用法说明:

    1. 构建网格示例:

        for (GPP::Int vid = 0; vid < vertexCount; vid++)
        {
            halfMesh->InsertVertex(vertexCoord[vid]);
        }
        std::vector< Vertex3D* > vertexList;
        for (GPP::Int fid = 0; fid < faceCount; fid++)
        {
            vertexList.clear();
            vertexList.push_back(halfMesh->GetVertex(triangleIndex[fid][0]));
            vertexList.push_back(halfMesh->GetVertex(triangleIndex[fid][1]));
            vertexList.push_back(halfMesh->GetVertex(triangleIndex[fid][2]));
            halfMesh->InsertFace(vertexList);
        }
        halfMesh->SetBoundaryVertexEdge();
        halfMesh->UpdateNormal();
    

    2. 如果知道Vertex, Edge, Face的数量,在做Insert前,可以调用ReverseVertex, ReserveEdge, ReserveFace来提升Insert的效率,原理类似std::vector::reserve

    3. 访问顶点邻域示例:

        Edge3D* startEdge = vertex->GetEdge();
        Edge3D* curEdge = startEdge;
        do {
            if (curEdge->GetPair()->GetFace() == NULL)
                break;
            curEdge = curEdge->GetPair()->GetNext();
        } while (curEdge != startEdge);
    

    4. 对于边界点,函数SetBoundaryVertexEdge使得其出边(GetEdge)位于边界边(如上图右红边所示)

    5. ValidateTopology做了3件事情: a. 移出没有Face的Edge; b. SetBoundaryVertexEdge; c. 移出孤立Vertex. 一般对HalfMesh做了拓扑改变后用

    6. 边界点判断:vertex->GetEdge()->GetFace() == NULL; 边界边判断: edge->GetFace() == NULL || edge->GetPair()->GetFace() == NULL

    7. Edge3D的Pair Edge(GetPair)一定不是NULL. 如果Edge3D的Face(GetFace)不是NULL, 则Pre Edge(GetPre)和Next Edge(GetNext)一定不是NULL


    网格导入导出
    TriMesh* Parser::ImportTriMesh(std::string fileName);
    

    目前支持的文件格式: obj, stl, off, ply. 用户也可以导入其它格式的网格数据, 只需要自己写一个Parser, 然后用导入的数据创建网格类(例如TriMesh)即可使用了

    fileName: 格式为path/xxx.obj, path/xxx.stl, path可以是绝对路径,也可以相对路径

    返回值: 如果导入失败则返回NULL


    void Parser::ExportTriMesh(std::string fileName, const ITriMesh* triMesh);
    

    目前支持的文件格式: obj, stl, ply

    fileName: 格式为path/xxx.obj, path/xxx.stl, path可以是绝对路径,也可以相对路径


    最近邻域点查询

    NNQuery: Nearest Neighbor Query。最近邻域查询工具。

    NNQuery::Init: NNQuery初始化。它给空间的点集建立一个KD Tree,提供最近邻域点的查询效率。

    NNQuery::FindKNN: 查找最近的K个点,按照距离的升序排列。

    NNQuery::FindRadiusNN: 查找半径内的点集,无序。注意squareRadius是半径平方。


    线性方程组(稠密)

    求解稠密矩阵的线性方程组。

    GeneralMatrix:稠密矩阵

    GeneralMatrix3:3X3的稠密矩阵。与GeneralMatrix的区别在于,GeneralMatrix3的特征值求解速度更快一些。

    LinearGeneralLUSolver:线性方程组求解,LU分解法。

    LeastSquareGeneralLDLSolver:最小二乘求解,LDL分解法。

    SelfAdjointEigenSolver:特征值求解。

    OrthgonalApproximation:仿射变换的旋转矩阵近似求解。


    线性方程组(稀疏)

    求解稀疏矩阵的线性方程组。

    SparseMatrix:稀疏矩阵

    LinearSparseLUSolver:线性方程组求解,LU分解法。

    LinearSparseLLTSolver:线性方程组求解,LLT分解法。

    LinearSparseCGSolver:线性方程组求解,共轭梯度法。

    LeastSquareSparseLLTSolver:最小二乘求解,LLT分解法。

    LeastSquareSparseCGSolver:最小二乘求解,共轭梯度法。


    齐次变换

    Matrix4X4: 齐次变换矩阵。

    齐次坐标,就是在传统坐标后面加入一维变量:C -> (C, W)。在三维空间中,它把三维坐标(X, Y, Z)提升到了四维的射影空间中(X, Y, Z, W),对应的线性变换就是射影变换。齐次坐标转回三维空间坐标分两种情况,如果W为0,则(X, Y, Z)表示三维空间中的一个方向,如果W不为0,则对应的三维点坐标为(X/W, Y/W, Z/W)。齐次坐标表示有两个好处:一个是可以区分向量和点,另一个是齐次坐标下的矩阵可以表示平移变换。

  • 透视变换:一般的射影变换是一个4X4的矩阵变换,如下图1所示,它可以表示射影空间中任意的线性变换,如透视变换。
  • 仿射变换:如果把矩阵元素做一些限制到下图2,则为一个仿射变换。它可以表示三维空间中任意的线性变换。
  • 刚体变换:如果把图2的a元素的矩阵块限制为正交矩阵块r,如下图3所示,它就变成了一个刚体变换。它可以表示旋转变换和平移变换。
  • transform matrix
    多线程设置
    void SetThreadCount(Int count);
    

    Geometry++有些API使用了多线程。这个API可以设置线程个数(>=0)。默认参数为0-根据CPU核心数自动设置。


    下载

    VS2013_64bit | VS2015_64bit | VS2017_64bit


    常见问题
    请不要在release的环境下debug程序,因为release环境下面的调试信息是不准确的。
    编译有错误
  • 请仔细检查头文件,lib库,宏定义是否都配置好了,详细请参考使用简介。如果工程中链接了很多第三方库,可以新建一个简单的工程来链接BGL库,来确认BGL库是否有问题。
  • API调用出现问题
  • 检查API的返回码:-6属于没有激活开发包;-8属于函数没有定义,如果你有这个函数的授权,可以前来联系。
  • 查看log文件,看能不能得到提示
  • 一般性问题还是特例问题
  • 某个api调用出现问题时,可以多试试几个例子,看看是对所有例子都有问题,还是个例有问题。如果对所有例子都有问题,一般都是用法有问题。
  • 程序崩溃了(Crash)怎么办
  • 请仔细确认程序的崩溃点!如果有异常发生,请在软件程序里Catch住异常。异常的捕获处理属于软件部分的内容。
  • 反馈问题时模型的导出
  • 导出模型坐标时,确保导出的坐标精度是没有截断的,如std::ofstream导出时可以调用precision来设置精度。
  • 网格导出时,一般使用OBJ格式,不要使用STL,因为STL没有网格拓扑信息。不同软件系统重构STL拓扑的实现可能不一样。具体可以参考为什么不建议使用STL格式。
  • 总之,最重要的环节是问题重现。只有问题能够重现出来,才能得到有效的解决。反馈问题前,请先想想反馈的信息是否能够得到问题重现。