You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
447 lines
15 KiB
447 lines
15 KiB
/* |
|
* Mesh.h |
|
* |
|
* Copyright (c) 2014-2015 SEACAVE |
|
* |
|
* Author(s): |
|
* |
|
* cDc <cdc.seacave@gmail.com> |
|
* |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU Affero General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU Affero General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Affero General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* |
|
* |
|
* Additional Terms: |
|
* |
|
* You are required to preserve legal notices and author attributions in |
|
* that material or in the Appropriate Legal Notices displayed by works |
|
* containing it. |
|
*/ |
|
|
|
#ifndef _MVS_MESH_H_ |
|
#define _MVS_MESH_H_ |
|
|
|
|
|
// I N C L U D E S ///////////////////////////////////////////////// |
|
|
|
#include "Platform.h" |
|
#include "PointCloud.h" |
|
#include <mutex> |
|
#include <map> |
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
namespace MVS { |
|
|
|
// a mesh represented by a list vertices and triangles (faces) |
|
class MVS_API Mesh |
|
{ |
|
public: |
|
typedef float Type; |
|
|
|
typedef TPoint3<Type> Vertex; |
|
typedef uint32_t VIndex; |
|
typedef TPoint3<VIndex> Face; |
|
typedef uint32_t FIndex; |
|
|
|
typedef SEACAVE::cList<Vertex,const Vertex&,0,8192,VIndex> VertexArr; |
|
typedef SEACAVE::cList<Face,const Face&,0,8192,FIndex> FaceArr; |
|
|
|
typedef SEACAVE::cList<VIndex,VIndex,0,8,VIndex> VertexIdxArr; |
|
typedef SEACAVE::cList<FIndex,FIndex,0,8,FIndex> FaceIdxArr; |
|
typedef SEACAVE::cList<VertexIdxArr,const VertexIdxArr&,2,8192,VIndex> VertexVerticesArr; |
|
typedef SEACAVE::cList<FaceIdxArr,const FaceIdxArr&,2,8192,VIndex> VertexFacesArr; |
|
|
|
typedef TPoint3<Type> Normal; |
|
typedef SEACAVE::cList<Normal,const Normal&,0,8192,FIndex> NormalArr; |
|
|
|
typedef TPoint2<Type> TexCoord; |
|
typedef SEACAVE::cList<TexCoord,const TexCoord&,0,8192,FIndex> TexCoordArr; |
|
|
|
typedef uint8_t TexIndex; |
|
typedef SEACAVE::cList<TexIndex,const TexIndex&,0,8192,FIndex> TexIndexArr; |
|
typedef SEACAVE::cList<Image8U3,const Image8U3&,2,4,TexIndex> Image8U3Arr; |
|
|
|
typedef TPoint3<FIndex> FaceFaces; |
|
typedef SEACAVE::cList<FaceFaces,const FaceFaces&,0,8192,FIndex> FaceFacesArr; |
|
|
|
// used to find adjacent face |
|
struct FaceCount { |
|
int count; |
|
inline FaceCount() : count(0) {} |
|
}; |
|
typedef std::unordered_map<FIndex,FaceCount> FacetCountMap; |
|
typedef FaceCount VertCount; |
|
typedef std::unordered_map<VIndex,VertCount> VertCountMap; |
|
|
|
typedef AABB3f Box; |
|
|
|
// used to render a mesh |
|
typedef TOctree<VertexArr,Vertex::Type,3> Octree; |
|
struct FacesInserter { |
|
FaceIdxArr& cameraFaces; |
|
FacesInserter(FaceIdxArr& _cameraFaces) |
|
: cameraFaces(_cameraFaces) {} |
|
inline void operator() (const Octree::IDX_TYPE* indices, Octree::SIZE_TYPE size) { |
|
cameraFaces.Join(indices, size); |
|
} |
|
static void CreateOctree(Octree& octree, const Mesh& mesh) { |
|
VertexArr centroids(mesh.faces.size()); |
|
FOREACH(idx, mesh.faces) |
|
centroids[idx] = mesh.ComputeCentroid(idx); |
|
octree.Insert(centroids, [](Octree::IDX_TYPE size, Octree::Type /*radius*/) { |
|
return size > 32; |
|
}); |
|
#if 0 && !defined(_RELEASE) |
|
Octree::DEBUGINFO_TYPE info; |
|
octree.GetDebugInfo(&info); |
|
Octree::LogDebugInfo(info); |
|
#endif |
|
octree.ResetItems(); |
|
} |
|
}; |
|
|
|
struct FaceChunk { |
|
FaceIdxArr faces; |
|
Box box; |
|
}; |
|
typedef SEACAVE::cList<FaceChunk,const FaceChunk&,2,16,uint32_t> FacesChunkArr; |
|
|
|
public: |
|
VertexArr vertices; |
|
FaceArr faces; |
|
|
|
struct ThreadSafeSet { |
|
std::shared_ptr<std::mutex> mtx = std::make_shared<std::mutex>(); |
|
std::unordered_set<FIndex> data; |
|
|
|
// 可拷贝 |
|
ThreadSafeSet() = default; |
|
ThreadSafeSet(const ThreadSafeSet& other) |
|
: mtx(std::make_shared<std::mutex>()), data(other.data) {} |
|
|
|
void Remove(FIndex idx) { |
|
// std::lock_guard<std::mutex> lock(*mtx); |
|
data.erase(idx); |
|
} |
|
|
|
bool Contains(FIndex idx) const { |
|
// std::lock_guard<std::mutex> lock(*mtx); |
|
return data.find(idx) != data.end(); |
|
} |
|
}; |
|
|
|
ThreadSafeSet invalidFaces; |
|
// std::map<uint32_t, ThreadSafeSet> invalidFacesAll; |
|
// ThreadSafeSet invalidFacesRelative; |
|
|
|
NormalArr vertexNormals; // for each vertex, the normal to the surface in that point (optional) |
|
VertexVerticesArr vertexVertices; // for each vertex, the list of adjacent vertices (optional) |
|
VertexFacesArr vertexFaces; // for each vertex, the ordered list of faces containing it (optional) |
|
BoolArr vertexBoundary; // for each vertex, stores if it is at the boundary or not (optional) |
|
|
|
NormalArr faceNormals; // for each face, the normal to it (optional) |
|
FaceFacesArr faceFaces; // for each face, the list of adjacent faces, NO_ID for border edges (optional) |
|
TexCoordArr faceTexcoords; // for each face, the texture-coordinates corresponding to its vertices, 3x num faces OR for each vertex (optional) |
|
TexIndexArr faceTexindices; // for each face, the corresponding index of the texture (optional) |
|
|
|
Image8U3Arr texturesDiffuse; // textures containing the diffuse color (optional) |
|
|
|
#ifdef _USE_CUDA |
|
static CUDA::KernelRT kernelComputeFaceNormal; |
|
#endif |
|
|
|
public: |
|
#ifdef _USE_CUDA |
|
inline Mesh() { |
|
InitKernels(CUDA::desiredDeviceID); |
|
} |
|
#endif |
|
|
|
void Release(); |
|
void ReleaseExtra(); |
|
void EmptyExtra(); |
|
void Swap(Mesh&); |
|
void Join(const Mesh&); |
|
bool IsEmpty() const { return vertices.empty(); } |
|
bool IsWatertight(); |
|
bool HasTexture() const { return HasTextureCoordinates() && !texturesDiffuse.empty(); } |
|
bool HasTextureCoordinates() const { ASSERT(faceTexcoords.empty() || faces.size()*3 == faceTexcoords.size() || vertices.size() == faceTexcoords.size()); return !faceTexcoords.empty(); } |
|
bool HasTextureCoordinatesPerVertex() const { return !faceTexcoords.empty() && vertices.size() == faceTexcoords.size(); } |
|
|
|
Box GetAABB() const; |
|
Box GetAABB(const Box& bound) const; |
|
Vertex GetCenter() const; |
|
|
|
void ListIncidenteVertices(); |
|
void ListIncidenteFaces(); |
|
void ListIncidenteFaceFaces(); |
|
void ListBoundaryVertices(); |
|
void ComputeNormalFaces(); |
|
void ComputeNormalVertices(); |
|
|
|
void SmoothNormalFaces(float fMaxGradient=25.f, float fOriginalWeight=0.5f, unsigned nIterations=3); |
|
|
|
void GetEdgeFaces(VIndex, VIndex, FaceIdxArr&) const; |
|
void GetFaceFaces(FIndex, FaceIdxArr&) const; |
|
void GetEdgeVertices(FIndex, FIndex, uint32_t vs0[2], uint32_t vs1[2]) const; |
|
bool GetEdgeOrientation(FIndex, VIndex, VIndex) const; |
|
FIndex GetEdgeAdjacentFace(FIndex, VIndex, VIndex) const; |
|
void GetAdjVertices(VIndex, VertexIdxArr&) const; |
|
void GetAdjVertexFaces(VIndex, VIndex, FaceIdxArr&) const; |
|
|
|
unsigned FixNonManifold(float magDisplacementDuplicateVertices = 0.01f, VertexIdxArr* duplicatedVertices = NULL); |
|
void Clean(float fDecimate=0.7f, float fSpurious=10.f, bool bRemoveSpikes=true, unsigned nCloseHoles=30, unsigned nSmoothMesh=2, float fEdgeLength=0, bool bLastClean=true); |
|
|
|
void EnsureEdgeSize(float minEdge=-0.5f, float maxEdge=-4.f, float collapseRatio=0.2, float degenerate_angle_deg=150, int mode=1, int max_iters=50); |
|
|
|
typedef cList<uint16_t,uint16_t,0,16,FIndex> AreaArr; |
|
void Subdivide(const AreaArr& maxAreas, uint32_t maxArea); |
|
void Decimate(VertexIdxArr& verticesRemove); |
|
void CloseHole(VertexIdxArr& vertsLoop); |
|
void CloseHoleQuality(VertexIdxArr& vertsLoop); |
|
void RemoveFacesOutside(const OBB3f&); |
|
void RemoveFaces(FaceIdxArr& facesRemove, bool bUpdateLists=false); |
|
void RemoveVertices(VertexIdxArr& vertexRemove, bool bUpdateLists=false); |
|
VIndex RemoveUnreferencedVertices(bool bUpdateLists=false); |
|
std::vector<Mesh> SplitMeshPerTextureBlob() const; |
|
void ConvertTexturePerVertex(Mesh&) const; |
|
|
|
TexIndex GetFaceTextureIndex(FIndex idxF) const { return faceTexindices.empty() ? 0 : faceTexindices[idxF]; } |
|
void FaceTexcoordsNormalize(TexCoordArr& newFaceTexcoords, bool flipY=true) const; |
|
void FaceTexcoordsUnnormalize(TexCoordArr& newFaceTexcoords, bool flipY=true) const; |
|
|
|
inline Normal FaceNormal(const Face& f) const { |
|
return ComputeTriangleNormal(vertices[f[0]], vertices[f[1]], vertices[f[2]]); |
|
} |
|
inline Normal VertexNormal(VIndex idxV) const { |
|
ASSERT(vertices.GetSize() == vertexFaces.GetSize()); |
|
const FaceIdxArr& vf = vertexFaces[idxV]; |
|
ASSERT(!vf.IsEmpty()); |
|
Normal n(Normal::ZERO); |
|
FOREACHPTR(pIdxF, vf) |
|
n += normalized(FaceNormal(faces[*pIdxF])); |
|
return n; |
|
} |
|
|
|
Planef EstimateGroundPlane(const ImageArr& images, float sampleMesh=0, float planeThreshold=0, const String& fileExportPlane="") const; |
|
|
|
Vertex ComputeCentroid(FIndex) const; |
|
Type ComputeArea(FIndex) const; |
|
REAL ComputeArea() const; |
|
REAL ComputeVolume() const; |
|
|
|
void SamplePoints(unsigned numberOfPoints, PointCloud&) const; |
|
void SamplePoints(REAL samplingDensity, PointCloud&) const; |
|
void SamplePoints(REAL samplingDensity, unsigned mumPointsTheoretic, PointCloud&) const; |
|
|
|
void Project(const Camera& camera, DepthMap& depthMap) const; |
|
void Project(const Camera& camera, DepthMap& depthMap, Image8U3& image) const; |
|
void Project(const Camera& camera, DepthMap& depthMap, NormalMap& normalMap) const; |
|
void ProjectOrtho(const Camera& camera, DepthMap& depthMap) const; |
|
void ProjectOrtho(const Camera& camera, DepthMap& depthMap, Image8U3& image) const; |
|
void ProjectOrthoTopDown(unsigned resolution, Image8U3& image, Image8U& mask, Point3& center) const; |
|
|
|
bool Split(FacesChunkArr&, float maxArea); |
|
Mesh SubMesh(const FaceIdxArr& faces) const; |
|
|
|
bool TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices={}, unsigned borderSize=3, unsigned textureSize=4096); |
|
|
|
// file IO |
|
bool Load(const String& fileName); |
|
bool Save(const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true) const; |
|
bool Save(const FacesChunkArr&, const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true) const; |
|
static bool Save(const VertexArr& vertices, const String& fileName, bool bBinary=true); |
|
|
|
static inline uint32_t FindVertex(const Face& f, VIndex v) { for (uint32_t i=0; i<3; ++i) if (f[i] == v) return i; return NO_ID; } |
|
static inline VIndex GetVertex(const Face& f, VIndex v) { const uint32_t idx(FindVertex(f, v)); ASSERT(idx != NO_ID); return f[idx]; } |
|
static inline VIndex& GetVertex(Face& f, VIndex v) { const uint32_t idx(FindVertex(f, v)); ASSERT(idx != NO_ID); return f[idx]; } |
|
|
|
protected: |
|
bool LoadPLY(const String& fileName); |
|
bool LoadOBJ(const String& fileName); |
|
bool LoadGLTF(const String& fileName, bool bBinary=true); |
|
|
|
bool SavePLY(const String& fileName, const cList<String>& comments=cList<String>(), bool bBinary=true, bool bTexLossless=true) const; |
|
bool SaveOBJ(const String& fileName) const; |
|
bool SaveGLTF(const String& fileName, bool bBinary=true) const; |
|
|
|
#ifdef _USE_CUDA |
|
static bool InitKernels(int device=-1); |
|
#endif |
|
|
|
#ifdef _USE_BOOST |
|
// implement BOOST serialization |
|
friend class boost::serialization::access; |
|
template <class Archive> |
|
void serialize(Archive& ar, const unsigned int /*version*/) { |
|
ar & vertices; |
|
ar & faces; |
|
ar & vertexNormals; |
|
ar & vertexVertices; |
|
ar & vertexFaces; |
|
ar & vertexBoundary; |
|
ar & faceNormals; |
|
ar & faceTexcoords; |
|
ar & faceTexindices; |
|
ar & texturesDiffuse; |
|
} |
|
#endif |
|
}; |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// used to render a 3D triangle |
|
template <typename DERIVED> |
|
struct TRasterMeshBase { |
|
typedef DERIVED Rasterizer; |
|
|
|
struct Triangle { |
|
Point3 ptc[3]; |
|
Point2f pti[3]; |
|
}; |
|
|
|
const Camera& camera; |
|
|
|
DepthMap& depthMap; |
|
|
|
TRasterMeshBase(const Camera& _camera, DepthMap& _depthMap) |
|
: camera(_camera), depthMap(_depthMap) {} |
|
|
|
inline void Clear() { |
|
depthMap.memset(0); |
|
} |
|
inline cv::Size Size() const { |
|
return depthMap.size(); |
|
} |
|
|
|
inline bool ProjectVertex(const Point3f& pt, int v, Triangle& t) { |
|
return (t.ptc[v] = camera.TransformPointW2C(Cast<REAL>(pt))).z > 0 && |
|
depthMap.isInsideWithBorder<float,3>(t.pti[v] = camera.TransformPointC2I(t.ptc[v])); |
|
} |
|
|
|
inline Point3f PerspectiveCorrectBarycentricCoordinates(const Triangle& t, const Point3f& bary) { |
|
return SEACAVE::PerspectiveCorrectBarycentricCoordinates(bary, (float)t.ptc[0].z, (float)t.ptc[1].z, (float)t.ptc[2].z); |
|
} |
|
inline float ComputeDepth(const Triangle& t, const Point3f& pbary) { |
|
return pbary[0]*(float)t.ptc[0].z + pbary[1]*(float)t.ptc[1].z + pbary[2]*(float)t.ptc[2].z; |
|
} |
|
void Raster(const ImageRef& pt, const Triangle& t, const Point3f& bary) { |
|
const Point3f pbary(PerspectiveCorrectBarycentricCoordinates(t, bary)); |
|
const Depth z(ComputeDepth(t, pbary)); |
|
ASSERT(z > Depth(0)); |
|
Depth& depth = depthMap(pt); |
|
if (depth == 0 || depth > z) |
|
depth = z; |
|
} |
|
struct TriangleRasterizer { |
|
Triangle& triangle; |
|
Rasterizer& rasterizer; |
|
TriangleRasterizer(Triangle& t, Rasterizer& r) : triangle(t), rasterizer(r) {} |
|
inline cv::Size Size() const { |
|
return rasterizer.Size(); |
|
} |
|
inline void operator()(const ImageRef& pt, const Point3f& bary) const { |
|
rasterizer.Raster(pt, triangle, bary); |
|
} |
|
}; |
|
inline TriangleRasterizer CreateTriangleRasterizer(Triangle& triangle) { |
|
return TriangleRasterizer(triangle, *static_cast<DERIVED*>(this)); |
|
} |
|
}; |
|
|
|
// used to render a mesh |
|
template <typename DERIVED> |
|
struct TRasterMesh : TRasterMeshBase<DERIVED> { |
|
typedef TRasterMeshBase<DERIVED> Base; |
|
using typename Base::Triangle; |
|
|
|
using Base::camera; |
|
using Base::depthMap; |
|
|
|
const Mesh::VertexArr& vertices; |
|
|
|
TRasterMesh(const Mesh::VertexArr& _vertices, const Camera& _camera, DepthMap& _depthMap) |
|
: Base(_camera, _depthMap), vertices(_vertices) {} |
|
|
|
template <typename TriangleRasterizer> |
|
void Project(const Mesh::Face& facet, TriangleRasterizer& tr) { |
|
// project face vertices to image plane |
|
for (int v=0; v<3; ++v) { |
|
// skip face if not completely inside |
|
if (!static_cast<DERIVED*>(this)->ProjectVertex(vertices[facet[v]], v, tr.triangle)) |
|
return; |
|
} |
|
// draw triangle |
|
Image8U3::RasterizeTriangleBary(tr.triangle.pti[0], tr.triangle.pti[1], tr.triangle.pti[2], tr); |
|
} |
|
void Project(const Mesh::Face& facet) { |
|
Triangle triangle; |
|
Project(facet, this->CreateTriangleRasterizer(triangle)); |
|
} |
|
}; |
|
|
|
bool TestMeshProjectionMT(const Mesh& mesh, const Image& image); |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
struct IntersectRayMesh { |
|
typedef Mesh::Octree Octree; |
|
typedef typename Octree::IDX_TYPE IDX; |
|
|
|
const Mesh& mesh; |
|
const Ray3& ray; |
|
IndexDist pick; |
|
|
|
IntersectRayMesh(const Octree& octree, const Ray3& _ray, const Mesh& _mesh) |
|
: mesh(_mesh), ray(_ray) |
|
{ |
|
octree.Collect(*this, *this); |
|
} |
|
|
|
inline bool Intersects(const typename Octree::POINT_TYPE& center, typename Octree::Type radius) const { |
|
return ray.Intersects(AABB3f(center, radius)); |
|
} |
|
|
|
void operator() (const IDX* idices, IDX size) { |
|
// store all intersected faces only once |
|
typedef std::unordered_set<Mesh::FIndex> FaceSet; |
|
FaceSet set; |
|
FOREACHRAWPTR(pIdx, idices, size) { |
|
const Mesh::VIndex idxVertex((Mesh::VIndex)*pIdx); |
|
const Mesh::FaceIdxArr& faces = mesh.vertexFaces[idxVertex]; |
|
set.insert(faces.begin(), faces.end()); |
|
} |
|
// test face intersection and keep the closest |
|
for (Mesh::FIndex idxFace : set) { |
|
const Mesh::Face& face = mesh.faces[idxFace]; |
|
REAL dist; |
|
if (ray.Intersects<true>(Triangle3(Cast<REAL>(mesh.vertices[face[0]]), Cast<REAL>(mesh.vertices[face[1]]), Cast<REAL>(mesh.vertices[face[2]])), &dist)) { |
|
ASSERT(dist >= 0); |
|
if (pick.dist > dist) { |
|
pick.dist = dist; |
|
pick.idx = idxFace; |
|
} |
|
} |
|
} |
|
} |
|
}; |
|
/*----------------------------------------------------------------*/ |
|
|
|
} // namespace MVS |
|
|
|
#endif // _MVS_MESH_H_
|
|
|