BGL (Basic Geometry Library) 是一个关于三维数据(点云,网格)处理的基础几何库。它包含了三维数据处理最基础的数据结构。用户可以很方便的使用它来开发各种几何相关的算法。
1. 头文件: 所有的头文件都在include文件夹里, 设置好头文件包含路径, 在使用api的源文件里包含BGL.h
2. 库: 设置好对应版本动态链接库的路径,在预处理里定义宏BGL_DLL_EXPORT,USECPP11,如果是Windows平台还需要定义WIN32
通用的点云数据表示接口类. 它包含了最基础,最常用的点云操作接口。
// 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卡。
用法说明:
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的具体用法可以参考相应模块的介绍
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)); */ }
// 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; }
有序点云的数据结构接口,它继承于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多了点云的位置连接关系信息。相关的计算可以更加快速。
用法说明:
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)); }
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; }
注意事项:
有些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点云采样)
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
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
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.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类似,使用接口类实现算法,用户只需要实现这个接口类,就可以调用所有相关的算法。在实际工程中,网格往往有各种不同的数据结构表示,用户不需要为每一个数据结构都去实现一遍算法。
用法说明:
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();
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()); }
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()); } }
注意事项:
HalfMesh类是网格半边结构的一个实现,用户可以直接使用它来表示网格数据. 半边结构的介绍可以参考这个网页
用法说明:
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
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
ErrorCode ComputeMeshGeometry::EnhanceDetail(ITriMesh* triMesh, Real intensity = 2.0);
triMesh: 网格数据
intensity: 网格细节增强强度,强度越大,增强越多,参数范围是(1.0, infinity), 默认为2.0
返回值: 如果成功则返回GPP_NO_ERROR
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);
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
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++的边缩减方法的网格简化效果更加好
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三角化 的介绍
inputPointList: 输入的二维点列
polylineList: 无向边约束。如左图左下角的线段约束
directPolylineList: 有向边约束。如左图右上角的有向线段约束。有向边约束可以表达网格洞,如右图所示
triangleList: 三角化结果
noUsePointList: 没有用到的点索引序列:比如inputPointList中的重复点,在三角化时只取一个点。
返回值: 如果成功则返回GPP_NO_ERROR
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
ErrorCode OffsetMesh::UniformApproximate(ITriMesh* triMesh, Real offsetValue);
网格均匀抽壳:计算网格的近似等距网格,如果是开网格,会把原始网格和等距网格的边界连接起来形成一个封闭网格。
triMesh: 网格数据
offsetValue: 等距值。正值代表平移方向朝网格外,负值代表平移方向朝网格内。
返回值: 如果成功则返回GPP_NO_ERROR
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展开的应用里,经常需要创建一些网格割缝。好的割缝,一般有这些性质:
下图是一些网格割线示例,第二行为UV展开结果:
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
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)。齐次坐标表示有两个好处:一个是可以区分向量和点,另一个是齐次坐标下的矩阵可以表示平移变换。
Matrix4X4的一些工具函数:
Profiler(Profiler.h): 获取当前时间。可通过时间差来计算某段程序执行的时间。
Real startTime = Profiler::GetTime(); ........... Real deltaTime = Profiler::GetTime() - startTime;
BglToolKit.h
void SetThreadCount(Int count);
有些API使用了多线程。这个API可以设置线程个数(>=0)。默认参数为0-根据CPU核心数自动设置。
GPP::DumpOnce(); // DumpInfo.h GPP::ErrorCode res = GPP::RegistratePointCloud::GlobalRegistrate(pointCloudRef, pointCloudFrom, transform);
std::string dumpDir = "/sdcard/"; GPP::SetDumpDirectory(dumpDir); // DumpInfo.h
程序调试的相关介绍也可以参考程序调试