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, ...); .......
通用的三角网格数据表示接口类. 它包含了最基础,最常用的三角网格操作接口。
// 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
bool ComputeMeshTopology::IsTriMeshManifold(const ITriMesh* triMesh, Int* invalidVertexId = NULL);;
判断三角网格是否流形结构. 非流形结构包括: 孤立顶点, 顶点邻域包含多个连通区域, 三角面片定向不统一协调
triMesh: 网格数据
invalidVertexId: 检测过程中遇到的第一个非流形
返回值: 如果是流形结构则返回true,不是则返回false
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