Basic Geometry Library

BGL (Basic Geometry Library) 是一个关于三维数据(点云,网格)处理的基础几何库。它包含了三维数据处理最基础的数据结构。用户可以很方便的使用它来开发各种几何相关的算法。


使用简介

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

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


IPointCloud

通用的点云数据表示接口类. 它包含了最基础,最常用的点云操作接口。

    // IPointCloud.h
    class BGL_EXPORT IPointCloud
    {
    public:
        IPointCloud(){}

        virtual Int GetPointCount() const = 0;

        virtual Vector3 GetPointCoord(Int pid) const = 0;
        virtual void SetPointCoord(Int pid, const Vector3& coord) = 0;
        
        virtual bool HasNormal() const = 0;
        virtual void SetHasNormal(bool has) = 0;
        virtual Vector3 GetPointNormal(Int pid) const = 0;
        virtual void SetPointNormal(Int pid, const Vector3& normal) = 0;
        
        virtual bool HasColor(void) const = 0;
        virtual void SetHasColor(bool has) = 0;
        virtual Vector3 GetPointColor(Int pid) const = 0;
        virtual void SetPointColor(Int pid, const Vector3& color) = 0;
        
        // Return inserted point id
        virtual Int InsertPoint(const Vector3& coord) = 0;
        // Return inserted point id
        virtual Int InsertPoint(const Vector3& coord, const Vector3& normal) = 0;
        
        virtual void SwapPoint(Int pointId0, Int pointId1) = 0; 
        virtual void PopbackPoints(Int popCount) = 0;
        virtual void Clear(void) = 0;

        virtual ~IPointCloud(){}
    };

用法:继承这个接口类,实现其成员函数。BGL的PointCloud是IPointCloud的一个实现,可以直接拿来用。

接口类的优点:使用接口类实现算法,用户只需要实现这个接口类,就可以调用所有相关的算法。在实际工程中,点云往往有各种不同的数据结构表示,用户不需要为每一个数据结构都去实现一遍算法。就好比用户面向OpenGL编程,显卡的硬件实现,可以是N卡,也可以是A卡。

用法说明:

  • 假设用户表示点云数据的类为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 = LaplaceSmooth(pointCloud); // 调用点云算法API来修改自己的点云数据
        res = CalculatePointCloudNormal(pointCloud);
        // 其它点云算法API的具体用法可以参考相应模块的介绍
    
  • 构造点云:通过InsertPoint不断的往点云里加点.
  •     IPointCloud* pointCloud = new PointCloud;
        for (int pid = 0; pid < pointSize; pid++)
        {
            pointCloud->InsertPoint(pointCoords.at(pid));
            /* If pointCloud has Normal information
            pointCloud->InsertPoint(pointCoords.at(pid), pointNormals.at(pid));
            */
        }
    
  • 点云顶点的存储格式是线性的,获取方便,但是删除会存在一些效率问题. IPointCloud提供了SwapPoint函数把需要删除的元素交换到尾部,然后再通过PopbackPoints删除尾部元素。下面是一个删除点云点的示例:
  •     // BglToolPointCloud.h
        // deleteIndex could have duplicate index
        // originPointIds->at(pid_AfterDelete) = pid_OriginId
        ErrorCode DeletePointCloudElements(IPointCloud* pointCloud, const std::vector< Int >& deleteIndex, std::vector< Int >* originPointIds)
        {
            if (pointCloud == NULL )
            {
                return GPP_INVALID_INPUT;
            }
            Int pointCount = pointCloud->GetPointCount();
            if (originPointIds)
            {
                originPointIds->resize(pointCount);
                for (Int pid = 0; pid < pointCount; pid++)
                {
                    originPointIds->at(pid) = pid;
                }
            }
            if (deleteIndex.empty())
            {
                return GPP_NO_ERROR;
            }
            std::vector< bool > validFlag(pointCount, 1);
            for (std::vector< Int >::const_iterator itr = deleteIndex.begin(); itr != deleteIndex.end(); ++itr)
            {
                validFlag.at(*itr) = 0;
            }
            Int validDeleteCount = 0;
            for (Int pid = 0; pid < pointCount; pid++)
            {
                if (!validFlag.at(pid))
                {
                    validDeleteCount++;
                }
            }
            Int nonNullId = -1;
            for (Int pid = pointCount - 1; pid >= 0; pid--)
            {
                if (validFlag.at(pid))
                {
                    nonNullId = pid;
                    break;
                }
            }
            if (nonNullId == -1)
            {
                pointCloud->PopbackPoints(pointCount);
                if (originPointIds)
                {
                    originPointIds->clear();
                }
                return GPP_NO_ERROR;
            }
            for (Int pid = 0; pid <= nonNullId; pid++)
            {
                if (!validFlag.at(pid))
                {
                    pointCloud->SwapPoint(pid, nonNullId);
                    if (originPointIds)
                    {
                        Int tempId = originPointIds->at(pid);
                        originPointIds->at(pid) = originPointIds->at(nonNullId);
                        originPointIds->at(nonNullId) = tempId;
                    }
                    validFlag.at(nonNullId) = 0;
                    validFlag.at(pid) = 1;
                    nonNullId--;
                    while (!validFlag.at(nonNullId))
                    {
                        nonNullId--;
                    }
                }
                if (!validFlag.at(pid))
                {
                    break;
                }
            }
            pointCloud->PopbackPoints(validDeleteCount);
            if (originPointIds)
            {
                originPointIds->erase(originPointIds->begin() + pointCount - validDeleteCount, originPointIds->end());
            }
            return GPP_NO_ERROR;
        }
    
  • Clear函数负责清除Point Coordinate, Point Normal, Point Color. 回到构造类初始化时的状态.
  • HasNormal, HasColor函数主要用意: 有时候点云创建后没有法线或者颜色信息,IPointCloud提供这些函数查询点云是否有可靠的法线或者颜色信息. SetHasNormal, SetHasColor函数可以设置点云是否有法线或者颜色信息

  • IGridPointCloud

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

        // IGridPointCloud.h
        class BGL_EXPORT IGridPointCloud : public IPointCloud
        {
        public:
            IGridPointCloud() {}
    
            // Initialise all variables
            virtual void InitGrid(Int width, Int height) = 0;
            // Making valid grid bounding box as small as possible
            virtual void RemoveOuterBlankGrids(void) = 0;
            virtual bool HasGridTopology(void) const = 0;
    
            virtual Int GetWidth(void) const = 0;
            virtual Int GetHeight(void) const = 0;
            
            virtual bool IsGridValid(Int wid, Int hid) const = 0;
            virtual void InvalidateGrid(Int wid, Int hid) = 0;
    
            virtual Int GetGridPointId(Int wid, Int hid) const = 0;;
            virtual void GetPointGridId(Int pointId, Int& wid, Int& hid) const = 0;
            
            // If grid is valid: it will set grid coord 
            // If grid is invalid: it will make grid valid, and set its coord.
            virtual void SetGridCoord(Int wid, Int hid, const Vector3& coord) = 0;
            virtual Vector3 GetGridCoord(Int wid, Int hid) const = 0;
            
            // Grid should be valid and GridPointCloud should has normal
            virtual void SetGridNormal(Int wid, Int hid, const Vector3& normal) = 0;
            virtual Vector3 GetGridNormal(Int wid, Int hid) const = 0;
    
            virtual void SetGridColor(Int wid, Int hid, const Vector3& color) = 0;
            virtual Vector3 GetGridColor(Int wid, Int hid) const = 0;
            
            virtual ~IGridPointCloud() {}
        };
    

    有序点云是一个方阵,如图所示。点云按照方阵一行一行的,从左上角到右下角排列。相比IPointCloud,IGridPointCloud多了点云的位置连接关系信息。相关的计算可以更加快速。

    Grid Point Cloud

    用法说明:

  • 构造有序点云:首先通过InitGrid初始化有序点云,设置长宽。初始化的点云格点默认为invalid。然后通过SetGridCoord往点云里加点。与IPointCloud不同的是,它不能通过InsertPoint函数来加点,因为这个函数没有格点坐标信息。
  •     IGridPointCloud* pointCloud = new PointCloud;
        pointCloud->InitGrid(width, height);
        for (Int pid = 0; pid < pointCount; pid++)
        {
            pointCloud->SetGridCoord(xidList.at(pid), yidList.at(pid), coordList.at(pid));
        }
    
  • RemoveOuterBlankGrids:移出掉四周无效的点,并重新设置点云的长宽。如下图所示,原始点云变为了红色框内缩小长宽的有序点云。
  • RemoveOuterBlankGrids
  • SetGridCoord:为格点设置坐标。也可以通过此函数来向点云里加点,因为格点默认是invalid,设置坐标以后就valid
  • SetGridNormal:为格点设置法线信息。注意,此格点必须是valid
  • GetGridPointId:获取格点对应的有效格点的索引。若格点无效,则返回-1
  • GetPointGridId:获取有效格点对应的格点坐标
  • 导入导出:可以使用gbg和gtg格式的文件存储有序点云。gtg是文本格式;gbg是二进制格式,读取速度会更快。

  • PointCloud

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

    这是PointCloud的一个实现示例:

        // PointCloud.h
        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< Int >().swap(mPoint2GridMap);
            std::vector< Int >().swap(mGrid2PointMap);
            mGrid2PointMap.resize(width * height, -1);
            mIsGrid = true;
            std::vector< Vector3 >().swap(mCoordList);
            std::vector< Vector3 >().swap(mNormalList);
            std::vector< Vector3 >().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之后,会变成无序点云

  • PointCloudInfo

    有些API有PointCloudInfo参数,它是点云缓存信息(比如点云的最近邻域创建),第二次计算的时候可以加速计算。如不需要,可设置为NULL。注意,如果点云的几何形状或者拓扑结构变化了,则当前的PointCloudInfo就失效了,必须Clear掉。

        //一般用法:
        PointCloudInfo cloudInfo;
        API0(..., &cloudInfo, ...);
        API1(..., &cloudInfo, ...);
        .......
    

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

    目前支持的文件格式:obj, asc,gbg,gtg. 用户也可以导入其它格式的点云数据, 只需要自己写一个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可以是绝对路径,也可以相对路径


    点云法向量

    点云法线的介绍可以参考点云法线,特别是如何给点云法线完美的定向。(Magic3D法线

    ErrorCode PointCloudNormal::Compute(IPointCloud* pointCloud, PointCloudInfo* pointCloudInfo, bool isDepthImage = false, Int neighborCount = 9, const Real* sampleInterval = NULL);
    

    pointCloud:点云数据

    pointCloudInfo:参考PointCloudInfo.

    isDepthImage: 如果点云是深度图数据(一般扫描仪扫描一帧的点云数据), 所有法线的z坐标会大于0(面向相机), 并且计算速度更加快

    neighborCount: 计算法线时点邻域个数. 默认值是9,如果点云均匀性不好,可以适当调大参数来保证计算结果的稳定. 如果是深度点云,可以设置为5.

    sampleInterval:点云如果严重非均匀,比如线激光扫描的点云,可以设置sampleInterval先对点云进行采样

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode PointCloudNormal::Smooth(IPointCloud* pointCloud, PointCloudInfo* pointCloudInfo, Real smoothWeight = 0.5, Int iterationCount = 1, Int neighborCount = 9, const std::vector* fixedIndex = NULL);
    

    pointCloud:点云数据

    pointCloudInfo:参考PointCloudInfo

    smoothWeight: 权重越大,Smooth程度越大. 参数范围是(0, infinity), 默认为1.0

    iterationCount: 光滑迭代次数

    neighborCount: 光滑法线时点邻域个数. 默认值是9,如果点云均匀性不好,可以适当调大参数来保证计算结果的稳定

    fixedIndex: 法线需要固定的点索引。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode PointCloudNormal::Update(IPointCloud* pointCloud, PointCloudInfo* pointCloudInfo, Int neighborCount);
    ErrorCode PointCloudNormal::UpdateByRadius(IPointCloud* pointCloud, Real neighborRadius);
    

    更新点云法线,不改变法线定向

    pointCloud:点云需要有法线信息。

    pointCloudInfo:参考PointCloudInfo

    neighborCount: 计算法线时点邻域个数. 默认值是9,如果点云均匀性不好,可以适当调大参数来保证计算结果的稳定. 如果是深度点云,可以设置为5.

    neighborRadius:计算法线时点邻域半径

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode PointCloudNormal::Orient(IPointCloud* pointCloud, PointCloudInfo* pointCloudInfo, Int neighborCount, const std::vector< Int >* fixedIndex);
    

    重新定向点云法向量

    pointCloud:点云需要有法线信息

    pointCloudInfo:参考PointCloudInfo

    neighborCount: 点云邻域个数。法线定向通过邻域往外传播扩散,一般可以设置为9

    fixedIndex: 法线需要固定的点索引。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode PointCloudNormal::CalculateGridNormal(IGridPointCloud* pointCloud);
    

    计算有序点云法线

    pointCloud:输入的有序点云

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode PointCloudNormal::SmoothGridNormal(IGridPointCloud* pointCloud, Int neighborWidth, Int iterationCount);
    

    光滑有序点云法线

    pointCloud:输入的有序点云

    neighborWidth:点云光滑邻域大小

    iterationCount:光滑迭代次数

    返回值: 如果成功则返回GPP_NO_ERROR


    点云采样

    点云采样广泛应用于三维数据处理的各个方面,它可以降低数据数据,也可以用于点云特征的计算。(Magic3D点云采样

    pointcloudsampling
    ErrorCode SamplePointCloud::UniformSample(const IPointCloud* pointCloud, Int sampleCount, std::vector< Int >& sampleIndex, Int seedId = 0, SampleQuality quality = SAMPLE_QUALITY_HIGH);
    

    均匀采样:采样结果分布均匀,如上图中所示

    pointCloud: 点云数据

    sampleCount: 采样的目标点数,点数应小于点云点的个数

    sampleIndex: 采样点的索引结果

    seedId: 采样种子点,不同的种子点得到的采样结果不一样

    quality: 采样质量,目前有SAMPLE_QUALITY_LOW和SAMPLE_QUALITY_HIGH两种类型

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SamplePointCloud::GeometrySample(const IPointCloud* pointCloud, Int sampleCount, std::vector< Int >& sampleIndex, Real uniformWeight = 0.1, Int neighborCount = 9, Int seedId = 0, SampleQuality quality = SAMPLE_QUALITY_HIGH, Real* planeRatio = NULL);
    

    几何采样:采样结果在几何特征明显的地方数量会多一些,如上图右所示

    pointCloud: 点云数据

    sampleCount: 采样的目标点数,点数应小于点云点的个数

    sampleIndex: 采样点的索引结果

    uniformWeight: 采样结果的均匀性,范围[0, 1]. 参数越大,均匀性越好.

    neighborCount: 点云邻域个数,主要用于计算几何特征. 默认参数为9,如果点云均匀性不好,可以提高此参数以获得更好的稳定性

    seedId: 采样种子点,不同的种子点得到的采样结果不一样

    quality: 采样质量,目前有SAMPLE_QUALITY_LOW和SAMPLE_QUALITY_HIGH两种类型

    planeRatio: 返回值,*planeRatio = 平面点数 / 总点数。API在采样的同时,可以通过这个参数得知点云中平面点的比例。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SamplePointCloud::GridSample(const IPointCloud* pointCloud, Real interval, std::vector< Int >& sampleIndex);
    

    格栅采样:根据点间距把空间分为一个一个的格子,每个格子采样一个点,并且使得采样的点云尽量均匀。

    pointCloud: 点云数据

    interval: 点间距,格栅边长

    sampleIndex: 采样点的索引结果

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SamplePointCloud::RandSample(Int pointCount, Int sampleCount, const std::vector< Real >* pointWeights, std::vector< Int >& sampleIndex);
    

    点云随机采样。注意随机采样并不是均匀采样。

    pointCount: 点云个数

    sampleCount: 采样的目标点数,点数应小于点云点的个数

    pointWeights: 点云采样权重,权重值越大的点,采到的几率越大

    sampleIndex: 采样点的索引结果

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SampleGrid::UniformSample(const IGridPointCloud* pointCloud, Int sampleCount, std::vector< std::pair< Int, Int > >* sampleIndex);
    

    有序点云均匀采样

    pointCloud:输入的有序点云

    sampleCount:采样的目标点数,点数应小于点云点的个数

    sampleIndex:返回采样点的索引结果

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SampleGrid::GeometrySample(const IGridPointCloud* pointCloud, Int sampleCount, Real uniformWeight, std::vector< std::pair< Int, Int > >* sampleIndex, Real* planeRatio);
    

    有序点云几何采样,采样结果在几何特征明显的地方数量会多一些

    pointCloud:输入的有序点云

    sampleCount:采样的目标点数,点数应小于点云点的个数

    uniformWeight:采样结果的均匀性,范围[0, 1]. 参数越大,均匀性越好.

    sampleIndex:返回采样点的索引结果

    planeRatio: 返回值,*planeRatio = 平面点数 / 总点数。PI在采样的同时,可以通过这个参数得知点云中平面点的比例。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SampleGrid::Simplify(const IGridPointCloud* pointCloud, Real interval, GridSimplifyType type, IGridPointCloud* simplifiedCloud);
    

    有序点云简化:根据点间距做点云简化。

    pointCloud:输入的有序点云

    interval:简化目标点间距。注意,此间距是一个参考值,简化后的点云密度在此值附近,不一定严格相等。

    type:GRID_SIMPLIFY_SAMPLE-从原始点云采样;GRID_SIMPLIFY_MERGE-简化时候会合并点云

    simplifiedCloud:简化后的点云。

    返回值: 如果成功则返回GPP_NO_ERROR


    拟合基本形
    bool FitPointCloud::FitPlane(const std::vector< Vector3 >& supportCoords, const std::vector< Vector3 >* supportNormals, Vector3& planeCenter, Vector3& planeNormal);
    

    拟合平面:若少于3个点,则必须提供点法线

    supportCoords: 点云坐标

    supportNormals: 点云法线,可以没有法线

    planeCenter: 平面中心坐标

    planeNormal: 平面法线

    返回值: 如果拟合成功则返回true


    bool FitPointCloud::FitSphere(const std::vector< Vector3 >& supportCoords, const std::vector< Vector3 >& supportNormals, Vector3& center, Real& radius);
    

    拟合球面:至少提供2个点

    supportCoords: 点云坐标

    supportNormals: 点云法线

    center: 球心坐标

    radius: 球面半径

    返回值: 如果拟合成功则返回true


    bool FitPointCloud::FitCylinder(const std::vector< Vector3 >& supportCoords, const std::vector< Vector3 >& supportNormals, const Vector3* initDir, Vector3& center, Vector3& dir, Real& radius);
    

    拟合圆柱面:至少提供2个点

    supportCoords: 点云坐标

    supportNormals: 点云法线

    initDir: 圆柱面中轴线的方向估计,可以设置为NULL

    center: 圆柱面中心坐标

    dir: 圆柱面中轴线方向

    radius: 圆柱面横截面圆半径

    返回值: 如果拟合成功则返回true


    bool FitPointCloud::FitCone(const std::vector< Vector3 >& supportCoords, const std::vector< Vector3 >& supportNormals, Vector3& apex, Vector3& dir, Real& angle);
    

    拟合圆锥面:至少提供3个点

    supportCoords: 点云坐标

    supportNormals: 点云法线

    apex: 圆锥面顶点

    dir: 圆锥面中心线方向

    angle: 圆锥面夹角

    返回值: 如果拟合成功则返回true


    点云边界点检测Magic3D点云边界点检测

    BglToolPointCloud.h

    ErrorCode DetectGridBoundary(const IGridPointCloud* pointCloud, Int boundarySize, std::vector< Int >& boundaryIds);
    ErrorCode DetectPointCloudBoundary(const IPointCloud* pointCloud, PointCloudInfo* cloudInfo, Int neighborSize, Int boundarySize, std::vector< Int >& boundaryIds);
    

    pointCloud: 输入的有序点云

    cloudInfo: 参考PointCloudInfo

    boundarySize: 边界厚度

    boundaryIds: 边界点索引序列

    返回值: 如果成功则返回GPP_NO_ERROR

    cat_point_boundary
    点云工具箱

    BglToolPointCloud.h

    ErrorCode CalculatePointCloudDensity(const IPointCloud* pointCloud, PointCloudInfo* pointCloudInfo, Int neighborCount, Real& density, const std::vector< Int >* selectIndex = NULL);
    ErrorCode CalculatePointListDensity(const IPointList* pointList, Int neighborCount, Real& density);
    ErrorCode CalculateGridDensity(const IGridPointCloud* pointCloud, Real& density);
    

    计算点云密度:计算每个点的neighborCount邻域点的平均距离。

    pointCloudInfo: 参考PointCloudInfo

    neighborCount > 0,值越大,density也越大

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode EstimateCameraDirection(const IPointCloud* depthData, const std::vector< Int >* sampleList, Vector3& cameraDir);
    

    估计深度相机方向。

    depthData: 深度点云数据,需要带法线

    sampleList: 采样的点云索引,如果输入值是NULL,则对所有点进行整体估计

    cameraDir: 深度相机方向,与点云法线相反

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode ComputePointCloudFieldGradient(const IPointCloud* pointCloud, PointCloudInfo* info, const std::vector< Real >& fields, Int neighborCount, std::vector< Vector3 >* gradients);
    

    计算点云标量场的梯度

    pointCloud: 输入点云,需要有法线信息。

    info: 参考PointCloudInfo

    fields: 点云标量场

    neighborCount: 点云邻域点个数

    gradients: fields对应的梯度场

    返回值: 如果成功则返回GPP_NO_ERROR


    ITriMesh

    通用的三角网格数据表示接口类. 它包含了最基础,最常用的三角网格操作接口。

        // ITriMesh.h
        class BGL_EXPORT ITriMesh
        {
        public:
            ITriMesh(){}
    
            virtual Int GetVertexCount(void) const = 0;
            virtual Int GetTriangleCount(void) const = 0;
    
            virtual Vector3 GetVertexCoord(Int vid) const = 0;
            virtual void SetVertexCoord(Int vid, const Vector3& coord) = 0;
            virtual Vector3 GetVertexNormal(Int vid) const = 0;
            virtual void SetVertexNormal(Int vid, const Vector3& normal) = 0;
            // Sometimes, Mesh doesn't need vertex normal information
            virtual bool HasVertexNormal(void) const = 0;
    
            // vertexIds are in a consistent order in all connected triangles
            virtual void GetTriangleVertexIds(Int fid, Int vertexIds[3]) const = 0;
            // make sure vertexIdx are in a consistent order in its connected triangles
            virtual void SetTriangleVertexIds(Int fid, Int vertexId0, Int vertexId1, Int vertexId2) = 0;
            virtual Vector3 GetTriangleNormal(Int fid) const = 0;
            virtual void SetTriangleNormal(Int fid, const Vector3& normal) = 0;
            // Sometimes, Mesh doesn't need triangular normal information
            virtual bool HasTriangleNormal(void) const = 0;
            
            // Return inserted triangle id
            virtual Int InsertTriangle(Int vertexId0, Int vertexId1, Int vertexId2) = 0;
            // Return inserted vertex id
            virtual Int InsertVertex(const Vector3& coord) = 0;
            
            // Be careful: if you swap vertex and popback them, then vertex index after the deleted vertices will be changed.
            // If you want to delete some vertices, please use api DeleteTriMeshVertex in BglToolMesh.h which is still developping.
            virtual void SwapVertex(Int vertexId0, Int vertexId1) = 0; 
            virtual void PopbackVertices(Int popCount) = 0;
            virtual void SwapTriangles(Int fid0, Int fid1) =0;
            virtual void PopbackTriangles(Int popCount) = 0;
    
            virtual void UpdateNormal(void) = 0;
            // Clear all geometry information to initial state
            virtual void Clear(void) = 0;
    
            virtual ~ITriMesh(){};
        };
    

    用法:继承这个接口类,实现其成员函数。BGL的TriMesh是ITriMesh的一个实现,可以直接拿来用。

    接口类的优点:与IPointCloud类似,使用接口类实现算法,用户只需要实现这个接口类,就可以调用所有相关的算法。在实际工程中,网格往往有各种不同的数据结构表示,用户不需要为每一个数据结构都去实现一遍算法。

    用法说明:

  • 假设用户表示三角网格数据的类为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)删除尾部元素。用户也可以直接调用BglToolMesh.h里的DeleteTriMeshVertices / DeleteTriMeshTriangles来删除顶点或者三角片。
  • 网格拓扑改变后,顶点和三角片所对应的属性如何相应的改变:这里的拓扑改变是指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可以是绝对路径,也可以相对路径


    非流形结构检测

    网格拓扑的相关介绍可以参考从STL文件到网格拓扑

    Magic3D网格流形检测
    bool ComputeMeshTopology::IsTriMeshManifold(const ITriMesh* triMesh, Int* invalidVertexId = NULL);;
    

    判断三角网格是否流形结构. 非流形结构包括: 孤立顶点, 顶点邻域包含多个连通区域, 三角面片定向不统一协调

    triMesh: 网格数据

    invalidVertexId: 检测过程中遇到的第一个非流形

    返回值: 如果是流形结构则返回true,不是则返回false


    网格洞检测
    ErrorCode ComputeMeshTopology::FindHoles(const ITriMesh* triMesh, std::vector< std::vector< Int > > *holesIds);
    

    找网格的洞

    triMesh: 网格数据

    holesIds: 返回一个二维数组,记录了hole loop vertex ids,每个一维vector< Int >记录了一条洞的边界顶点,顶点是顺序排列的

    返回值: 如果成功则返回GPP_NO_ERROR


    网格几何修整
    bool ComputeMeshGeometry::IsGeometryDegenerate(const ITriMesh* triMesh);
    

    检测网格是否含有面积退化的三角片


    ErrorCode ComputeMeshGeometry::ConsolidateDegenerateAreas(const ITriMesh* triMesh, const std::vector< bool >* vertexFixFlags, Real minArea2AvgRatio, std::vector< Vector3 >& vertexCoords);
    

    修整网格的面积退化的三角片,使其不含退化三角片

    triMesh: 网格数据

    vertexFixFlags: 网格顶点固定标记,true-顶点固定,false-顶点非固定。如果输入为NULL,则没有固定顶点。

    minArea2AvgRatio: 面积退化的判定标准:三角片面积 / 平均三角片面积 < minArea2AvgRatio,则判定为退化三角片

    vertexCoords: 修整后的网格顶点坐标

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode ComputeMeshGeometry::ConsolidateGeometry(ITriMesh* triMesh, Real minTriangleAngle, Real minEdgeLength, Real foldoverAngleTol);
    

    优化退化三角形角, 退化三角边,折叠三角形,不改变网格的拓扑结构

    triMesh: 网格数据

    minTriangleAngle: 三角面片最小角度,单位是弧度,如果小于这个角度,就会被优化使其角度增大

    minEdgeLength: 三角面片最小边长,如果小于这个值,就会被优化使其边长增长

    foldoverAngleTol: 相邻三角面片最大夹角,单位是弧度,如果大于这个值,夹角会被优化使其减小

    返回值: 如果成功则返回GPP_NO_ERROR


    网格去噪Magic3D网格去噪
    meshdenoise
    ErrorCode ComputeMeshGeometry::RemoveGeometryNoise(ITriMesh* triMesh, Real sharpAngle = 70.0 * ONE_RADIAN, Real positionWeight = 1.0);
    

    triMesh: 网格数据

    sharpAngle: 锐利边的二面角大小,大于这个角度的边在去噪过程中不会被光滑掉,参数范围是(0, 180 * ONE_RADIAN),单位是弧度

    positionWeight: 去噪的时候,顶点位置固定的权重,权重越大,去噪程度越小,参数范围是(0, infinity), 默认为1.0

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode ComputeMeshGeometry::LaplaceSmooth(ITriMesh* triMesh, bool keepBoundary = true, Real positionWeight = 1.0, const std::vector< Int >* softConstraints = NULL);
    

    triMesh: 网格数据

    keepBoundary: 是否保持边界固定

    positionWeight: 光滑网格的时候,顶点位置固定的权重,权重越大,Smooth程度越小,参数范围是(0, infinity), 默认为1.0

    softConstraints:设置顶点为位置约束,软约束。如果为NULL,则没有位置软约束

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode ComputeMeshGeometry::SmoothTriangleNormal(ITriMesh* triMesh, TriMeshInfo* meshInfo, Real sharpAngle, Real normalWeight);
    

    光滑网格面法线

    triMesh:输入网格

    meshInfo:如果没有此信息,可以设置为NULL

    sharpAngle:相邻面法线夹角大于sharpAngle,则不做光滑

    normalWeight: 法线光滑时,保持原法线的权重。数值越大,法线光滑的效果越弱

    返回值: 如果成功则返回GPP_NO_ERROR


    几何细节增强Magic3D网格几何细节增强
    MeshDetailEnhancement
    ErrorCode ComputeMeshGeometry::EnhanceDetail(ITriMesh* triMesh, Real intensity = 2.0);
    

    triMesh: 网格数据

    intensity: 网格细节增强强度,强度越大,增强越多,参数范围是(1.0, infinity), 默认为2.0

    返回值: 如果成功则返回GPP_NO_ERROR


    网格去除孤立项Magic3D网格去除孤立项
    ErrorCode ComputeMeshTopology::CalculateIsolation(ITriMesh* triMesh, std::vector< Real >* isolation);
    

    用法:计算网格每个顶点的孤立值. 孤立值越小, 孤立项的几率越大,范围是(0, 1). 比如可以认为孤立值小于0.1的点就为孤立项

    triMesh:网格数据

    isolation: 网格顶点孤立值,范围是(0, 1]

    返回值: 如果成功则返回GPP_NO_ERROR

        // 去除点云孤立项示例
        std::vector< GPP::Real > isolations;
        GPP::ErrorCode res = GPP::ComputeMeshTopology::CalculateIsolation(triMesh, &isolations);
        GPP::Real cutValue = 0.1;
        std::vector< GPP::Int > deleteIndex;
        for (GPP::Int vid = 0; vid < vertexCount; vid++)
        {
            if (isolations.at(vid) < cutValue)
            {
                deleteIndex.push_back(vid);
            }
        }
        res = DeleteTriMeshVertiecs(triMesh, deleteIndex);
    

    网格细分加密Magic3D网格细分加密
    ErrorCode SubdivideMesh::LoopSubdivideMesh(ITriMesh* triMesh, const std::vector< Real >* vertexFields = NULL, std::vector< Real >* insertedVertexFields = NULL);
    

    网格Loop细分:根据Loop细分规则加密网格,同时磨光网格几何

    triMesh: 网格数据

    vertexFields: 网格可以自带一些属性,具体格式和点云重建里的pointFields一样

    insertedVertexFields: 新增加顶点的属性

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SubdivideMesh::DensifyMesh(ITriMesh* triMesh, Int targetVertexCount, const std::vector< Real >* vertexFields = NULL, std::vector< Real >* insertedVertexFields = NULL);
    ErrorCode DensifyMesh(ITriMesh* triMesh, Int targetVertexCount, std::vector< std::vector< PointOnFace > >* insertVertexCoords);
    ErrorCode DensifyMeshByLength(ITriMesh* triMesh, Real maxEdgeLength, std::vector< std::vector< PointOnFace > >* insertVertexCoords);
    

    加密网格顶点,新增加的顶点位于输入网格的三角面片上,加密后的网格不会改变输入网格的几何

    triMesh: 网格数据

    targetVertexCount: 加密网格的目标顶点个数,应大于输入网格顶点个数

    vertexFields: 网格可以自带一些属性,具体格式和点云重建里的pointFields一样

    insertedVertexFields: 新增加顶点的属性

    insertVertexCoords: 新增加顶点在原网格的重心坐标PointOnFace。如果顶点位于网格边或者顶点上,则表示为多个PointOnFace的交。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode MutualTessellation(ITriMesh* triMesh, const std::vector< PointOnFace >* pointsOnFace, const std::vector< PointOnEdge >* pointsOnEdge, std::vector< bool >* pointValidFlags);
    

    加密网格:通过pointsOnFace,pointsOnEdge点来加密网格

    triMesh: 网格数据

    pointsOnFace,pointsOnFace:加入的新顶点。注意新顶点不能位于原始网格顶点上,pointsOnFace点应该严格位于三角面内部

    pointValidFlags: 新增加顶点是否有效的加入了新网格,可以设置为NULL

    返回值: 如果成功则返回GPP_NO_ERROR


    切割网格Magic3D平面切割网格
    ErrorCode SplitMesh::SplitByPlane(ITriMesh* triMesh, const Plane3* plane, std::vector< bool >* triangleFlags, const std::vector< Real >* vertexFields = NULL, std::vector< Real >* insertedVertexFields = NULL);
    

    用平面切割网格

    triMesh: 被切割的网格数据

    plane: 切割平面

    triangleFlags: 网格切割后,三角面片相对于平面位置的标志:1-在平面上方;0-在平面下方

    vertexFields: 原网格顶点fields

    insertedVertexFields:新插入网格顶点的field

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SplitMesh::SplitByLines(ITriMesh* triMesh, const std::vector< std::vector< Int > >& splitLines, std::vector< Int >* insertVertexIdMap = NULL);
    

    用网格顶点构成的线段切割网格

    注意:关于面点属性,此api只会修改割缝处面点的顶点索引,不会修改其它面点属性,比如面点纹理坐标。

    triMesh: 被切割的网格数据

    splitLines: 一组切割线段。splitLines.at(lineid)是一条切割线段,vector内容为线段顶点ID。

    insertVertexIdMap: 插入的顶点与原网格顶点的对应:切割网格的时候,在割缝处会插入新的顶点,这些新顶点是原网格顶点的一个复制。由于新顶点的id是顺序push_back进网格顶点列表的,其对应关系为:insertVertexIdMap.at(insertedVertexId - originVertexCount) = originVertexId。

    返回值: 如果成功则返回GPP_NO_ERROR


    网格简化
    ErrorCode ComputeMeshTopology::SimplifyByRemovingVertex(ITriMesh* triMesh, const std::vector< Int >& removingVertices, std::vector< Int >* vertexMap, const Int* targetVertexCount, const Real* sharpAngle, std::vector< bool >* vertexSharpFlags);
    

    减少网格顶点个数。方法是迭代的删除网格顶点,每次删除顶点后,局部三角化补洞。

    triMesh: 被切割的网格数据

    removingVertices: 用户指定的需要删除的网格顶点

    vertexMap: 简化后的网格顶点与原始网格顶点的对应

    targetVertexCount: 网格目标顶点数。这个参数的意义在于,有时候removingVertices里的顶点有可能会删除失败,为了精确控制简化后网格的顶点数量,可以通过这个参数来设置。如果删除removingVertices后的网格顶点数还大于targetVertexCount的话,API会接着简化顶点。

    sharpAngle:网格边的相邻面夹角大于sharpAngle,则认为是一个sharp边。

    vertexSharpFlags:sharp边上的顶点标记为true

    返回值: 如果成功则返回GPP_NO_ERROR

    Geometry++的边缩减方法的网格简化效果更加好


    带约束Delaunay三角化Magic3D三角化2D
    ErrorCode Triangulation::ConstrainedDelaunay2D(const std::vector< Vector2 >& inputPointList, const std::vector< std::vector< Int > >* polylineList, const std::vector< std::vector< Int > >* directPolylineList, std::vector< Int >& triangleList, std::vector< Int >* noUsePointList);
    

    有时候,点云包含一些线段连接约束,如下左图所示。有些约束边并不满足Denaulay性质,所以,它并不能得到整体的Delaunay三角化结果(如下中图是点云的一个Delaunay三角化结果)。我们可以放开一些Delaunay性质约束,使其尽量的接近Delaunay三角化。下右图是一个带约束的Denaulay三角化的结果。可以比较一下中图和右图的结果差异。Denaulay三角化的相关知识可以参考 带约束的Delaunay三角化 的介绍

    cdt

    inputPointList: 输入的二维点列

    polylineList: 无向边约束。如左图左下角的线段约束

    directPolylineList: 有向边约束。如左图右上角的有向线段约束。有向边约束可以表达网格洞,如右图所示

    triangleList: 三角化结果

    noUsePointList: 没有用到的点索引序列:比如inputPointList中的重复点,在三角化时只取一个点。

    返回值: 如果成功则返回GPP_NO_ERROR


    网格采样Magic3D网格采样
    ErrorCode SampleMesh::UniformSample(const ITriMesh* triMesh, Int sampleCount, Real sharpAngle, std::vector< PointOnFace >& pointsOnFace, std::vector< PointOnEdge >& pointsOnEdge, std::vector< Int >& pointsOnVertex, bool excludeBoundary);
    

    网格采样:在网格上均匀采样点。

    triMesh: 网格数据。

    sampleCount: 采样点数。

    sharpAngle: 网格边的相邻面夹角大于sharpAngle,则认为是一个sharp边。sharp边上一定会采点。参数范围是(0, 180 * ONE_RADIAN)。

    pointsOnFace: 采样点-三角形内部的点

    pointsOnEdge: 采样点-网格边上的点

    pointsOnVertex: 采样点-网格顶点上的点

    excludeBoundary: 采样时是否排除网格边界。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode SampleMesh::GeometrySample(const ITriMesh* triMesh, Int sampleCount, Real sharpAngle, Real uniformWeight, std::vector< PointOnFace >& pointsOnFace, std::vector< PointOnEdge >& pointsOnEdge, std::vector< Int >& pointsOnVertex, bool excludeBoundary);
    

    网格采样:在网格上几何采样点。

    triMesh: 网格数据。

    sampleCount: 采样点数。

    sharpAngle: 网格边的相邻面夹角大于sharpAngle,则认为是一个sharp边。sharp边上一定会采点。参数范围是(0, 180 * ONE_RADIAN)。

    uniformWeight:范围[0, 1]。数值越大,采样的点越均匀。

    pointsOnFace: 采样点-三角形内部的点

    pointsOnEdge: 采样点-网格边上的点

    pointsOnVertex: 采样点-网格顶点上的点

    excludeBoundary: 采样时是否排除网格边界。

    返回值: 如果成功则返回GPP_NO_ERROR


    网格抽壳(等距网格)Magic3D网格抽壳
    ErrorCode OffsetMesh::UniformApproximate(ITriMesh* triMesh, Real offsetValue);
    

    网格均匀抽壳:计算网格的近似等距网格,如果是开网格,会把原始网格和等距网格的边界连接起来形成一个封闭网格。

    triMesh: 网格数据

    offsetValue: 等距值。正值代表平移方向朝网格外,负值代表平移方向朝网格内。

    返回值: 如果成功则返回GPP_NO_ERROR


    网格曲率Magic3D网格曲率
    meancurvature
    ErrorCode ComputeMeanCurvature(const ITriMesh* triMesh, std::vector< Real >& curvature);
    

    网格平均曲率测量

    triMesh: 网格数据

    curvature: 网格平均曲率

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode ComputeGaussCurvature(const ITriMesh* triMesh, std::vector< Real >& curvature);
    

    网格高斯曲率测量

    triMesh: 网格数据

    curvature: 网格高斯曲率

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode ComputePrincipalCurvature(const ITriMesh* triMesh, std::vector< Real >& minCurvature, std::vector< Real >& maxCurvature, std::vector< Vector3 >& minDirs, std::vector< Vector3 >& maxDirs);
    

    网格主曲率测量

    triMesh: 网格数据

    minCurvature:最小主曲率值

    maxCurvature:最大主曲率值

    minDirs:最小主曲率方向

    maxDirs:最大主曲率方向

    返回值: 如果成功则返回GPP_NO_ERROR


    UV展开割线Magic3D网格UV展开割线-自动

    UV展开的应用里,经常需要创建一些网格割缝。好的割缝,一般有这些性质:

  • 长度很短
  • 割线光滑
  • 沿着特征边
  • 分布在视觉不明显的地方
  • 在全自动UV展开应用里,割缝首先要能把网格割成一片一片的圆盘结构
  • 下图是一些网格割线示例,第二行为UV展开结果:

    MeshCutCurve
    ErrorCode MeshCutCurve::GenerateGaussianSingularity(const ITriMesh* triMesh, TriMeshInfo* meshInfo, Int maxSingularityCount, std::vector< Int >* singularityList);
    ErrorCode MeshCutCurve::GenerateConeSingularity(const ITriMesh* triMesh, Int maxSingularityCount, std::vector< Int >* singularityList);
    

    生成网格奇异点

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode MeshCutCurve::GenerateSingularitySplitLines(const ITriMesh* triMesh, TriMeshInfo* meshInfo, const std::vector< Int >& singularityList, std::vector< std::vector< Int > >* splitLines);
    

    连接网格奇异点,生成网格割线

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode MeshCutCurve::GenerateAtlasSplitLines(const ITriMesh* triMesh, Int initChartCount, std::vector< std::vector< Int > >& splitLines);
    

    生成网格拓扑割线

    返回值: 如果成功则返回GPP_NO_ERROR


    网格曲线Magic3D网格曲线
    geodesics
    ErrorCode CurveOnMesh::ComputeApproximateGeodesics(const ITriMesh* triMesh, TriMeshInfo* meshInfo, const std::vector< Int >& sectionVertexIds, bool isSectionClose, std::vector< Int >& pathVertexIds, Real& distance, IMeshDistance* meshDistance);
    

    网格测地线的近似求解,测地线经过网格顶点,求解速度很快

    triMesh: 网格数据

    sectionVertexIds: 需要测量的线段顶点ID

    isSectionClose: 线段是否封闭

    pathVertexIds: 返回测地线路径顶点ID

    distance: 返回测地线距离

    meshDistance: 这一参数可以改变度量两个点距离的函数,默认参数为NULL,即为欧式距离EuclidDistance,其它类型的度量需要单独实现IMeshDistance接口

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode CurveOnMesh::SmoothCurveOnMesh(ITriMesh* triMesh, const std::vector< Int >& curve, Real sharpAngle, Real smoothWeight, Int iterationCount, bool optimizeTopology = false, std::vector< Int >* optimizedCurve = NULL);
    

    光滑网格顶点上的曲线:通过改变曲线所在顶点的位置,来达到smooth的效果

    triMesh: 网格数据

    curve: 曲线顶点索引

    sharpAngle: 特征边角。二面角角度大于sharpAngle的边为特征边,特征边上的曲线段不会smooth

    smoothWeight: 曲线光滑强度,范围(0, 1)

    iterationCount: 光滑次数

    optimizeTopology: 是否优化曲线,去掉ear vertex

    optimizedCurve: 如果optimizeTopology==true,则输入的curve有可能会删除一些ear vertex,optimizedCurve用于存储优化后的曲线顶点索引。

    返回值: 如果成功则返回GPP_NO_ERROR


    ErrorCode CurveOnMesh::SmoothCurveCrossMesh(const ITriMesh* triMesh, const std::vector< PointOnEdge >& curve, Real smoothWeight, Int iterationCount, std::vector< PointOnEdge >& smoothCurve);
    

    光滑PointOnEdge类型的曲线:通过移动curve在网格上的位置,来达到smooth的效果,输入网格几何不变。

    triMesh: 网格数据

    curve: PointOnEdge类型的曲线

    smoothWeight: 曲线光滑强度,范围(0, 1)

    iterationCount: 光滑次数

    smoothCurve: 光滑后的曲线。注意,smoothCurve和curve没有一一对应的关系。

    返回值: 如果成功则返回GPP_NO_ERROR


    网格工具箱

    BglToolMesh.h

    Real ComputeTriMeshArea(const ITriMesh* triMesh);
    

    计算网格面积。(Magic3D网格测量-面积

    triMesh:输入网格

    返回值: 网格面积


    Real ComputeTriMeshVolume(const ITriMesh* triMesh);
    

    计算网格体积。(Magic3D网格测量-体积

    triMesh:输入网格

    返回值: 网格体积


    Vector3 ComputeTriMeshBarycentre(const ITriMesh* triMesh);
    

    计算网格重心。

    triMesh:输入网格

    返回值: 网格重心坐标


    三维平面
    Real Plane3::SignedDistance(const Vector3& point) const;
    

    计算点到平面距离


    Vector3 Plane3::Intersection(const Vector3& startPoint, const Vector3& endPoint, Real* ratio) const;
    

    计算线段与平面的交点


    Vector3 Plane3::ProjectPoint(const Vector3& point) const;
    

    计算点到平面的投影


    Vector3 Plane3::ProjectVector(const Vector3& vec3) const;
    

    计算向量到平面的投影


    最近邻点查询

    NNQuery(ToolNNQuery.h): Nearest Neighbor Query。最近邻域查询工具:给N维点集创建KD Tree,查询点的K邻域和给定半径邻域。


    ErrorCode NNQuery::Init(const Real* refData, Int refCount, Int refDim);
    ErrorCode NNQuery::Init(const IPointList* pointList, const Matrix4x4* transform = NULL);
    

    NNQuery::Init: NNQuery初始化。

    refData: 点集坐标数组,数组长度 = refCount * refDim

    refCount: 点集点个数

    refDim: 点集维数

    pointList:也可以直接通过IPointList的点集来初始化NNQuery

    transform:点集的变换矩阵


    ErrorCode NNQuery::FindKNN(const Real* searchData, Int searchCount, Int neighborCount, Int* indexRes, Real* squareDistRes) const;
    ErrorCode NNQuery::FindKNN(const IPointList* pointList, const Matrix4x4* transform, Int neighborCount, Int* indexRes, Real* squareDistRes) const;
    

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

    searchData:查询点坐标数组,数组长度 = searchCount * refDim

    searchCount:查询点个数

    neighborCount:邻域个数,也就是K邻域里的K

    indexRes:查询结果,邻域点索引数组,长度为neighborCount * searchCount。需要先分配好内存。也可以传入NULL

    squareDistRes:查询结果,邻域点距离平方数组,长度为neighborCount。需要先分配好内存。也可以传入NULL

    pointList:也可以查询IPointList的点

    transform:点集的变换矩阵


    ErrorCode NNQuery::FindRadiusNN(const Real* searchData, Real squareRadius, std::vector< Int >* indexRes, std::vector< Real >* squareDistRes) const;
    

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

    searchData:单个查询点坐标数组

    squareRadius:查询半径平方

    indexRes:查询结果,邻域点索引数组

    squareDistRes:查询结果,邻域点距离平方数组


        // Compute point cloud density
        ErrorCode CalculatePointCloudDensity(const IPointCloud* pointCloud, Int neighborCount, Real& density)
        {
            if (pointCloud == NULL)
            {
                return GPP_INVALID_INPUT;
            }
            Int pointCount = pointList->GetPointCount();
            if (pointCount < 1 || neighborCount < 1)
            {
                return GPP_INVALID_INPUT;
            }
            neighborCount++;
            PointCloudPointList pointList(pointCloud);
            NNQuery nnQuery;
            ErrorCode res = nnQuery.Init(&pointList);
            if (res != GPP_NO_ERROR)
            {
                return res;
            }
            if (pointCount < neighborCount)
            {
                neighborCount = pointCount;
            }
            Real* distanceRes = new Real[pointCount * neighborCount];
            res = nnQuery.FindKNN(pointList, NULL, neighborCount, NULL, distanceRes);
            if (res != GPP_NO_ERROR)
            {
                GPPFREEARRAY(distanceRes);
                return res;
            }
            std::vector< Real > densityList(pointCount, 0);
            Real avgWeight = 1.0 / Real(neighborCount - 1);
            for (Int pid = 0; pid < pointCount; pid++)
            {
                Int nBase = pid * neighborCount;
                Real curDensity = 0.0;
                for (Int nid = 1; nid < neighborCount; nid++)
                {
                    curDensity += sqrt(distanceRes[nBase + nid]);
                }
                densityList.at(pid) = curDensity * avgWeight;
            }
            Int halfPointId = ceil(Real(pointCount) / 2.0) - 1;
            std::nth_element(densityList.begin(), densityList.begin() + halfPointId, densityList.end());
            density = densityList.at(halfPointId);
            GPPFREEARRAY(distanceRes);
            return GPP_NO_ERROR;
        }
    

    线性方程组(稠密)

    求解稠密矩阵的线性方程组:AX = b

    GeneralMatrix(GeneralMatrix.h):稠密矩阵

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

    LinearGeneralLUSolver(GeneralMatrix.h):线性方程组求解,LU分解法。

        GeneralMatrix A(matrixSize, matrixSize);
        std::vector< Real > b(matrixSize, 0);
        for (Int rid = 0; rid < matrixSize; rid++)
        {
            A.SetValue(rid, rid, 1.0);
            b.at(rid) = Real(rid);
        }
        LinearGeneralLUSolver solver;
        if (solver.Factorize(A) != GPP_NO_ERROR)
        {
            return false;
        }
        std::vector< Real > result;
        if (solver.Solve(A, b, &result) != GPP_NO_ERROR)
        {
            return false;
        }
    

    LeastSquareGeneralLDLSolver(GeneralMatrix.h):最小二乘求解,LDL分解法。

        // fit a sphere by points
        Int pointSize = points.size(); // pointSize >= 4
        GeneralMatrix matA(pointSize, 4);
        std::vector< Real > vecB(pointSize, 1);
        for (Int rid = 0; rid < supportNum; rid++)
        {
            Vector3 pos = points.at(rid);
            Vector3 nor = pointNormals.at(rid);
            matA.SetValue(rid, 0, nor[0]);
            matA.SetValue(rid, 1, nor[1]);
            matA.SetValue(rid, 2, nor[2]);
            matA.SetValue(rid, 3, 1.0);
            vecB.at(rid) = pos * nor;
        }
        LeastSquareGeneralLDLSolver solver;
        if (solver.Factorize(matA) != GPP_NO_ERROR)
        {
            GPPDebug << "FitSphere solver Factorize failed " << std::endl;
            return false;
        }
        std::vector< Real > result;
        if (solver.Solve(matA, vecB, &result) != GPP_NO_ERROR)
        {
            GPPDebug << "FitSphere solver Solve failed " << std::endl;
            return false;
        }
        sphereCenter = Vector3(result.at(0), result.at(1), result.at(2));
        sphereRadius = fabs(result.at(3));
    

    OrthgonalApproximation(GeneralMatrix.h):仿射变换的旋转矩阵近似求解。


    特征值求解(稠密)

    SelfAdjointEigenSolver(GeneralMatrix.h):特征值求解。

        // compute point cloud normal
        std::vector< Vector3 > deltaCoord(neighborCount)
        for (Int nid = 0; nid < neighborCount; nid++)
        {
            deltaCoord.at(nid) = neighorCoords.at(nid) - pointCoord;
        }
        GeneralMatrix3 matrix;
        for (Int rid = 0; rid < 3; rid++)
        {
            for (Int cid = rid; cid < 3; cid++)
            {
                Real v = 0;
                for (Int kk = 0; kk < neighborCount; kk++)
                {
                    v += deltaCoord.at(kk)[rid] * deltaCoord.at(kk)[cid];
                }
                matrix.SetValue(rid, cid, v);
                matrix.SetValue(cid, rid, v);
            }
        }
        SelfAdjointEigenSolver eigenSolver;
        res = eigenSolver.Compute(matrix);
        if (res != GPP_NO_ERROR)
        {
            GPPInfo << "EigenSolver Compute failed " << res << std::endl;
            return res;
        }
        eigenSolver.GetEigenVector(0, eigenVector);
        pointCloud->SetPointNormal(pid, Vector3(eigenVector.at(0), eigenVector.at(1), eigenVector.at(2)));
    

    线性方程组(稀疏)

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

    SparseMatrix(SparseMatrix.h):稀疏矩阵

    LinearSparseLUSolver(SparseMatrix.h):线性方程组求解,LU分解法。

    LinearSparseLLTSolver(SparseMatrix.h):线性方程组求解,LLT分解法。其中矩阵必须为对称正定矩阵。

        SparseMatrix matA(matrixSize, matrixSize);
        std::vector< Real > vecB(matrixSize);
        for (int eid = 0; eid < entitySize; eid++)
        {
            matA.AddTriplet(rows.at(eid), cols.at(eid), values.at(eid));
        }
        if (!matA.BuildFromTriplets())
        {
            return false;
        }
        LinearSparseLUSolver solver;
        // if matA is symmetric positive definite, user can use LinearSparseLLTSolver here.
        ErrorCode res = solver.Factorize(sparseMatrix);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        std::vector< Real > result;
        res = solver.Solve(vecB, &result);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        solver.Free();
    

    LinearSparseCGSolver(SparseMatrix.h):线性方程组求解,共轭梯度法。适合有初始值的大型稀疏矩阵求解。

        SparseMatrix matA(matrixSize, matrixSize);
        std::vector< Real > vecB(matrixSize);
        for (int eid = 0; eid < entitySize; eid++)
        {
            matA.AddTriplet(rows.at(eid), cols.at(eid), values.at(eid));
        }
        if (!matA.BuildFromTriplets())
        {
            return false;
        }
        LinearSparseCGSolver solver;
        ErrorCode res = solver.Factorize(sparseMatrix);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        solver.SetMaxIteration(maxIterationSize);
        std::vector< Real > result;
        res = solver.Solve(vecB, &result, &initValue);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        solver.Free();
    

    LeastSquareSparseLLTSolver(SparseMatrix.h):最小二乘求解,LLT分解法。

        SparseMatrix matA(rowSize, colSize);  // rowSize >= colSize
        std::vector< Real > vecB(matrixSize);
        for (int eid = 0; eid < rowSize; eid++)
        {
            matA.AddTriplet(rows.at(eid), cols.at(eid), values.at(eid));
        }
        if (!matA.BuildFromTriplets())
        {
            return false;
        }
        LeastSquareSparseLLTSolver solver;
        ErrorCode res = solver.Factorize(sparseMatrix);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        std::vector< Real > result;
        res = solver.Solve(vecB, &result);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        solver.Free();
    

    LeastSquareSparseCGSolver(SparseMatrix.h):最小二乘求解,共轭梯度法。适合有初始值的大型稀疏矩阵求解。

        SparseMatrix matA(rowSize, colSize);  // rowSize >= colSize
        std::vector< Real > vecB(matrixSize);
        for (int eid = 0; eid < rowSize; eid++)
        {
            matA.AddTriplet(rows.at(eid), cols.at(eid), values.at(eid));
        }
        if (!matA.BuildFromTriplets())
        {
            return false;
        }
        LeastSquareSparseCGSolver solver;
        ErrorCode res = solver.Factorize(sparseMatrix);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        solver.SetMaxIteration(maxIterationSize);
        std::vector< Real > result;
        res = solver.Solve(vecB, &result, &initValue);
        if (res != GPP_NO_ERROR)
        {
            return false;
        }
        solver.Free();
    

    齐次变换

    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

    Matrix4X4的一些工具函数:

  • GenerateScale: 缩放变换矩阵
  • GenerateTranslation:平移变换矩阵
  • GenerateVectorToVectorRotation:一个方向旋转到另一个方向的旋转矩阵
  • GenerateLShapeRotation:一个L形状旋转到另一个L形状的旋转矩阵
  • GenerateUnitQuaternionRotation:单位四元数对应的旋转矩阵
  • AffineInverse:仿射逆矩阵

  • 时间统计

    Profiler(Profiler.h): 获取当前时间。可通过时间差来计算某段程序执行的时间。

        Real startTime = Profiler::GetTime();
        ...........
        Real deltaTime = Profiler::GetTime() - startTime;
    
    多线程设置

    BglToolKit.h

    void SetThreadCount(Int count);
    

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


    调试
    请先看看常见问题
    这里的调试特指某个API的调试。对于一系列API的调用后出现的问题,请先分析定位出是哪个API出了问题,一般是合理的输入没有得到合理的输出。
    Geometry++ API的调试。首先查看API的返回码和Log文件,如果不能解决,则采用dump api输入的方式来反馈错误。dump数据的生成步骤是:
  • a) 在需要Dump调试信息的API前使用函数GPP::DumpOnce. 比如需要Dump GlobalRegistrate的调试信息:
  •     GPP::DumpOnce();  // DumpInfo.h
        GPP::ErrorCode res = GPP::RegistratePointCloud::GlobalRegistrate(pointCloudRef, pointCloudFrom, transform);
    
  • b) 调试信息在调用API时会输出到程序可执行文件的当前目录下。用户也可以自定义dump输出目录:
  •     std::string dumpDir = "/sdcard/";
        GPP::SetDumpDirectory(dumpDir);  // DumpInfo.h
    
    问题反馈请发送到邮箱geometryhub@qq.com,文件请压缩后发送
    问题反馈的内容:
  • 一次只反馈一个问题,多个问题请分多次反馈。
  • 问题的详细描述:请准确,定量的,用专业术语描述问题。
  • 数据:dump文件***.dump,API执行结果文件***.res。
  • Log文件:请保证是操作出错时的Log
  • API返回码
  • 结果描述:形式可以是多种多样,如导出结果文件,结果截图,操作视频等。
  • 代码片断:可以截取api调用时的代码片断,注意不是整个代码文件
  • 其它:任何可以帮助问题重现的资料
  • 注意:问题反馈的内容,主要是为了重现问题。反馈的内容越精确,问题越能得到重现。解决问题是建立在问题重现的基础之上的。
    问题反馈,请用准确,定量的专业术语来描述。定性的,或者不准确的的问题反馈,很难得到及时有效的回复。比如“我的SDK为什么不能用呢?”“某某功能为什么不能用啊?为什么出错啊?”“我的程序崩溃是怎么回事呢?”

    程序调试的相关介绍也可以参考程序调试


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