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.
4617 lines
156 KiB
4617 lines
156 KiB
/* |
|
* Mesh.cpp |
|
* |
|
* 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. |
|
*/ |
|
|
|
#include "Common.h" |
|
#include "Mesh.h" |
|
// fix non-manifold vertices |
|
#include <boost/graph/adjacency_list.hpp> |
|
#include <boost/graph/filtered_graph.hpp> |
|
#include <boost/graph/connected_components.hpp> |
|
#ifdef _MSC_VER |
|
#pragma warning(push) |
|
#pragma warning(disable: 4244 4267 4305) |
|
#ifdef _SUPPORT_CPP17 |
|
namespace std { |
|
template <typename ArgumentType, typename ResultType> |
|
struct unary_function { |
|
}; |
|
} // namespace std |
|
#endif // _SUPPORT_CPP17 |
|
#endif // _MSC_VER |
|
// VCG: mesh reconstruction post-processing |
|
#define _SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS |
|
#include <vcg/complex/complex.h> |
|
#include <vcg/complex/algorithms/create/platonic.h> |
|
#include <vcg/complex/algorithms/stat.h> |
|
#include <vcg/complex/algorithms/clean.h> |
|
#include <vcg/complex/algorithms/smooth.h> |
|
#include <vcg/complex/algorithms/hole.h> |
|
#include <vcg/complex/algorithms/polygon_support.h> |
|
#include <vcg/complex/algorithms/isotropic_remeshing.h> |
|
// VCG: mesh simplification |
|
#include <vcg/complex/algorithms/update/position.h> |
|
#include <vcg/complex/algorithms/update/bounding.h> |
|
#include <vcg/complex/algorithms/update/selection.h> |
|
#include <vcg/complex/algorithms/local_optimization.h> |
|
#include <vcg/complex/algorithms/local_optimization/tri_edge_collapse_quadric.h> |
|
#undef Split |
|
#ifdef _MSC_VER |
|
# pragma warning(pop) |
|
#endif |
|
// GLTF: mesh import/export |
|
#define JSON_NOEXCEPTION |
|
#define TINYGLTF_NOEXCEPTION |
|
#define TINYGLTF_NO_STB_IMAGE |
|
#define TINYGLTF_NO_STB_IMAGE_WRITE |
|
#define TINYGLTF_NO_INCLUDE_JSON |
|
#define TINYGLTF_NO_INCLUDE_STB_IMAGE |
|
#define TINYGLTF_NO_INCLUDE_STB_IMAGE_WRITE |
|
#define TINYGLTF_IMPLEMENTATION |
|
#include "../IO/json.hpp" |
|
#include "../IO/tiny_gltf.h" |
|
|
|
using namespace MVS; |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
// uncomment to enable multi-threading based on OpenMP |
|
#ifdef _USE_OPENMP |
|
#define MESH_USE_OPENMP |
|
#endif |
|
|
|
// select fast ray-face intersection search method |
|
#define USE_MESH_BF 0 // brute-force |
|
#define USE_MESH_OCTREE 1 // octree (misses some triangles) |
|
#define USE_MESH_BVH 2 // BVH (misses some triangles) |
|
#define USE_MESH_INT USE_MESH_BVH |
|
|
|
#if USE_MESH_INT == USE_MESH_BVH |
|
#include <unsupported/Eigen/BVH> |
|
#endif |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
// free all memory |
|
void Mesh::Release() |
|
{ |
|
vertices.Release(); |
|
faces.Release(); |
|
ReleaseExtra(); |
|
} // Release |
|
void Mesh::ReleaseExtra() |
|
{ |
|
vertexNormals.Release(); |
|
vertexVertices.Release(); |
|
vertexFaces.Release(); |
|
vertexBoundary.Release(); |
|
faceNormals.Release(); |
|
faceFaces.Release(); |
|
faceTexcoords.Release(); |
|
texturesDiffuse.Release(); |
|
} // ReleaseExtra |
|
void Mesh::EmptyExtra() |
|
{ |
|
vertexNormals.Empty(); |
|
vertexVertices.Empty(); |
|
vertexFaces.Empty(); |
|
vertexBoundary.Empty(); |
|
faceNormals.Empty(); |
|
faceFaces.Empty(); |
|
faceTexcoords.Empty(); |
|
texturesDiffuse.Empty(); |
|
} // EmptyExtra |
|
void Mesh::Swap(Mesh& rhs) |
|
{ |
|
vertices.Swap(rhs.vertices); |
|
faces.Swap(rhs.faces); |
|
vertexNormals.Swap(rhs.vertexNormals); |
|
vertexVertices.Swap(rhs.vertexVertices); |
|
vertexFaces.Swap(rhs.vertexFaces); |
|
vertexBoundary.Swap(rhs.vertexBoundary); |
|
faceNormals.Swap(rhs.faceNormals); |
|
faceFaces.Swap(rhs.faceFaces); |
|
faceTexcoords.Swap(rhs.faceTexcoords); |
|
faceTexindices.Swap(rhs.faceTexindices); |
|
std::swap(texturesDiffuse, rhs.texturesDiffuse); |
|
} // Swap |
|
// combine this mesh with the given mesh, without removing duplicate vertices |
|
void Mesh::Join(const Mesh& mesh) |
|
{ |
|
ASSERT(!HasTexture() && !mesh.HasTexture()); |
|
vertexVertices.Release(); |
|
vertexFaces.Release(); |
|
vertexBoundary.Release(); |
|
faceFaces.Release(); |
|
if (IsEmpty()) { |
|
*this = mesh; |
|
return; |
|
} |
|
const VIndex offsetV(vertices.size()); |
|
vertices.Join(mesh.vertices); |
|
vertexNormals.Join(mesh.vertexNormals); |
|
faces.ReserveExtra(mesh.faces.size()); |
|
for (const Face& face: mesh.faces) |
|
faces.emplace_back(face.x+offsetV, face.y+offsetV, face.z+offsetV); |
|
faceNormals.Join(mesh.faceNormals); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
bool Mesh::IsWatertight() |
|
{ |
|
if (vertexBoundary.empty()) { |
|
if (vertexFaces.empty()) |
|
ListIncidenteFaces(); |
|
ListBoundaryVertices(); |
|
} |
|
for (const bool b : vertexBoundary) |
|
if (b) |
|
return false; |
|
return true; |
|
} |
|
|
|
// compute the axis-aligned bounding-box of the mesh |
|
Mesh::Box Mesh::GetAABB() const |
|
{ |
|
Box box(true); |
|
for (const Vertex& X: vertices) |
|
box.InsertFull(X); |
|
return box; |
|
} |
|
// same, but only for vertices inside the given AABB |
|
Mesh::Box Mesh::GetAABB(const Box& bound) const |
|
{ |
|
Box box(true); |
|
for (const Vertex& X: vertices) |
|
if (bound.Intersects(X)) |
|
box.InsertFull(X); |
|
return box; |
|
} |
|
|
|
// compute the center of the point-cloud as the median |
|
Mesh::Vertex Mesh::GetCenter() const |
|
{ |
|
const VIndex step(5); |
|
const VIndex numPoints(vertices.size()/step); |
|
if (numPoints == 0) |
|
return Vertex::INF; |
|
typedef CLISTDEF0IDX(Vertex::Type,VIndex) Scalars; |
|
Scalars x(numPoints), y(numPoints), z(numPoints); |
|
for (VIndex i=0; i<numPoints; ++i) { |
|
const Vertex& X = vertices[i*step]; |
|
x[i] = X.x; |
|
y[i] = X.y; |
|
z[i] = X.z; |
|
} |
|
return Vertex(x.GetMedian(), y.GetMedian(), z.GetMedian()); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// extract array of vertices incident to each vertex |
|
void Mesh::ListIncidenteVertices() |
|
{ |
|
vertexVertices.clear(); |
|
vertexVertices.resize(vertices.size()); |
|
FOREACH(i, faces) { |
|
const Face& face = faces[i]; |
|
for (int v=0; v<3; ++v) { |
|
VertexIdxArr& verts(vertexVertices[face[v]]); |
|
for (int i=1; i<3; ++i) { |
|
const VIndex idxVert(face[(v+i)%3]); |
|
if (verts.Find(idxVert) == VertexIdxArr::NO_INDEX) |
|
verts.emplace_back(idxVert); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// extract the (ordered) array of triangles incident to each vertex |
|
void Mesh::ListIncidenteFaces() |
|
{ |
|
vertexFaces.clear(); |
|
vertexFaces.resize(vertices.size()); |
|
FOREACH(i, faces) { |
|
const Face& face = faces[i]; |
|
for (int v=0; v<3; ++v) { |
|
ASSERT(vertexFaces[face[v]].Find(i) == FaceIdxArr::NO_INDEX); |
|
vertexFaces[face[v]].emplace_back(i); |
|
} |
|
} |
|
} |
|
|
|
// extract array face adjacencies for each face in the mesh (3 * number of faces); |
|
// each triple describes the adjacent face triangles for a given face |
|
// in the following edge order: v1v2, v2v3, v3v1; |
|
// NO_ID indicates there is no adjacent face on that edge |
|
void Mesh::ListIncidenteFaceFaces() |
|
{ |
|
ASSERT(vertexFaces.size() == vertices.size()); |
|
struct inserter_data_t { |
|
const FIndex idxF; |
|
FaceFaces& faces; |
|
int idx; |
|
inline inserter_data_t(FIndex _idxF, FaceFaces& _faces) : idxF(_idxF), faces(_faces), idx(0) {} |
|
inline void operator=(FIndex f) { faces[idx++] = f; } |
|
}; |
|
struct face_back_inserter_t { |
|
inserter_data_t* data; |
|
inline face_back_inserter_t(inserter_data_t& _data) : data(&_data) {} |
|
inline face_back_inserter_t& operator*() { return *this; } |
|
inline face_back_inserter_t& operator++() { return *this; } |
|
inline void operator=(FIndex f) { if (f != data->idxF) *data = f; } |
|
}; |
|
faceFaces.resize(faces.size()); |
|
FOREACH(f, faces) { |
|
const Face& face = faces[f]; |
|
const FaceIdxArr* const pFaces[] = {&vertexFaces[face[0]], &vertexFaces[face[1]], &vertexFaces[face[2]]}; |
|
inserter_data_t inserterData(f, faceFaces[f]); |
|
face_back_inserter_t faceBackInserter(inserterData); |
|
for (int v=0; v<3; ++v) { |
|
const FaceIdxArr& facesI = *pFaces[v]; |
|
const FaceIdxArr& facesJ = *pFaces[(v+1)%3]; |
|
std::set_intersection( |
|
facesI.begin(), facesI.end(), |
|
facesJ.begin(), facesJ.end(), |
|
faceBackInserter); |
|
if (inserterData.idx == v) |
|
inserterData = NO_ID; |
|
} |
|
} |
|
} |
|
|
|
// check each vertex if it is at the boundary or not |
|
// (make sure you called ListIncidenteFaces() before) |
|
void Mesh::ListBoundaryVertices() |
|
{ |
|
vertexBoundary.clear(); |
|
vertexBoundary.resize(vertices.size()); |
|
vertexBoundary.Memset(0); |
|
VertCountMap mapVerts; mapVerts.reserve(12*2); |
|
FOREACH(idxV, vertices) { |
|
const FaceIdxArr& vf = vertexFaces[idxV]; |
|
// count how many times vertices in the first triangle ring are seen; |
|
// usually they are seen two times each as the vertex in not at the boundary |
|
// so there are two triangles (on the ring) containing same vertex |
|
ASSERT(mapVerts.empty()); |
|
FOREACHPTR(pFaceIdx, vf) { |
|
const Face& face = faces[*pFaceIdx]; |
|
for (int i=0; i<3; ++i) { |
|
const VIndex idx(face[i]); |
|
if (idx != idxV) |
|
++mapVerts[idx].count; |
|
} |
|
} |
|
for (const auto& vc: mapVerts) { |
|
ASSERT(vc.second.count == 1 || vc.second.count == 2); |
|
if (vc.second.count != 2) { |
|
vertexBoundary[idxV] = true; |
|
break; |
|
} |
|
} |
|
mapVerts.clear(); |
|
} |
|
} |
|
|
|
|
|
// compute normal for all faces |
|
void Mesh::ComputeNormalFaces() |
|
{ |
|
faceNormals.resize(faces.size()); |
|
#ifndef _USE_CUDA |
|
FOREACH(idxFace, faces) |
|
faceNormals[idxFace] = normalized(FaceNormal(faces[idxFace])); |
|
#else |
|
if (kernelComputeFaceNormal.IsValid()) { |
|
reportCudaError(kernelComputeFaceNormal((int)faces.size(), |
|
vertices, |
|
faces, |
|
CUDA::KernelRT::OutputParam(faceNormals.GetDataSize()), |
|
faces.size() |
|
)); |
|
reportCudaError(kernelComputeFaceNormal.GetResult(0, |
|
faceNormals |
|
)); |
|
kernelComputeFaceNormal.Reset(); |
|
} else { |
|
FOREACH(idxFace, faces) |
|
faceNormals[idxFace] = normalized(FaceNormal(faces[idxFace])); |
|
} |
|
#endif |
|
} |
|
|
|
// compute normal for all vertices |
|
#if 1 |
|
// computes the vertex normal as the area weighted face normals average |
|
void Mesh::ComputeNormalVertices() |
|
{ |
|
vertexNormals.resize(vertices.size()); |
|
vertexNormals.Memset(0); |
|
for (const Face& face: faces) { |
|
const Vertex& v0 = vertices[face[0]]; |
|
const Vertex& v1 = vertices[face[1]]; |
|
const Vertex& v2 = vertices[face[2]]; |
|
const Normal t((v1 - v0).cross(v2 - v0)); |
|
vertexNormals[face[0]] += t; |
|
vertexNormals[face[1]] += t; |
|
vertexNormals[face[2]] += t; |
|
} |
|
for (Normal& vertexNormal: vertexNormals) |
|
normalize(vertexNormal); |
|
} |
|
#else |
|
// computes the vertex normal as an angle weighted average |
|
// (the vertex first ring of faces and the face normals are used) |
|
// |
|
// The normal of a vertex v computed as a weighted sum f the incident face normals. |
|
// The weight is simply the angle of the involved wedge. Described in: |
|
// G. Thurmer, C. A. Wuthrich "Computing vertex normals from polygonal facets", Journal of Graphics Tools, 1998 |
|
void Mesh::ComputeNormalVertices() |
|
{ |
|
ASSERT(!faceNormals.empty()); |
|
vertexNormals.resize(vertices.size()); |
|
vertexNormals.Memset(0); |
|
FOREACH(idxFace, faces) { |
|
const Face& face = faces[idxFace]; |
|
const Normal& t = faceNormals[idxFace]; |
|
const Vertex& v0 = vertices[face[0]]; |
|
const Vertex& v1 = vertices[face[1]]; |
|
const Vertex& v2 = vertices[face[2]]; |
|
const Normal e0(normalized(v1-v0)); |
|
const Normal e1(normalized(v2-v1)); |
|
const Normal e2(normalized(v0-v2)); |
|
vertexNormals[face[0]] += t*ACOS(-ComputeAngleN(e0.ptr(), e2.ptr())); |
|
vertexNormals[face[1]] += t*ACOS(-ComputeAngleN(e0.ptr(), e1.ptr())); |
|
vertexNormals[face[2]] += t*ACOS(-ComputeAngleN(e1.ptr(), e2.ptr())); |
|
} |
|
for (Normal& vertexNormal: vertexNormals) |
|
normalize(vertexNormal); |
|
} |
|
#endif |
|
|
|
// Smoothen the normals for each face |
|
// - fMaxGradient: maximum angle (in degrees) difference between neighbor normals that is |
|
// allowed to take into consideration; higher angles are ignored |
|
// - fOriginalWeight: weight (0..1] to use for current normal value when averaging with neighbor normals |
|
// - nIterations: number of times to repeat the smoothening process |
|
void Mesh::SmoothNormalFaces(float fMaxGradient, float fOriginalWeight, unsigned nIterations) { |
|
if (faceNormals.size() != faces.size()) |
|
ComputeNormalFaces(); |
|
if (vertexFaces.size() != vertices.size()) |
|
ListIncidenteFaces(); |
|
if (faceFaces.size() != faces.size()) |
|
ListIncidenteFaceFaces(); |
|
const float cosMaxGradient = COS(FD2R(fMaxGradient)); |
|
for (unsigned rep = 0; rep < nIterations; ++rep) { |
|
NormalArr newFaceNormals(faceNormals.size()); |
|
FOREACH(idxFace, faces) { |
|
const Normal& originalNormal = faceNormals[idxFace]; |
|
Normal sumNeighborNormals = Normal::ZERO; |
|
for (int i = 0; i < 3; ++i) { |
|
const FIndex fIdx = faceFaces[idxFace][i]; |
|
if (fIdx == NO_ID) |
|
continue; |
|
const Normal& neighborNormal = faceNormals[fIdx]; |
|
if (ComputeAngleN(originalNormal.ptr(), neighborNormal.ptr()) >= cosMaxGradient) |
|
sumNeighborNormals += neighborNormal; |
|
} |
|
const Normal avgNeighborsNormal = normalized(sumNeighborNormals); |
|
const Normal newFaceNormal = normalized(originalNormal * fOriginalWeight + avgNeighborsNormal * (1.f - fOriginalWeight)); |
|
newFaceNormals[idxFace] = newFaceNormal; |
|
} |
|
newFaceNormals.Swap(faceNormals); |
|
} |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
void Mesh::GetEdgeFaces(VIndex v0, VIndex v1, FaceIdxArr& afaces) const |
|
{ |
|
const FaceIdxArr& faces0 = vertexFaces[v0]; |
|
const FaceIdxArr& faces1 = vertexFaces[v1]; |
|
std::unordered_set<FIndex> setFaces1(faces1.begin(), faces1.end()); |
|
for (FIndex idxFace: faces0) { |
|
if (setFaces1.find(idxFace) != setFaces1.end()) |
|
afaces.Insert(idxFace); |
|
} |
|
} |
|
|
|
void Mesh::GetFaceFaces(FIndex f, FaceIdxArr& afaces) const |
|
{ |
|
const Face& face = faces[f]; |
|
const FaceIdxArr& faces0 = vertexFaces[face[0]]; |
|
const FaceIdxArr& faces1 = vertexFaces[face[1]]; |
|
const FaceIdxArr& faces2 = vertexFaces[face[2]]; |
|
std::unordered_set<FIndex> setFaces(faces1.begin(), faces1.end()); |
|
for (FIndex idxFace: faces0) { |
|
if (f != idxFace && setFaces.find(idxFace) != setFaces.end()) |
|
afaces.InsertSortUnique(idxFace); |
|
} |
|
for (FIndex idxFace: faces2) { |
|
if (f != idxFace && setFaces.find(idxFace) != setFaces.end()) |
|
afaces.InsertSortUnique(idxFace); |
|
} |
|
setFaces.clear(); |
|
setFaces.insert(faces2.begin(), faces2.end()); |
|
for (FIndex idxFace: faces0) { |
|
if (f != idxFace && setFaces.find(idxFace) != setFaces.end()) |
|
afaces.InsertSortUnique(idxFace); |
|
} |
|
} |
|
|
|
void Mesh::GetEdgeVertices(FIndex f0, FIndex f1, uint32_t* vs0, uint32_t* vs1) const |
|
{ |
|
const Face& face0 = faces[f0]; |
|
const Face& face1 = faces[f1]; |
|
int i(0); |
|
for (int v=0; v<3; ++v) { |
|
if ((vs1[i] = FindVertex(face1, face0[v])) != NO_ID) { |
|
vs0[i] = v; |
|
if (++i == 2) |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// get the edge orientation in the given face: |
|
// return false for backward, true for forward |
|
bool Mesh::GetEdgeOrientation(FIndex idxFace, VIndex iV0, VIndex iV1) const |
|
{ |
|
const Face& face = faces[idxFace]; |
|
const VIndex i0 = FindVertex(face, iV0); |
|
ASSERT(i0 != NO_ID); |
|
ASSERT(face[(i0+1)%3] == iV1 || face[(i0+2)%3] == iV1); |
|
return face[(i0+1)%3] == iV1; |
|
} |
|
|
|
// find the adjacent face for the given face edge; |
|
// return NO_ID if no adjacent faces exist OR |
|
// more than one adjacent face exist OR |
|
// the edge have opposite orientations in each face |
|
Mesh::FIndex Mesh::GetEdgeAdjacentFace(FIndex idxFace, VIndex iV0, VIndex iV1) const |
|
{ |
|
// iterate over all faces containing the first vertex |
|
ASSERT(vertexFaces.size() == vertices.size()); |
|
const bool edgeOrientation = GetEdgeOrientation(idxFace, iV0, iV1); |
|
FIndex idxFaceAdj = NO_ID; |
|
for (FIndex iF: vertexFaces[iV0]) { |
|
// if this adjacent face is not the analyzed face |
|
if (iF != idxFace) { |
|
// iterate over all face vertices |
|
const Face& face = faces[iF]; |
|
for (int i = 0; i < 3; ++i) { |
|
// if the face vertex is the second vertex |
|
if (face[i] == iV1) { |
|
// check if there are more than two adjacent faces (manifold constraint) |
|
if (idxFaceAdj != NO_ID) |
|
return NO_ID; |
|
// check if edge vertices ordering is opposite in the two faces (manifold constraint) |
|
if (GetEdgeOrientation(iF, iV0, iV1) == edgeOrientation) |
|
return NO_ID; |
|
idxFaceAdj = iF; |
|
} |
|
} |
|
} |
|
} |
|
return idxFaceAdj; |
|
} |
|
|
|
void Mesh::GetAdjVertices(VIndex v, VertexIdxArr& indices) const |
|
{ |
|
ASSERT(vertexFaces.size() == vertices.size()); |
|
const FaceIdxArr& idxFaces = vertexFaces[v]; |
|
std::unordered_set<VIndex> setIndices; |
|
for (FIndex idxFace: idxFaces) { |
|
const Face& face = faces[idxFace]; |
|
for (int i=0; i<3; ++i) { |
|
const VIndex vAdj(face[i]); |
|
if (vAdj != v && setIndices.insert(vAdj).second) |
|
indices.emplace_back(vAdj); |
|
} |
|
} |
|
} |
|
|
|
void Mesh::GetAdjVertexFaces(VIndex idxVCenter, VIndex idxVAdj, FaceIdxArr& indices) const |
|
{ |
|
ASSERT(vertexFaces.size() == vertices.size()); |
|
const FaceIdxArr& idxFaces = vertexFaces[idxVCenter]; |
|
for (FIndex idxFace: idxFaces) { |
|
const Face& face = faces[idxFace]; |
|
ASSERT(FindVertex(face, idxVCenter) != NO_ID); |
|
if (FindVertex(face, idxVAdj) != NO_ID) |
|
indices.emplace_back(idxFace); |
|
} |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
// fix non-manifold vertices and edges; |
|
// return the number of non-manifold issues found |
|
unsigned Mesh::FixNonManifold(float magDisplacementDuplicateVertices, VertexIdxArr* duplicatedVertices) |
|
{ |
|
ASSERT(!vertices.empty() && !faces.empty()); |
|
if (vertexFaces.size() != vertices.size()) |
|
ListIncidenteFaces(); |
|
// iterate over all vertices and separates the components |
|
// incident to the same vertex by duplicating the vertex |
|
unsigned numNonManifoldIssues(0); |
|
CLISTDEF0IDX(int, FIndex) components(faces.size()); |
|
FOREACH(idxVert, vertices) { |
|
// reset component indices to which each face connected to this vertex |
|
const FaceIdxArr& vertFaces = vertexFaces[idxVert]; |
|
for (FIndex iF: vertFaces) |
|
components[iF] = -1; |
|
// find the components connected to this vertex |
|
FaceIdxArr queueFaces; |
|
queueFaces.reserve(vertFaces.size()); |
|
FIndex idxFaceNext(0); |
|
int component(0); |
|
for ( ; ; ++component) { |
|
// find one face not yet belonging to a component |
|
while (idxFaceNext < vertFaces.size()) { |
|
const FIndex iF(vertFaces[idxFaceNext++]); |
|
if (components[iF] == -1) { |
|
// add component as seed to the list |
|
queueFaces.push_back(iF); |
|
// mark the current face with a new component |
|
components[iF] = component; |
|
// process component |
|
goto ProcessComponent; |
|
} |
|
} |
|
// no more components found |
|
break; |
|
ProcessComponent: |
|
// grow seed face component until no more connected faces found |
|
do { |
|
const FIndex idxFaceCurrent(queueFaces.back()); |
|
queueFaces.pop_back(); |
|
const Face& face = faces[idxFaceCurrent]; |
|
// go over all vertices of the current face |
|
for (int i = 0; i < 3; ++i) { |
|
const VIndex idxVertAdj(face[i]); |
|
if (idxVertAdj == idxVert) |
|
continue; |
|
// if there is exactly one face adjacent to this edge |
|
// tag it with the current component and add it to the queue |
|
const FIndex idxFaceAdj(GetEdgeAdjacentFace(idxFaceCurrent, idxVert, idxVertAdj)); |
|
if (idxFaceAdj != NO_ID && components[idxFaceAdj] == -1) { |
|
components[idxFaceAdj] = component; |
|
queueFaces.push_back(idxFaceAdj); |
|
} |
|
} |
|
} while (!queueFaces.empty()); |
|
} |
|
// if there is only one component, continue with the next vertex |
|
if (component <= 1) |
|
continue; |
|
// separate the vertex components |
|
for (int c = 1; c < component; ++c) { |
|
// duplicate the point to achieve the separation |
|
const VIndex idxVertNew = vertices.size(); |
|
const Vertex v = vertices[idxVert]; |
|
vertices.emplace_back(v); |
|
if (duplicatedVertices) |
|
duplicatedVertices->emplace_back(idxVert); |
|
// update the face indices of the current component |
|
FaceIdxArr& vertFacesNew = vertexFaces.emplace_back(); |
|
FaceIdxArr& vertFaces = vertexFaces[idxVert]; |
|
RFOREACH(ivf, vertFaces) { |
|
const FIndex idxFace = vertFaces[ivf]; |
|
if (components[idxFace] != c) |
|
continue; |
|
// link face to the new vertex and remove it from the original vertex |
|
Face& face = faces[idxFace]; |
|
for (int i = 0; i < 3; ++i) { |
|
if (face[i] == idxVert) { |
|
face[i] = idxVertNew; |
|
vertFacesNew.InsertAt(0, idxFace); |
|
break; |
|
} |
|
} |
|
vertFaces.RemoveAtMove(ivf); |
|
} |
|
++numNonManifoldIssues; |
|
} |
|
// adjust vertex positions |
|
if (magDisplacementDuplicateVertices > 0) { |
|
// list changed vertices |
|
VertexIdxArr verts(component); |
|
verts[0] = idxVert; |
|
for (int c = 1; c < component; ++c) |
|
verts[c] = vertices.size()-(component-c); |
|
// adjust the position of the vertices in the direction |
|
// to the center of the first ring of faces |
|
FOREACH(i, verts) { |
|
const VIndex idxVert(verts[i]); |
|
VertexIdxArr adjVerts; |
|
GetAdjVertices(idxVert, adjVerts); |
|
TAccumulator<Vertex> accum; |
|
for (VIndex iV: adjVerts) |
|
accum.Add(vertices[iV], 1.f); |
|
const Vertex bv(accum.Normalized()); |
|
Vertex& v(vertices[idxVert]); |
|
const Vertex dir(bv-v); |
|
v += dir * magDisplacementDuplicateVertices; |
|
} |
|
} |
|
} |
|
vertexFaces.Release(); |
|
return numNonManifoldIssues; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
namespace CLEAN { |
|
// define mesh type |
|
class Vertex; class Edge; class Face; |
|
struct UsedTypes : public vcg::UsedTypes< |
|
vcg::Use<Vertex>::AsVertexType, |
|
vcg::Use<Edge> ::AsEdgeType, |
|
vcg::Use<Face> ::AsFaceType > {}; |
|
|
|
class Vertex : public vcg::Vertex<UsedTypes, vcg::vertex::Coord3f, vcg::vertex::Normal3f, vcg::vertex::VFAdj, vcg::vertex::Mark, vcg::vertex::BitFlags> {}; |
|
class Face : public vcg::Face< UsedTypes, vcg::face::VertexRef, vcg::face::Normal3f, vcg::face::FFAdj, vcg::face::VFAdj, vcg::face::Mark, vcg::face::BitFlags> {}; |
|
class Edge : public vcg::Edge< UsedTypes, vcg::edge::VertexRef, vcg::edge::Mark, vcg::edge::BitFlags> {}; |
|
|
|
class Mesh : public vcg::tri::TriMesh< std::vector<Vertex>, std::vector<Face>, std::vector<Edge> > {}; |
|
|
|
// decimation helper classes |
|
typedef vcg::SimpleTempData< Mesh::VertContainer, vcg::math::Quadric<double> > QuadricTemp; |
|
|
|
class QHelper |
|
{ |
|
public: |
|
QHelper() {} |
|
static void Init() {} |
|
static vcg::math::Quadric<double> &Qd(Vertex &v) { return TD()[v]; } |
|
static vcg::math::Quadric<double> &Qd(Vertex *v) { return TD()[*v]; } |
|
static Vertex::ScalarType W(Vertex * /*v*/) { return 1.0; } |
|
static Vertex::ScalarType W(Vertex & /*v*/) { return 1.0; } |
|
static void Merge(Vertex & /*v_dest*/, Vertex const & /*v_del*/) {} |
|
static QuadricTemp* &TDp() { static QuadricTemp *td; return td; } |
|
static QuadricTemp &TD() { return *TDp(); } |
|
}; |
|
|
|
typedef vcg::tri::BasicVertexPair<Vertex> VertexPair; |
|
|
|
class TriEdgeCollapse : public vcg::tri::TriEdgeCollapseQuadric<Mesh, VertexPair, TriEdgeCollapse, QHelper> { |
|
public: |
|
typedef vcg::tri::TriEdgeCollapseQuadric<Mesh, VertexPair, TriEdgeCollapse, QHelper> TECQ; |
|
inline TriEdgeCollapse(const VertexPair &p, int i, vcg::BaseParameterClass *pp) :TECQ(p, i, pp) {} |
|
}; |
|
} |
|
|
|
// decimate, clean and smooth mesh |
|
// fDecimate factor is in range (0..1], if 1 no decimation takes place |
|
void Mesh::Clean(float fDecimate, float fSpurious, bool bRemoveSpikes, unsigned nCloseHoles, unsigned nSmooth, float fEdgeLength, bool bLastClean) |
|
{ |
|
if (vertices.empty() || faces.empty()) |
|
return; |
|
TD_TIMER_STARTD(); |
|
// create VCG mesh |
|
CLEAN::Mesh mesh; |
|
{ |
|
CLEAN::Mesh::VertexIterator vi = vcg::tri::Allocator<CLEAN::Mesh>::AddVertices(mesh, vertices.size()); |
|
FOREACHPTR(pVert, vertices) { |
|
const Vertex& p(*pVert); |
|
CLEAN::Vertex::CoordType& P((*vi).P()); |
|
P[0] = p.x; |
|
P[1] = p.y; |
|
P[2] = p.z; |
|
++vi; |
|
} |
|
vertices.Release(); |
|
vi = mesh.vert.begin(); |
|
std::vector<CLEAN::Mesh::VertexPointer> indices(mesh.vert.size()); |
|
for (CLEAN::Mesh::VertexPointer& idx: indices) { |
|
idx = &*vi; |
|
++vi; |
|
} |
|
CLEAN::Mesh::FaceIterator fi = vcg::tri::Allocator<CLEAN::Mesh>::AddFaces(mesh, faces.size()); |
|
FOREACHPTR(pFace, faces) { |
|
const Face& f(*pFace); |
|
ASSERT((*fi).VN() == 3); |
|
ASSERT(f[0]<(uint32_t)mesh.vn); |
|
(*fi).V(0) = indices[f[0]]; |
|
ASSERT(f[1]<(uint32_t)mesh.vn); |
|
(*fi).V(1) = indices[f[1]]; |
|
ASSERT(f[2]<(uint32_t)mesh.vn); |
|
(*fi).V(2) = indices[f[2]]; |
|
++fi; |
|
} |
|
faces.Release(); |
|
} |
|
|
|
// decimate mesh |
|
if (fDecimate < 1) { |
|
ASSERT(fDecimate > 0); |
|
const int nZeroAreaFaces = vcg::tri::Clean<CLEAN::Mesh>::RemoveZeroAreaFace(mesh); |
|
DEBUG_ULTIMATE("Removed %d zero-area faces", nZeroAreaFaces); |
|
const int nDuplicateFaces = vcg::tri::Clean<CLEAN::Mesh>::RemoveDuplicateFace(mesh); |
|
DEBUG_ULTIMATE("Removed %d duplicate faces", nDuplicateFaces); |
|
const int nUnreferencedVertices = vcg::tri::Clean<CLEAN::Mesh>::RemoveUnreferencedVertex(mesh); |
|
DEBUG_ULTIMATE("Removed %d unreferenced vertices", nUnreferencedVertices); |
|
vcg::tri::TriEdgeCollapseQuadricParameter pp; |
|
pp.QualityThr = 0.3; // Quality Threshold for penalizing bad shaped faces: the value is in the range [0..1], 0 accept any kind of face (no penalties), 0.5 penalize faces with quality < 0.5, proportionally to their shape |
|
pp.PreserveBoundary = false; // the simplification process tries to not affect mesh boundaries during simplification |
|
pp.PreserveTopology = false; // avoid all collapses that cause a topology change in the mesh (like closing holes, squeezing handles, etc); if checked the genus of the mesh should stay unchanged |
|
pp.QualityWeight = false; // use the Per-Vertex quality as a weighting factor for the simplification: the weight is used as an error amplification value, so a vertex with a high quality value will not be simplified and a portion of the mesh with low quality values will be aggressively simplified |
|
pp.NormalCheck = false; // try to avoid face flipping effects and try to preserve the original orientation of the surface |
|
pp.OptimalPlacement = true; // each collapsed vertex is placed in the position minimizing the quadric error; it can fail (creating bad spikes) in case of very flat areas; if disabled edges are collapsed onto one of the two original vertices and the final mesh is composed by a subset of the original vertices |
|
pp.QualityQuadric = false; // add additional simplification constraints that improves the quality of the simplification of the planar portion of the mesh |
|
// decimate |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::VertexFace(mesh); |
|
vcg::tri::UpdateFlags<CLEAN::Mesh>::FaceBorderFromVF(mesh); |
|
const int TargetFaceNum(ROUND2INT(fDecimate*mesh.fn)); |
|
vcg::math::Quadric<double> QZero; |
|
QZero.SetZero(); |
|
CLEAN::QuadricTemp TD(mesh.vert, QZero); |
|
CLEAN::QHelper::TDp()=&TD; |
|
if (pp.PreserveBoundary) { |
|
pp.FastPreserveBoundary = true; |
|
pp.PreserveBoundary = false; |
|
} |
|
if (pp.NormalCheck) |
|
pp.NormalThrRad = M_PI/4.0; |
|
const int OriginalFaceNum(mesh.fn); |
|
Util::Progress progress(_T("Decimated faces"), OriginalFaceNum-TargetFaceNum); |
|
vcg::LocalOptimization<CLEAN::Mesh> DeciSession(mesh, &pp); |
|
DeciSession.Init<CLEAN::TriEdgeCollapse>(); |
|
DeciSession.SetTargetSimplices(TargetFaceNum); |
|
DeciSession.SetTimeBudget(0.1f); // this allow to update the progress bar 10 time for sec... |
|
while (DeciSession.DoOptimization() && mesh.fn>TargetFaceNum) |
|
progress.display(OriginalFaceNum - mesh.fn); |
|
DeciSession.Finalize<CLEAN::TriEdgeCollapse>(); |
|
progress.close(); |
|
DEBUG_ULTIMATE("Mesh decimated: %d -> %d faces", OriginalFaceNum, TargetFaceNum); |
|
} |
|
|
|
// clean mesh |
|
{ |
|
const int nZeroAreaFaces = vcg::tri::Clean<CLEAN::Mesh>::RemoveZeroAreaFace(mesh); |
|
DEBUG_ULTIMATE("Removed %d zero-area faces", nZeroAreaFaces); |
|
const int nDuplicateFaces = vcg::tri::Clean<CLEAN::Mesh>::RemoveDuplicateFace(mesh); |
|
DEBUG_ULTIMATE("Removed %d duplicate faces", nDuplicateFaces); |
|
const int nUnreferencedVertices = vcg::tri::Clean<CLEAN::Mesh>::RemoveUnreferencedVertex(mesh); |
|
DEBUG_ULTIMATE("Removed %d unreferenced vertices", nUnreferencedVertices); |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
const int nNonManifoldFaces = vcg::tri::Clean<CLEAN::Mesh>::RemoveNonManifoldFace(mesh); |
|
DEBUG_ULTIMATE("Removed %d non-manifold faces", nNonManifoldFaces); |
|
const int nDegenerateVertices = vcg::tri::Clean<CLEAN::Mesh>::RemoveDegenerateVertex(mesh); |
|
DEBUG_ULTIMATE("Removed %d degenerate vertices", nDegenerateVertices); |
|
#if 1 |
|
const int nDuplicateVertices = vcg::tri::Clean<CLEAN::Mesh>::RemoveDuplicateVertex(mesh); |
|
DEBUG_ULTIMATE("Removed %d duplicate vertices", nDuplicateVertices); |
|
#endif |
|
#if 1 |
|
vcg::tri::Allocator<CLEAN::Mesh>::CompactFaceVector(mesh); |
|
vcg::tri::Allocator<CLEAN::Mesh>::CompactVertexVector(mesh); |
|
for (int i=0; i<10; ++i) { |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::VertexFace(mesh); |
|
const int nSplitNonManifoldVertices = vcg::tri::Clean<CLEAN::Mesh>::SplitNonManifoldVertex(mesh, 0.1f); |
|
DEBUG_ULTIMATE("Split %d non-manifold vertices", nSplitNonManifoldVertices); |
|
if (nSplitNonManifoldVertices == 0) |
|
break; |
|
} |
|
#else |
|
const int nNonManifoldVertices = vcg::tri::Clean<CLEAN::Mesh>::RemoveNonManifoldVertex(mesh); |
|
DEBUG_ULTIMATE("Removed %d non-manifold vertices", nNonManifoldVertices); |
|
vcg::tri::Allocator<CLEAN::Mesh>::CompactFaceVector(mesh); |
|
vcg::tri::Allocator<CLEAN::Mesh>::CompactVertexVector(mesh); |
|
#endif |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::AllocateEdge(mesh); |
|
} |
|
|
|
// remove spurious components |
|
if (fSpurious > 0) { |
|
FloatArr edgeLens(0, mesh.EN()); |
|
for (CLEAN::Mesh::EdgeIterator ei=mesh.edge.begin(); ei!=mesh.edge.end(); ++ei) { |
|
const CLEAN::Vertex::CoordType& P0((*ei).V(0)->P()); |
|
const CLEAN::Vertex::CoordType& P1((*ei).V(1)->P()); |
|
edgeLens.Insert((P1-P0).Norm()); |
|
} |
|
#if 0 |
|
const auto ret(ComputeX84Threshold<float,float>(edgeLens.Begin(), edgeLens.size(), 3.f*fSpurious)); |
|
const float thLongEdge(ret.first+ret.second); |
|
#else |
|
const float thLongEdge(edgeLens.GetNth(edgeLens.size()*95/100)*fSpurious); |
|
#endif |
|
// remove faces with too long edges |
|
const size_t numLongFaces(vcg::tri::UpdateSelection<CLEAN::Mesh>::FaceOutOfRangeEdge(mesh, 0, thLongEdge)); |
|
for (CLEAN::Mesh::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end(); ++fi) |
|
if (!(*fi).IsD() && (*fi).IsS()) |
|
vcg::tri::Allocator<CLEAN::Mesh>::DeleteFace(mesh, *fi); |
|
DEBUG_ULTIMATE("Removed %d faces with edges longer than %f", numLongFaces, thLongEdge); |
|
// remove isolated components |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
const std::pair<int, int> delInfo(vcg::tri::Clean<CLEAN::Mesh>::RemoveSmallConnectedComponentsDiameter(mesh, thLongEdge)); |
|
DEBUG_ULTIMATE("Removed %d connected components out of %d", delInfo.second, delInfo.first); |
|
} |
|
|
|
// remove spikes |
|
if (bRemoveSpikes) { |
|
int nTotalSpikes(0); |
|
vcg::tri::RequireVFAdjacency(mesh); |
|
while (true) { |
|
if (fSpurious <= 0 || nTotalSpikes != 0) |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::VertexFace(mesh); |
|
int nSpikes(0); |
|
for (CLEAN::Mesh::VertexIterator vi=mesh.vert.begin(); vi!=mesh.vert.end(); ++vi) { |
|
if (vi->IsD()) |
|
continue; |
|
CLEAN::Face* const start(vi->cVFp()); |
|
if (start == NULL) { |
|
vcg::tri::Allocator<CLEAN::Mesh>::DeleteVertex(mesh, *vi); |
|
continue; |
|
} |
|
vcg::face::JumpingPos<CLEAN::Face> p(start, vi->cVFi(), &*vi); |
|
int count(0); |
|
do { |
|
++count; |
|
p.NextFE(); |
|
} while (p.f!=start); |
|
if (count == 1) { |
|
vcg::tri::Allocator<CLEAN::Mesh>::DeleteVertex(mesh, *vi); |
|
++nSpikes; |
|
} |
|
} |
|
if (nSpikes == 0) |
|
break; |
|
for (CLEAN::Mesh::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end(); ++fi) { |
|
if (!fi->IsD() && |
|
(fi->V(0)->IsD() || |
|
fi->V(1)->IsD() || |
|
fi->V(2)->IsD())) |
|
vcg::tri::Allocator<CLEAN::Mesh>::DeleteFace(mesh, *fi); |
|
} |
|
nTotalSpikes += nSpikes; |
|
} |
|
DEBUG_ULTIMATE("Removed %d spikes", nTotalSpikes); |
|
} |
|
|
|
// close holes |
|
if (nCloseHoles > 0) { |
|
if (fSpurious <= 0 && !bRemoveSpikes) |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
vcg::tri::UpdateNormal<CLEAN::Mesh>::PerFaceNormalized(mesh); |
|
vcg::tri::UpdateNormal<CLEAN::Mesh>::PerVertexAngleWeighted(mesh); |
|
ASSERT(vcg::tri::Clean<CLEAN::Mesh>::CountNonManifoldEdgeFF(mesh) == 0); |
|
const int OriginalSize(mesh.fn); |
|
#if 1 |
|
// When closing holes it tries to prevent the creation of faces that intersect faces adjacent to |
|
// the boundary of the hole. It is an heuristic, non intersecting hole filling can be NP-complete. |
|
const int holeCnt(vcg::tri::Hole<CLEAN::Mesh>::EarCuttingIntersectionFill< vcg::tri::SelfIntersectionEar<CLEAN::Mesh> >(mesh, (int)nCloseHoles, false)); |
|
#else |
|
const int holeCnt = vcg::tri::Hole<CLEAN::Mesh>::EarCuttingFill< vcg::tri::MinimumWeightEar<CLEAN::Mesh> >(mesh, (int)nCloseHoles, false); |
|
#endif |
|
DEBUG_ULTIMATE("Closed %d holes and added %d new faces", holeCnt, mesh.fn-OriginalSize); |
|
} |
|
|
|
// smooth mesh |
|
if (nSmooth > 0) { |
|
if (fSpurious <= 0 && !bRemoveSpikes && nCloseHoles <= 0) |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
vcg::tri::UpdateFlags<CLEAN::Mesh>::FaceBorderFromFF(mesh); |
|
#if 1 |
|
vcg::tri::Smooth<CLEAN::Mesh>::VertexCoordLaplacian(mesh, (int)nSmooth, false, false); |
|
#else |
|
vcg::tri::Smooth<CLEAN::Mesh>::VertexCoordLaplacianHC(mesh, (int)nSmooth, false); |
|
#endif |
|
DEBUG_ULTIMATE("Smoothed %d vertices", mesh.vn); |
|
} |
|
|
|
// remesh |
|
if (fEdgeLength > 0) { |
|
vcg::tri::Clean<CLEAN::Mesh>::RemoveDuplicateVertex(mesh); |
|
vcg::tri::Clean<CLEAN::Mesh>::RemoveUnreferencedVertex(mesh); |
|
vcg::tri::Allocator<CLEAN::Mesh>::CompactEveryVector(mesh); |
|
CLEAN::Mesh original; |
|
vcg::tri::Append<CLEAN::Mesh,CLEAN::Mesh>::MeshCopy(original, mesh); |
|
vcg::tri::IsotropicRemeshing<CLEAN::Mesh>::Params params; |
|
params.SetTargetLen(fEdgeLength); |
|
params.iter = 3; |
|
params.surfDistCheck = false; |
|
params.maxSurfDist = fEdgeLength * 0.4f; |
|
params.cleanFlag = true; |
|
params.userSelectedCreases = false; |
|
try { |
|
vcg::tri::IsotropicRemeshing<CLEAN::Mesh>::Do(mesh, original, params); |
|
} |
|
catch(vcg::MissingPreconditionException& e) { |
|
VERBOSE("error: %s", e.what()); |
|
} |
|
} |
|
|
|
// clean mesh |
|
if (bLastClean && (fSpurious > 0 || bRemoveSpikes || nCloseHoles > 0 || nSmooth > 0)) { |
|
const int nNonManifoldFaces = vcg::tri::Clean<CLEAN::Mesh>::RemoveNonManifoldFace(mesh); |
|
DEBUG_ULTIMATE("Removed %d non-manifold faces", nNonManifoldFaces); |
|
#if 0 // not working |
|
vcg::tri::Allocator<CLEAN::Mesh>::CompactEveryVector(mesh); |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::FaceFace(mesh); |
|
vcg::tri::UpdateTopology<CLEAN::Mesh>::VertexFace(mesh); |
|
const int nSplitNonManifoldVertices = vcg::tri::Clean<CLEAN::Mesh>::SplitNonManifoldVertex(mesh, 0.1f); |
|
DEBUG_ULTIMATE("Split %d non-manifold vertices", nSplitNonManifoldVertices); |
|
#else |
|
const int nNonManifoldVertices = vcg::tri::Clean<CLEAN::Mesh>::RemoveNonManifoldVertex(mesh); |
|
DEBUG_ULTIMATE("Removed %d non-manifold vertices", nNonManifoldVertices); |
|
#endif |
|
} |
|
|
|
// import VCG mesh |
|
{ |
|
ASSERT(vertices.empty() && faces.empty()); |
|
vertices.Reserve(mesh.VN()); |
|
vcg::SimpleTempData<CLEAN::Mesh::VertContainer, VIndex> indices(mesh.vert); |
|
VIndex idx(0); |
|
for (CLEAN::Mesh::VertexIterator vi=mesh.vert.begin(); vi!=mesh.vert.end(); ++vi) { |
|
if (vi->IsD()) |
|
continue; |
|
Vertex& p(vertices.AddEmpty()); |
|
const CLEAN::Vertex::CoordType& P((*vi).P()); |
|
p.x = P[0]; |
|
p.y = P[1]; |
|
p.z = P[2]; |
|
indices[vi] = idx++; |
|
} |
|
faces.Reserve(mesh.FN()); |
|
for (CLEAN::Mesh::FaceIterator fi=mesh.face.begin(); fi!=mesh.face.end(); ++fi) { |
|
if (fi->IsD()) |
|
continue; |
|
CLEAN::Mesh::FacePointer fp(&(*fi)); |
|
Face& f(faces.AddEmpty()); |
|
f[0] = indices[fp->cV(0)]; |
|
f[1] = indices[fp->cV(1)]; |
|
f[2] = indices[fp->cV(2)]; |
|
} |
|
} |
|
DEBUG("Cleaned mesh: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str()); |
|
} // Clean |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// project vertices and compute bounding-box; |
|
// account for diferences in pixel center convention: while OpenMVS uses the same convention as OpenCV and DirectX 9 where the center |
|
// of a pixel is defined at integer coordinates, i.e. the center is at (0, 0) and the top left corner is at (-0.5, -0.5), |
|
// DirectX 10+, OpenGL, and Vulkan convention is the center of a pixel is defined at half coordinates, i.e. the center is at (0.5, 0.5) |
|
// and the top left corner is at (0, 0) |
|
static const Mesh::TexCoord halfPixel(0.5f, 0.5f); |
|
|
|
// translate, normalize and flip Y axis of the texture coordinates |
|
void Mesh::FaceTexcoordsNormalize(TexCoordArr& newFaceTexcoords, bool flipY) const |
|
{ |
|
ASSERT(HasTextureCoordinates()); |
|
newFaceTexcoords.resize(faceTexcoords.size()); |
|
if (texturesDiffuse.empty()) { |
|
if (flipY) { |
|
FOREACH(i, faceTexcoords) { |
|
const TexCoord& texcoord = faceTexcoords[i]; |
|
newFaceTexcoords[i] = TexCoord(texcoord.x, 1.f-texcoord.y); |
|
} |
|
} else |
|
newFaceTexcoords = faceTexcoords; |
|
} else { |
|
TexCoordArr invNorms(texturesDiffuse.size()); |
|
FOREACH(i, texturesDiffuse) { |
|
ASSERT(!texturesDiffuse[i].empty()); |
|
invNorms[i] = TexCoord(1.f/(float)texturesDiffuse[i].cols, 1.f/(float)texturesDiffuse[i].rows); |
|
} |
|
if (flipY) { |
|
FOREACH(i, faceTexcoords) { |
|
const TexCoord& texcoord = faceTexcoords[i]; |
|
const TexCoord& invNorm = invNorms[GetFaceTextureIndex(i/3)]; |
|
newFaceTexcoords[i] = TexCoord( |
|
(texcoord.x+halfPixel.x)*invNorm.x, |
|
1.f-(texcoord.y+halfPixel.y)*invNorm.y |
|
); |
|
} |
|
} else { |
|
FOREACH(i, faceTexcoords) { |
|
const TexCoord& invNorm = invNorms[GetFaceTextureIndex(i/3)]; |
|
newFaceTexcoords[i] = (faceTexcoords[i]+halfPixel)*invNorm; |
|
} |
|
} |
|
} |
|
} // FaceTexcoordsNormalize |
|
|
|
// flip Y axis, unnormalize and translate back texture coordinates |
|
void Mesh::FaceTexcoordsUnnormalize(TexCoordArr& newFaceTexcoords, bool flipY) const |
|
{ |
|
ASSERT(HasTextureCoordinates()); |
|
newFaceTexcoords.resize(faceTexcoords.size()); |
|
if (texturesDiffuse.empty()) { |
|
if (flipY) { |
|
FOREACH(i, faceTexcoords) { |
|
const TexCoord& texcoord = faceTexcoords[i]; |
|
newFaceTexcoords[i] = TexCoord(texcoord.x, 1.f-texcoord.y); |
|
} |
|
} else |
|
newFaceTexcoords = faceTexcoords; |
|
} else { |
|
TexCoordArr scales(texturesDiffuse.size()); |
|
FOREACH(i, texturesDiffuse) { |
|
ASSERT(!texturesDiffuse[i].empty()); |
|
scales[i] = TexCoord((float)texturesDiffuse[i].cols, (float)texturesDiffuse[i].rows); |
|
} |
|
if (flipY) { |
|
FOREACH(i, faceTexcoords) { |
|
const TexCoord& texcoord = faceTexcoords[i]; |
|
const TexCoord& scale = scales[GetFaceTextureIndex(i/3)]; |
|
newFaceTexcoords[i] = TexCoord( |
|
texcoord.x*scale.x-halfPixel.x, |
|
(1.f-texcoord.y)*scale.y-halfPixel.y |
|
); |
|
} |
|
} else { |
|
FOREACH(i, faceTexcoords) { |
|
const TexCoord& scale = scales[GetFaceTextureIndex(i/3)]; |
|
newFaceTexcoords[i] = faceTexcoords[i]*scale - halfPixel; |
|
} |
|
} |
|
} |
|
} // FaceTexcoordsUnnormalize |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// define a PLY file format composed only of vertices and triangles |
|
namespace MeshInternal { |
|
namespace BasicPLY { |
|
// list of the kinds of elements in the PLY |
|
static const char* elem_names[] = { |
|
"vertex", |
|
"face" |
|
}; |
|
// list of property information for a vertex |
|
struct Vertex { |
|
Mesh::Vertex v; |
|
Mesh::Normal n; |
|
static void InitLoadProps(PLY& ply, int elem_count, |
|
Mesh::VertexArr& vertices, Mesh::NormalArr& vertexNormals) |
|
{ |
|
PLY::PlyElement* elm = ply.find_element(elem_names[0]); |
|
const size_t nMaxProps(SizeOfArray(props)); |
|
for (size_t p=0; p<nMaxProps; ++p) { |
|
if (ply.find_property(elm, props[p].name.c_str()) < 0) |
|
continue; |
|
ply.setup_property(props[p]); |
|
switch (p) { |
|
case 0: vertices.resize((IDX)elem_count); break; |
|
case 3: vertexNormals.resize((IDX)elem_count); break; |
|
} |
|
} |
|
} |
|
static void Select(PLY& ply) { |
|
ply.put_element_setup(elem_names[0]); |
|
} |
|
static void InitSaveProps(PLY& ply, int elem_count, |
|
bool bNormals) |
|
{ |
|
ply.describe_property(elem_names[0], 3, props+0); |
|
if (bNormals) |
|
ply.describe_property(elem_names[0], 3, props+3); |
|
if (elem_count) |
|
ply.element_count(elem_names[0], elem_count); |
|
} |
|
static const PLY::PlyProperty props[9]; |
|
}; |
|
const PLY::PlyProperty Vertex::props[] = { |
|
{"x", PLY::Float32, PLY::Float32, offsetof(Vertex,v.x), 0, 0, 0, 0}, |
|
{"y", PLY::Float32, PLY::Float32, offsetof(Vertex,v.y), 0, 0, 0, 0}, |
|
{"z", PLY::Float32, PLY::Float32, offsetof(Vertex,v.z), 0, 0, 0, 0}, |
|
{"nx", PLY::Float32, PLY::Float32, offsetof(Vertex,n.x), 0, 0, 0, 0}, |
|
{"ny", PLY::Float32, PLY::Float32, offsetof(Vertex,n.y), 0, 0, 0, 0}, |
|
{"nz", PLY::Float32, PLY::Float32, offsetof(Vertex,n.z), 0, 0, 0, 0} |
|
}; |
|
// list of property information for a face |
|
struct Face { |
|
struct FaceIndices { |
|
uint8_t num; |
|
Mesh::Face* pFace; |
|
} face; |
|
struct TexCoord { |
|
uint8_t num; |
|
Mesh::TexCoord* pTex; |
|
} tex; |
|
Mesh::TexIndex texId; |
|
float weight; |
|
static void InitLoadProps(PLY& ply, int elem_count, |
|
Mesh::FaceArr& faces, Mesh::TexCoordArr& faceTexcoords, Mesh::TexIndexArr& faceTexindices) |
|
{ |
|
PLY::PlyElement* elm = ply.find_element(elem_names[1]); |
|
const size_t nMaxProps(SizeOfArray(props)); |
|
for (size_t p=0; p<nMaxProps; ++p) { |
|
if (ply.find_property(elm, props[p].name.c_str()) < 0) |
|
continue; |
|
ply.setup_property(props[p]); |
|
switch (p) { |
|
case 0: faces.resize((IDX)elem_count); break; |
|
case 1: faceTexcoords.resize((IDX)elem_count*3); break; |
|
case 2: faceTexindices.resize((IDX)elem_count); break; |
|
} |
|
} |
|
} |
|
static void Select(PLY& ply) { |
|
ply.put_element_setup(elem_names[1]); |
|
} |
|
static void InitSaveProps( |
|
PLY& ply, int elem_count, |
|
bool bFaces, bool bTexcoord, bool bTexnumber, bool bFaceweight=false) |
|
{ |
|
if (bFaces) |
|
ply.describe_property(elem_names[1], props[0]); |
|
if (bTexcoord) |
|
ply.describe_property(elem_names[1], props[1]); |
|
if (bTexnumber) |
|
ply.describe_property(elem_names[1], props[2]); |
|
if (bFaceweight) |
|
ply.describe_property(elem_names[1], props[3]); |
|
if (elem_count) |
|
ply.element_count(elem_names[1], elem_count); |
|
} |
|
static const PLY::PlyProperty props[4]; |
|
}; |
|
const PLY::PlyProperty Face::props[] = { |
|
{"vertex_indices", PLY::Uint32, PLY::Uint32, offsetof(Face,face.pFace), 1, PLY::Uint8, PLY::Uint8, offsetof(Face,face.num)}, |
|
{"texcoord", PLY::Float32, PLY::Float32, offsetof(Face,tex.pTex), 1, PLY::Uint8, PLY::Uint8, offsetof(Face,tex.num)}, |
|
{"texnumber", PLY::Int32, PLY::Uint8, offsetof(Face,texId), 0, 0, 0, 0}, |
|
{"weight", PLY::Float32, PLY::Float32, offsetof(Face,weight), 0, 0, 0, 0}, |
|
}; |
|
} // namespace BasicPLY |
|
} // namespace MeshInternal |
|
|
|
// import the mesh from the given file |
|
bool Mesh::Load(const String& fileName) |
|
{ |
|
TD_TIMER_STARTD(); |
|
const String ext(Util::getFileExt(fileName).ToLower()); |
|
bool ret; |
|
if (ext == _T(".obj")) |
|
ret = LoadOBJ(fileName); |
|
else |
|
if (ext == _T(".gltf") || ext == _T(".glb")) |
|
ret = LoadGLTF(fileName, ext == _T(".glb")); |
|
else |
|
ret = LoadPLY(fileName); |
|
if (!ret) |
|
return false; |
|
DEBUG_EXTRA("Mesh loaded: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str()); |
|
return true; |
|
} |
|
// import the mesh as a PLY file |
|
bool Mesh::LoadPLY(const String& fileName) |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Release(); |
|
|
|
// open PLY file and read header |
|
using namespace MeshInternal; |
|
PLY ply; |
|
if (!ply.read(fileName)) { |
|
DEBUG_EXTRA("error: invalid PLY file"); |
|
return false; |
|
} |
|
for (int i = 0; i < ply.get_elements_count(); ++i) { |
|
int elem_count; |
|
LPCSTR elem_name = ply.setup_element_read(i, &elem_count); |
|
if (PLY::equal_strings(BasicPLY::elem_names[0], elem_name)) { |
|
vertices.resize(elem_count); |
|
} else |
|
if (PLY::equal_strings(BasicPLY::elem_names[1], elem_name)) { |
|
faces.resize(elem_count); |
|
} |
|
} |
|
if (vertices.empty() && faces.empty()) |
|
return true; |
|
if (vertices.empty() || faces.empty()) { |
|
DEBUG_EXTRA("error: invalid mesh file"); |
|
return false; |
|
} |
|
|
|
// read PLY body |
|
for (int i = 0; i < ply.get_elements_count(); i++) { |
|
int elem_count; |
|
LPCSTR elem_name = ply.setup_element_read(i, &elem_count); |
|
if (PLY::equal_strings(BasicPLY::elem_names[0], elem_name)) { |
|
ASSERT(vertices.size() == (VIndex)elem_count); |
|
BasicPLY::Vertex::InitLoadProps(ply, elem_count, vertices, vertexNormals); |
|
if (vertexNormals.empty()) { |
|
for (Vertex& vert: vertices) |
|
ply.get_element(&vert); |
|
} else { |
|
BasicPLY::Vertex vertex; |
|
for (int v=0; v<elem_count; ++v) { |
|
ply.get_element(&vertex); |
|
vertices[v] = vertex.v; |
|
vertexNormals[v] = vertex.n; |
|
} |
|
} |
|
} else |
|
if (PLY::equal_strings(BasicPLY::elem_names[1], elem_name)) { |
|
ASSERT(faces.size() == (FIndex)elem_count); |
|
BasicPLY::Face::InitLoadProps(ply, elem_count, faces, faceTexcoords, faceTexindices); |
|
BasicPLY::Face face; |
|
FOREACH(f, faces) { |
|
ply.get_element(&face); |
|
if (face.face.num != 3) { |
|
DEBUG_EXTRA("error: unsupported mesh file (face not triangle)"); |
|
return false; |
|
} |
|
memcpy(faces.data()+f, face.face.pFace, sizeof(Face)); |
|
delete[] face.face.pFace; |
|
if (!faceTexcoords.empty()) { |
|
if (face.tex.num != 6) { |
|
DEBUG_EXTRA("error: unsupported mesh file (texture coordinates not per face vertex)"); |
|
return false; |
|
} |
|
memcpy(faceTexcoords.data()+f*3, face.tex.pTex, sizeof(TexCoord)*3); |
|
delete[] face.tex.pTex; |
|
} |
|
if (!faceTexindices.empty()) |
|
faceTexindices[f] = face.texId; |
|
} |
|
if (!faceTexcoords.empty()) { |
|
// load the texture |
|
for (const std::string& comment: ply.get_comments()) { |
|
if (_tcsncmp(comment.c_str(), _T("TextureFile "), 12) == 0) { |
|
const String textureFileName(comment.substr(12)); |
|
texturesDiffuse.emplace_back().Load(Util::getFilePath(fileName)+textureFileName); |
|
} |
|
} |
|
if (texturesDiffuse.size() <= 1) |
|
faceTexindices.Release(); |
|
// flip Y axis, unnormalize and translate back texture coordinates |
|
TexCoordArr unnormFaceTexcoords; |
|
FaceTexcoordsUnnormalize(unnormFaceTexcoords, true); |
|
faceTexcoords.Swap(unnormFaceTexcoords); |
|
} |
|
} else { |
|
ply.get_other_element(); |
|
} |
|
} |
|
return true; |
|
} |
|
// import the mesh as a OBJ file |
|
bool Mesh::LoadOBJ(const String& fileName) |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Release(); |
|
|
|
// open and parse OBJ file |
|
ObjModel model; |
|
if (!model.Load(fileName)) { |
|
DEBUG_EXTRA("error: invalid OBJ file"); |
|
return false; |
|
} |
|
|
|
if (model.get_vertices().empty() || model.get_groups().empty()) { |
|
DEBUG_EXTRA("error: invalid mesh file"); |
|
return false; |
|
} |
|
|
|
// store vertices |
|
ASSERT(sizeof(ObjModel::Vertex) == sizeof(Vertex)); |
|
ASSERT(model.get_vertices().size() < std::numeric_limits<VIndex>::max()); |
|
vertices.CopyOf(&model.get_vertices()[0], (VIndex)model.get_vertices().size()); |
|
|
|
// store vertex normals |
|
ASSERT(sizeof(ObjModel::Normal) == sizeof(Normal)); |
|
ASSERT(model.get_vertices().size() < std::numeric_limits<VIndex>::max()); |
|
if (!model.get_normals().empty()) { |
|
ASSERT(model.get_normals().size() == model.get_vertices().size()); |
|
vertexNormals.CopyOf(&model.get_normals()[0], (VIndex)model.get_normals().size()); |
|
} |
|
|
|
// store faces |
|
FOREACH(groupIdx, model.get_groups()) { |
|
const auto& group = model.get_groups()[groupIdx]; |
|
ASSERT(group.faces.size() < std::numeric_limits<FIndex>::max()); |
|
faces.reserve((FIndex)group.faces.size()); |
|
for (const ObjModel::Face& f: group.faces) { |
|
ASSERT(f.vertices[0] != NO_ID); |
|
faces.emplace_back(f.vertices[0], f.vertices[1], f.vertices[2]); |
|
if (f.texcoords[0] != NO_ID) { |
|
for (int i=0; i<3; ++i) |
|
faceTexcoords.emplace_back(model.get_texcoords()[f.texcoords[i]]); |
|
faceTexindices.emplace_back((TexIndex)groupIdx); |
|
} |
|
if (f.normals[0] != NO_ID) { |
|
Normal& n = faceNormals.emplace_back(Normal::ZERO); |
|
for (int i=0; i<3; ++i) |
|
n += normalized(model.get_normals()[f.normals[i]]); |
|
normalize(n); |
|
} |
|
} |
|
// store texture |
|
ObjModel::MaterialLib::Material* pMaterial(model.GetMaterial(group.material_name)); |
|
if (pMaterial && pMaterial->LoadDiffuseMap()) |
|
texturesDiffuse.emplace_back(pMaterial->diffuse_map); |
|
} |
|
|
|
// flip Y axis, unnormalize and translate back texture coordinates |
|
if (!faceTexcoords.empty()) { |
|
if (texturesDiffuse.size() <= 1) |
|
faceTexindices.Release(); |
|
TexCoordArr unnormFaceTexcoords; |
|
FaceTexcoordsUnnormalize(unnormFaceTexcoords, true); |
|
faceTexcoords.Swap(unnormFaceTexcoords); |
|
} |
|
return true; |
|
} |
|
// import the mesh as a GLTF file |
|
bool Mesh::LoadGLTF(const String& fileName, bool bBinary) |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Release(); |
|
|
|
// load model |
|
tinygltf::Model gltfModel; { |
|
tinygltf::TinyGLTF loader; |
|
std::string err, warn; |
|
if (bBinary ? |
|
!loader.LoadBinaryFromFile(&gltfModel, &err, &warn, fileName) : |
|
!loader.LoadASCIIFromFile(&gltfModel, &err, &warn, fileName)) |
|
return false; |
|
if (!err.empty()) { |
|
VERBOSE("error: %s", err.c_str()); |
|
return false; |
|
} |
|
if (!warn.empty()) |
|
DEBUG("warning: %s", warn.c_str()); |
|
} |
|
|
|
// parse model |
|
for (const tinygltf::Mesh& gltfMesh : gltfModel.meshes) { |
|
for (const tinygltf::Primitive& gltfPrimitive : gltfMesh.primitives) { |
|
if (gltfPrimitive.mode != TINYGLTF_MODE_TRIANGLES) |
|
continue; |
|
Mesh mesh; |
|
// read vertices |
|
{ |
|
const tinygltf::Accessor& gltfAccessor = gltfModel.accessors[gltfPrimitive.attributes.at("POSITION")]; |
|
if (gltfAccessor.type != TINYGLTF_TYPE_VEC3) |
|
continue; |
|
const tinygltf::BufferView& gltfBufferView = gltfModel.bufferViews[gltfAccessor.bufferView]; |
|
const tinygltf::Buffer& buffer = gltfModel.buffers[gltfBufferView.buffer]; |
|
const uint8_t* pData = buffer.data.data() + gltfBufferView.byteOffset + gltfAccessor.byteOffset; |
|
mesh.vertices.resize((VIndex)gltfAccessor.count); |
|
if (gltfAccessor.componentType == TINYGLTF_COMPONENT_TYPE_FLOAT) { |
|
ASSERT(gltfBufferView.byteLength == sizeof(Vertex) * gltfAccessor.count); |
|
memcpy(mesh.vertices.data(), pData, gltfBufferView.byteLength); |
|
} |
|
else if (gltfAccessor.componentType == TINYGLTF_COMPONENT_TYPE_DOUBLE) { |
|
for (VIndex i = 0; i < gltfAccessor.count; ++i) |
|
mesh.vertices[i] = ((const Point3d*)pData)[i]; |
|
} |
|
else { |
|
VERBOSE("error: unsupported vertices (component type)"); |
|
continue; |
|
} |
|
} |
|
// read faces |
|
{ |
|
const tinygltf::Accessor& gltfAccessor = gltfModel.accessors[gltfPrimitive.indices]; |
|
if (gltfAccessor.type != TINYGLTF_TYPE_SCALAR) |
|
continue; |
|
const tinygltf::BufferView& gltfBufferView = gltfModel.bufferViews[gltfAccessor.bufferView]; |
|
const tinygltf::Buffer& buffer = gltfModel.buffers[gltfBufferView.buffer]; |
|
const uint8_t* pData = buffer.data.data() + gltfBufferView.byteOffset + gltfAccessor.byteOffset; |
|
mesh.faces.resize((FIndex)(gltfAccessor.count/3)); |
|
if (gltfAccessor.componentType == TINYGLTF_COMPONENT_TYPE_INT || |
|
gltfAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT) { |
|
ASSERT(gltfBufferView.byteLength == sizeof(uint32_t) * gltfAccessor.count); |
|
memcpy(mesh.faces.data(), pData, gltfBufferView.byteLength); |
|
} |
|
else { |
|
VERBOSE("error: unsupported faces (component type)"); |
|
continue; |
|
} |
|
} |
|
Join(mesh); |
|
} |
|
} |
|
return true; |
|
} // Load |
|
/*----------------------------------------------------------------*/ |
|
|
|
// export the mesh to the given file |
|
bool Mesh::Save(const String& fileName, const cList<String>& comments, bool bBinary) const |
|
{ |
|
TD_TIMER_STARTD(); |
|
const String ext(Util::getFileExt(fileName).ToLower()); |
|
bool ret; |
|
if (ext == _T(".obj")) |
|
ret = SaveOBJ(fileName); |
|
else |
|
if (ext == _T(".gltf") || ext == _T(".glb")) |
|
ret = SaveGLTF(fileName, ext == _T(".glb")); |
|
else |
|
ret = SavePLY(ext != _T(".ply") ? String(fileName+_T(".ply")) : fileName, comments, bBinary); |
|
if (!ret) |
|
return false; |
|
DEBUG_EXTRA("Mesh saved: %u vertices, %u faces (%s)", vertices.size(), faces.size(), TD_TIMER_GET_FMT().c_str()); |
|
return true; |
|
} |
|
// export the mesh as a PLY file |
|
bool Mesh::SavePLY(const String& fileName, const cList<String>& comments, bool bBinary, bool bTexLossless) const |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Util::ensureFolder(fileName); |
|
|
|
// create PLY object |
|
using namespace MeshInternal; |
|
PLY ply; |
|
if (!ply.write(fileName, 2, BasicPLY::elem_names, bBinary?PLY::BINARY_LE:PLY::ASCII)) { |
|
DEBUG_EXTRA("error: can not create the mesh file"); |
|
return false; |
|
} |
|
|
|
// export comments |
|
for (const String& comment: comments) |
|
ply.append_comment(comment); |
|
|
|
// export texture file name as comment if needed |
|
if (HasTexture()) { |
|
FOREACH(texId, texturesDiffuse) { |
|
const String textureFileName(Util::getFileFullName(fileName) + std::to_string(texId).c_str() + (bTexLossless?_T(".png"):_T(".jpg"))); |
|
ply.append_comment((_T("TextureFile ")+Util::getFileNameExt(textureFileName)).c_str()); |
|
texturesDiffuse[texId].Save(textureFileName); |
|
} |
|
} |
|
|
|
// describe what properties go into vertex and face elements |
|
ASSERT(vertexNormals.empty() || vertexNormals.size() == vertices.size()); |
|
BasicPLY::Vertex::InitSaveProps(ply, (int)vertices.size(), !vertexNormals.empty()); |
|
BasicPLY::Face::InitSaveProps(ply, (int)faces.size(), !faces.empty(), !faceTexcoords.empty(), !faceTexindices.empty()); |
|
if (!ply.header_complete()) |
|
return false; |
|
|
|
// export the array of vertices |
|
BasicPLY::Vertex::Select(ply); |
|
if (vertexNormals.empty()) { |
|
FOREACHPTR(pVert, vertices) |
|
ply.put_element(pVert); |
|
} else { |
|
BasicPLY::Vertex v; |
|
FOREACH(i, vertices) { |
|
v.v = vertices[i]; |
|
v.n = vertexNormals[i]; |
|
ply.put_element(&v); |
|
} |
|
} |
|
ASSERT(ply.get_current_element_count() == (int)vertices.size()); |
|
|
|
// export the array of faces |
|
BasicPLY::Face::Select(ply); |
|
BasicPLY::Face face = {{3},{6}}; |
|
if (faceTexcoords.empty()) { |
|
FOREACHPTR(pFace, faces) { |
|
face.face.pFace = const_cast<Face*>(pFace); |
|
ply.put_element(&face); |
|
} |
|
} else { |
|
// translate, normalize and flip Y axis of the texture coordinates |
|
TexCoordArr normFaceTexcoords; |
|
FaceTexcoordsNormalize(normFaceTexcoords, true); |
|
|
|
// export the array of faces |
|
FOREACH(f, faces) { |
|
face.face.pFace = faces.data()+f; |
|
face.tex.pTex = normFaceTexcoords.data()+f*3; |
|
if (!faceTexindices.empty()) |
|
face.texId = faceTexindices[f]; |
|
ply.put_element(&face); |
|
} |
|
} |
|
ASSERT(ply.get_current_element_count() == (int)faces.size()); |
|
|
|
return true; |
|
} |
|
// export the mesh as a OBJ file |
|
bool Mesh::SaveOBJ(const String& fileName) const |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Util::ensureFolder(fileName); |
|
|
|
// create the OBJ model |
|
ObjModel model; |
|
|
|
// store vertices |
|
ASSERT(sizeof(ObjModel::Vertex) == sizeof(Vertex)); |
|
model.get_vertices().insert(model.get_vertices().begin(), vertices.begin(), vertices.end()); |
|
|
|
// store vertex normals |
|
ASSERT(sizeof(ObjModel::Normal) == sizeof(Normal)); |
|
ASSERT(model.get_vertices().size() < std::numeric_limits<VIndex>::max()); |
|
if (!vertexNormals.empty()) { |
|
ASSERT(vertexNormals.size() == vertices.size()); |
|
model.get_normals().insert(model.get_normals().begin(), vertexNormals.begin(), vertexNormals.end()); |
|
} |
|
|
|
// store face texture coordinates |
|
ASSERT(sizeof(ObjModel::TexCoord) == sizeof(TexCoord)); |
|
if (!faceTexcoords.empty()) { |
|
// translate, normalize and flip Y axis of the texture coordinates |
|
TexCoordArr normFaceTexcoords; |
|
FaceTexcoordsNormalize(normFaceTexcoords, true); |
|
ASSERT(normFaceTexcoords.size() == faces.size()*3); |
|
model.get_texcoords().insert(model.get_texcoords().begin(), normFaceTexcoords.begin(), normFaceTexcoords.end()); |
|
} |
|
|
|
// store faces |
|
FOREACH(idxTexture, texturesDiffuse) { |
|
ObjModel::Group& group = model.AddGroup(_T("material_" + std::to_string(idxTexture))); |
|
group.faces.reserve(faces.size()); |
|
FOREACH(idxFace, faces) { |
|
if (idxFace < faceTexindices.GetSize()) |
|
{ |
|
const auto texIdx = faceTexindices[idxFace]; |
|
if (texIdx != idxTexture) |
|
continue; |
|
} |
|
|
|
const Face& face = faces[idxFace]; |
|
ObjModel::Face f; |
|
memset(&f, 0xFF, sizeof(ObjModel::Face)); |
|
for (int i=0; i<3; ++i) { |
|
f.vertices[i] = face[i]; |
|
if (!faceTexcoords.empty()) |
|
f.texcoords[i] = idxFace*3+i; |
|
if (!vertexNormals.empty()) |
|
f.normals[i] = face[i]; |
|
} |
|
group.faces.push_back(f); |
|
} |
|
ObjModel::MaterialLib::Material* pMaterial(model.GetMaterial(group.material_name)); |
|
ASSERT(pMaterial != NULL); |
|
pMaterial->diffuse_map = texturesDiffuse[idxTexture]; |
|
} |
|
|
|
return model.Save(fileName, 6U, true); |
|
} |
|
// export the mesh as a GLTF file |
|
template <typename T> |
|
void ExtendBufferGLTF(const T* src, size_t size, tinygltf::Buffer& dst, size_t& byte_offset, size_t& byte_length) { |
|
byte_offset = dst.data.size(); |
|
byte_length = sizeof(T) * size; |
|
byte_length = ((byte_length + 3) / 4) * 4; |
|
dst.data.resize(byte_offset + byte_length); |
|
memcpy(&dst.data[byte_offset], &src[0], byte_length); |
|
} |
|
|
|
bool Mesh::SaveGLTF(const String& fileName, bool bBinary) const |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Util::ensureFolder(fileName); |
|
|
|
std::vector<Mesh> meshes; |
|
if (texturesDiffuse.size() > 1) { |
|
meshes = SplitMeshPerTextureBlob(); |
|
for (Mesh& mesh: meshes) { |
|
Mesh convertedMesh; |
|
mesh.ConvertTexturePerVertex(convertedMesh); |
|
mesh.Swap(convertedMesh); |
|
} |
|
} else { |
|
Mesh convertedMesh; |
|
ConvertTexturePerVertex(convertedMesh); |
|
meshes.emplace_back(std::move(convertedMesh)); |
|
} |
|
|
|
// create GLTF model |
|
tinygltf::Model gltfModel; |
|
tinygltf::Scene gltfScene; |
|
tinygltf::Mesh gltfMesh; |
|
tinygltf::Buffer gltfBuffer; |
|
gltfScene.name = "scene"; |
|
gltfMesh.name = "mesh"; |
|
|
|
for (size_t meshId = 0; meshId < meshes.size(); meshId++) { |
|
const Mesh& mesh = meshes[meshId]; |
|
ASSERT(mesh.HasTextureCoordinatesPerVertex()); |
|
tinygltf::Primitive gltfPrimitive; |
|
// setup vertices |
|
{ |
|
STATIC_ASSERT(3 * sizeof(Vertex::Type) == sizeof(Vertex)); // VertexArr should be continuous |
|
const Box box(GetAABB()); |
|
gltfPrimitive.attributes["POSITION"] = (int)gltfModel.accessors.size(); |
|
tinygltf::Accessor vertexPositionAccessor; |
|
vertexPositionAccessor.name = "vertexPositionAccessor"; |
|
vertexPositionAccessor.bufferView = (int)gltfModel.bufferViews.size(); |
|
vertexPositionAccessor.type = TINYGLTF_TYPE_VEC3; |
|
vertexPositionAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; |
|
vertexPositionAccessor.count = mesh.vertices.size(); |
|
vertexPositionAccessor.minValues = {box.ptMin.x(), box.ptMin.y(), box.ptMin.z()}; |
|
vertexPositionAccessor.maxValues = {box.ptMax.x(), box.ptMax.y(), box.ptMax.z()}; |
|
gltfModel.accessors.emplace_back(std::move(vertexPositionAccessor)); |
|
// setup vertices buffer |
|
tinygltf::BufferView vertexPositionBufferView; |
|
vertexPositionBufferView.name = "vertexPositionBufferView"; |
|
vertexPositionBufferView.buffer = (int)gltfModel.buffers.size(); |
|
ExtendBufferGLTF(mesh.vertices.data(), mesh.vertices.size(), gltfBuffer, |
|
vertexPositionBufferView.byteOffset, vertexPositionBufferView.byteLength); |
|
gltfModel.bufferViews.emplace_back(std::move(vertexPositionBufferView)); |
|
} |
|
|
|
// setup faces |
|
{ |
|
STATIC_ASSERT(3 * sizeof(Face::Type) == sizeof(Face)); // FaceArr should be continuous |
|
gltfPrimitive.indices = (int)gltfModel.accessors.size(); |
|
tinygltf::Accessor triangleAccessor; |
|
triangleAccessor.name = "triangleAccessor"; |
|
triangleAccessor.bufferView = (int)gltfModel.bufferViews.size(); |
|
triangleAccessor.type = TINYGLTF_TYPE_SCALAR; |
|
triangleAccessor.componentType = TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT; |
|
triangleAccessor.count = mesh.faces.size() * 3; |
|
gltfModel.accessors.emplace_back(std::move(triangleAccessor)); |
|
// setup triangles buffer |
|
tinygltf::BufferView triangleBufferView; |
|
triangleBufferView.name = "triangleBufferView"; |
|
triangleBufferView.buffer = (int)gltfModel.buffers.size(); |
|
ExtendBufferGLTF(mesh.faces.data(), mesh.faces.size(), gltfBuffer, |
|
triangleBufferView.byteOffset, triangleBufferView.byteLength); |
|
gltfModel.bufferViews.emplace_back(std::move(triangleBufferView)); |
|
gltfPrimitive.mode = TINYGLTF_MODE_TRIANGLES; |
|
} |
|
|
|
// setup material |
|
gltfPrimitive.material = (int)gltfModel.materials.size(); |
|
tinygltf::Material gltfMaterial; |
|
gltfMaterial.name = "material"; |
|
gltfMaterial.doubleSided = true; |
|
if (mesh.HasTexture()) { |
|
// setup texture |
|
gltfMaterial.emissiveFactor = std::vector<double>{0,0,0}; |
|
gltfMaterial.pbrMetallicRoughness.baseColorTexture.index = (int)gltfModel.textures.size(); |
|
gltfMaterial.pbrMetallicRoughness.baseColorTexture.texCoord = 0; |
|
gltfMaterial.pbrMetallicRoughness.baseColorFactor = std::vector<double>{1,1,1,1}; |
|
gltfMaterial.pbrMetallicRoughness.metallicFactor = 0; |
|
gltfMaterial.pbrMetallicRoughness.roughnessFactor = 1; |
|
gltfMaterial.extensions = {{"KHR_materials_unlit", {}}}; |
|
gltfModel.extensionsUsed = {"KHR_materials_unlit"}; |
|
// setup texture coordinates accessor |
|
gltfPrimitive.attributes["TEXCOORD_0"] = (int)gltfModel.accessors.size(); |
|
tinygltf::Accessor vertexTexcoordAccessor; |
|
vertexTexcoordAccessor.name = "vertexTexcoordAccessor"; |
|
vertexTexcoordAccessor.bufferView = (int)gltfModel.bufferViews.size(); |
|
vertexTexcoordAccessor.componentType = TINYGLTF_COMPONENT_TYPE_FLOAT; |
|
vertexTexcoordAccessor.count = mesh.faceTexcoords.size(); |
|
vertexTexcoordAccessor.type = TINYGLTF_TYPE_VEC2; |
|
gltfModel.accessors.emplace_back(std::move(vertexTexcoordAccessor)); |
|
// setup texture coordinates |
|
STATIC_ASSERT(2 * sizeof(TexCoord::Type) == sizeof(TexCoord)); // TexCoordArr should be continuous |
|
ASSERT(mesh.vertices.size() == mesh.faceTexcoords.size()); |
|
tinygltf::BufferView vertexTexcoordBufferView; |
|
vertexTexcoordBufferView.name = "vertexTexcoordBufferView"; |
|
vertexTexcoordBufferView.buffer = (int)gltfModel.buffers.size(); |
|
TexCoordArr normFaceTexcoords; |
|
mesh.FaceTexcoordsNormalize(normFaceTexcoords, false); |
|
ExtendBufferGLTF(normFaceTexcoords.data(), normFaceTexcoords.size(), gltfBuffer, |
|
vertexTexcoordBufferView.byteOffset, vertexTexcoordBufferView.byteLength); |
|
gltfModel.bufferViews.emplace_back(std::move(vertexTexcoordBufferView)); |
|
// setup texture |
|
tinygltf::Texture texture; |
|
texture.name = "texture"; |
|
texture.source = (int)gltfModel.images.size(); |
|
texture.sampler = (int)gltfModel.samplers.size(); |
|
gltfModel.textures.emplace_back(std::move(texture)); |
|
// setup texture image |
|
tinygltf::Image image; |
|
image.name = Util::getFileFullName(fileName) + "_" + std::to_string(meshId).c_str(); |
|
image.width = mesh.texturesDiffuse[0].cols; |
|
image.height = mesh.texturesDiffuse[0].rows; |
|
image.component = 3; |
|
image.bits = 8; |
|
image.pixel_type = TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE; |
|
image.mimeType = "image/png"; |
|
image.image.resize(mesh.texturesDiffuse[0].size().area() * 3); |
|
mesh.texturesDiffuse[0].copyTo(cv::Mat(mesh.texturesDiffuse[0].size(), CV_8UC3, image.image.data())); |
|
gltfModel.images.emplace_back(std::move(image)); |
|
// setup texture sampler |
|
tinygltf::Sampler sampler; |
|
sampler.name = "sampler"; |
|
sampler.minFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; |
|
sampler.magFilter = TINYGLTF_TEXTURE_FILTER_LINEAR; |
|
sampler.wrapS = TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE; |
|
sampler.wrapT = TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE; |
|
gltfModel.samplers.emplace_back(std::move(sampler)); |
|
} |
|
gltfModel.materials.emplace_back(std::move(gltfMaterial)); |
|
gltfModel.buffers.emplace_back(std::move(gltfBuffer)); |
|
gltfMesh.primitives.emplace_back(std::move(gltfPrimitive)); |
|
} |
|
|
|
// setup scene node |
|
gltfScene.nodes.emplace_back((int)gltfModel.nodes.size()); |
|
tinygltf::Node node; |
|
node.name = "node"; |
|
node.mesh = (int)gltfModel.meshes.size(); |
|
gltfModel.nodes.emplace_back(std::move(node)); |
|
gltfModel.meshes.emplace_back(std::move(gltfMesh)); |
|
gltfModel.scenes.emplace_back(std::move(gltfScene)); |
|
gltfModel.asset.generator = "OpenMVS"; |
|
gltfModel.asset.version = "2.0"; |
|
gltfModel.defaultScene = 0; |
|
|
|
// setup GLTF |
|
struct Tools { |
|
static bool WriteImageData(const std::string *basepath, const std::string *filename, |
|
tinygltf::Image *image, bool embedImages, void *) { |
|
ASSERT(!embedImages); |
|
image->uri = Util::isFullPath(filename->c_str()) ? |
|
Util::getRelativePath(*basepath, *filename) : String(*filename); |
|
String basePath(*basepath); |
|
return cv::imwrite( |
|
Util::ensureFolderSlash(basePath) + image->uri, |
|
cv::Mat(image->height, image->width, CV_8UC3, image->image.data())); |
|
} |
|
}; |
|
tinygltf::TinyGLTF gltf; |
|
gltf.SetImageWriter(Tools::WriteImageData, NULL); |
|
const bool bEmbedImages(false), bEmbedBuffers(true), bPrettyPrint(true); |
|
return gltf.WriteGltfSceneToFile(&gltfModel, fileName, bEmbedImages, bEmbedBuffers, bPrettyPrint, bBinary); |
|
} // Save |
|
/*----------------------------------------------------------------*/ |
|
|
|
bool Mesh::Save(const FacesChunkArr& chunks, const String& fileName, const cList<String>& comments, bool bBinary) const |
|
{ |
|
if (chunks.size() < 2) |
|
return Save(fileName, comments, bBinary); |
|
FOREACH(i, chunks) { |
|
const Mesh mesh(SubMesh(chunks[i].faces)); |
|
if (!mesh.Save(Util::insertBeforeFileExt(fileName, String::FormatString("_chunk%02u", i)), comments, bBinary)) |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
bool Mesh::Save(const VertexArr& vertices, const String& fileName, bool bBinary) |
|
{ |
|
ASSERT(!fileName.empty()); |
|
Util::ensureFolder(fileName); |
|
|
|
// create PLY object |
|
using namespace MeshInternal; |
|
PLY ply; |
|
if (!ply.write(fileName, 1, BasicPLY::elem_names, bBinary?PLY::BINARY_LE:PLY::ASCII)) { |
|
DEBUG_EXTRA("error: can not create the mesh file"); |
|
return false; |
|
} |
|
|
|
// describe what properties go into vertex and face elements |
|
BasicPLY::Vertex::InitSaveProps(ply, (int)vertices.size(), false); |
|
if (!ply.header_complete()) |
|
return false; |
|
|
|
// export the array of vertices |
|
FOREACHPTR(pVert, vertices) |
|
ply.put_element(pVert); |
|
ASSERT(ply.get_current_element_count() == (int)vertices.size()); |
|
|
|
return true; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
// Ensure edge size and improve vertex valence; |
|
// inspired by TransforMesh library of Andrei Zaharescu (cooperz@gmail.com) |
|
// Code: https://scm.gforge.inria.fr/anonscm/svn/mvviewer |
|
// Paper: http://perception.inrialpes.fr/Publications/2007/ZBH07/ |
|
|
|
#include <CGAL/Simple_cartesian.h> |
|
|
|
#include <CGAL/Polyhedron_3.h> |
|
#include <CGAL/Polyhedron_incremental_builder_3.h> |
|
|
|
#include <CGAL/Triangulation_face_base_with_info_2.h> |
|
#include <CGAL/Triangulation_vertex_base_with_info_2.h> |
|
|
|
#include <CGAL/Inverse_index.h> |
|
|
|
#define CURVATURE_TH 0 // 0.1 |
|
#define ROBUST_NORMALS 0 // 4 |
|
#define ENSURE_MIN_AREA 0 // 2 |
|
#define SPLIT_BORDER_EDGES 1 |
|
#define STRONG_EDGE_COLLAPSE_CHECK 0 |
|
|
|
namespace CLN { |
|
typedef CGAL::Simple_cartesian<double> Kernel; |
|
typedef Kernel::Point_3 Point; |
|
typedef Kernel::Vector_3 Vector; |
|
typedef Kernel::Triangle_3 Triangle; |
|
typedef Kernel::Plane_3 Plane; |
|
|
|
inline double v_norm(const Vector& A) { |
|
return SQRT(A*A); // operator * is overloaded as dot product |
|
} |
|
inline Vector v_normalized(const Vector& A) { |
|
const double nrmSq(A*A); |
|
return (nrmSq==0 ? A : A / SQRT(nrmSq)); |
|
} |
|
inline double v_angle(const Vector& A, const Vector& B) { |
|
return acos(MAXF(-1.0, MINF(1.0, v_normalized(A)*v_normalized(B)))); |
|
} |
|
inline double p_angle(const Point& A, const Point& B, const Point& C) { |
|
return v_angle(A-B, C-B); |
|
} |
|
#define edge_size(h) v_norm((h)->vertex()->point() - (h)->next()->next()->vertex()->point()) |
|
|
|
template <class Refs, class T, class P, class Normal> |
|
class MeshVertex : public CGAL::HalfedgeDS_vertex_base<Refs, T, P> |
|
{ |
|
public: |
|
typedef CGAL::HalfedgeDS_vertex_base<Refs, T, P> Base; |
|
|
|
enum FALGS { |
|
FLG_EULER = (1 << 0), // Euler operations |
|
FLG_BORDER = (1 << 1), // border edge |
|
}; |
|
Flags flags; |
|
|
|
Normal normal; |
|
Normal laplacian; |
|
Normal laplacian_deriv; |
|
#if CURVATURE_TH>0 |
|
float mean_curvature; |
|
#endif |
|
|
|
MeshVertex() {} |
|
MeshVertex(const P& pt) : CGAL::HalfedgeDS_vertex_base<Refs, T, P>(pt) {} |
|
|
|
void setBorder() { flags.set(FLG_BORDER); } |
|
void unsetBorder() { flags.unset(FLG_BORDER); } |
|
bool isBorder() const { return flags.isSet(FLG_BORDER); } |
|
|
|
void move(const Vector& offset) { |
|
if (isBorder()) return; |
|
this->point() = this->point() + offset; |
|
} |
|
}; |
|
|
|
template <class Refs, class T, class Normal> |
|
class MeshFacet : public CGAL::HalfedgeDS_face_base<Refs, T> |
|
{ |
|
public: |
|
typedef CGAL::HalfedgeDS_face_base<Refs, T> Base; |
|
typedef typename Refs::Vertex_handle Vertex_handle; |
|
typedef typename Refs::Vertex_const_handle Vertex_const_handle; |
|
typedef typename Refs::Halfedge_handle Halfedge_handle; |
|
typedef typename Refs::Halfedge_const_handle Halfedge_const_handle; |
|
typedef typename Refs::Face_handle Face_handle; |
|
typedef typename Refs::Face_const_handle Face_const_handle; |
|
|
|
char removal_status; // for self intersection removal: U - unvisited; 'P' - partially valid; V - valid |
|
// for connected components: U - unvisited; V - visited |
|
|
|
MeshFacet() : removal_status('U') {} |
|
|
|
inline bool isTrinagle() const { |
|
return this->halfedge()->vertex() == this->halfedge()->next()->next()->next()->vertex(); |
|
} |
|
|
|
inline Triangle triangle() const { |
|
ASSERT(isTrinagle()); |
|
return Triangle(get_point(0), get_point(1), get_point(2)); |
|
} |
|
|
|
inline Point center() const { |
|
ASSERT(isTrinagle()); |
|
return baricentric(0.333f, 0.333f); |
|
} |
|
|
|
inline Point baricentric(float u1, float u2) const { |
|
ASSERT(isTrinagle()); |
|
Point p[3]; |
|
Point result; |
|
p[0] = get_point(0); |
|
p[1] = get_point(1); |
|
p[2] = get_point(2); |
|
return Point(p[0].x()*u1 + p[1].x()*u2 + p[2].x()*(1.f - u1 -u2), |
|
p[0].y()*u1 + p[1].y()*u2 + p[2].y()*(1.f - u1 -u2), |
|
p[0].z()*u1 + p[1].z()*u2 + p[2].z()*(1.f - u1 -u2)); |
|
|
|
} |
|
|
|
inline Halfedge_const_handle get_edge(int index) const { |
|
ASSERT(isTrinagle()); |
|
switch (index) { |
|
case 0: return this->halfedge(); |
|
case 1: return this->halfedge()->next(); |
|
case 2: return this->halfedge()->next()->next(); |
|
} |
|
ASSERT("invalid index" == NULL); |
|
return Halfedge_const_handle(); |
|
} |
|
inline Halfedge_handle get_edge(int index) { |
|
ASSERT(isTrinagle()); |
|
switch (index) { |
|
case 0: return this->halfedge(); |
|
case 1: return this->halfedge()->next(); |
|
case 2: return this->halfedge()->next()->next(); |
|
} |
|
ASSERT("invalid index" == NULL); |
|
return Halfedge_handle(); |
|
} |
|
inline Vertex_const_handle get_vertex(int index) const { |
|
return get_edge(index)->vertex(); |
|
} |
|
inline Vertex_handle get_vertex(int index) { |
|
return get_edge(index)->vertex(); |
|
} |
|
inline Point get_point(int index) const { |
|
return get_edge(index)->vertex()->point(); |
|
} |
|
|
|
inline double edgeStatistics(int mode=1) const { // 0 - min; 1-avg; 2-max |
|
ASSERT(isTrinagle()); |
|
const double e1(v_norm(get_point(0)-get_point(1))); |
|
const double e2(v_norm(get_point(0)-get_point(2))); |
|
const double e3(v_norm(get_point(1)-get_point(2))); |
|
switch (mode) { |
|
case 0: return MINF3(e1, e2, e3); |
|
case 1: return (e1+e2+e3) / 3; |
|
case 2: return MAXF3(e1, e2, e3); |
|
} |
|
ASSERT("invalid mode" == NULL); |
|
return 0; |
|
} |
|
|
|
inline double edgeMin(Halfedge_handle& h) { |
|
ASSERT(isTrinagle()); |
|
const double e1(v_norm(get_point(0)-get_point(2))); |
|
const double e2(v_norm(get_point(1)-get_point(0))); |
|
const double e3(v_norm(get_point(2)-get_point(1))); |
|
if (e1 < e2) { |
|
if (e1 < e3) { |
|
h = get_edge(0); |
|
return e1; |
|
} else { |
|
h = get_edge(2); |
|
return e3; |
|
} |
|
} else { |
|
if (e2 < e3) { |
|
h = get_edge(1); |
|
return e2; |
|
} else { |
|
h = get_edge(2); |
|
return e3; |
|
} |
|
} |
|
} |
|
|
|
inline Normal normal() const { |
|
return CGAL::cross_product(get_point(1)-get_point(0), get_point(2)-get_point(0)); |
|
} |
|
|
|
inline double area() const { |
|
return v_norm(normal())/2; |
|
} |
|
}; |
|
|
|
class MeshItems : public CGAL::Polyhedron_items_3 |
|
{ |
|
public: |
|
template <class Refs, class Traits> |
|
struct Vertex_wrapper { |
|
typedef typename Traits::Point_3 Point; |
|
typedef typename Traits::Vector_3 Normal; |
|
typedef MeshVertex<Refs, CGAL::Tag_true, Point, Normal> Vertex; |
|
}; |
|
template <class Refs, class Traits> |
|
struct Face_wrapper { |
|
typedef typename Traits::Vector_3 Normal; |
|
typedef MeshFacet<Refs, CGAL::Tag_true, Normal> Face; |
|
}; |
|
}; |
|
|
|
typedef CGAL::Polyhedron_3<Kernel, MeshItems> Polyhedron; |
|
|
|
typedef Polyhedron::Vertex Vertex; |
|
typedef Polyhedron::Facet Facet; |
|
typedef Polyhedron::Halfedge Halfedge; |
|
|
|
typedef Polyhedron::Vertex_iterator Vertex_iterator; |
|
typedef Polyhedron::Vertex_const_iterator Vertex_const_iterator; |
|
typedef Polyhedron::Facet_iterator Facet_iterator; |
|
typedef Polyhedron::Facet_const_iterator Facet_const_iterator; |
|
|
|
typedef Polyhedron::Point_iterator Point_iterator; |
|
typedef Polyhedron::Point_const_iterator Point_const_iterator; |
|
typedef Polyhedron::Edge_iterator Edge_iterator; |
|
typedef Polyhedron::Edge_const_iterator Edge_const_iterator; |
|
typedef Polyhedron::Halfedge_iterator Halfedge_iterator; |
|
typedef Polyhedron::Halfedge_const_iterator Halfedge_const_iterator; |
|
typedef Polyhedron::Halfedge_around_facet_circulator HF_circulator; |
|
typedef Polyhedron::Halfedge_around_vertex_circulator HV_circulator; |
|
|
|
struct Stats { |
|
double min, avg, stdDev, max; |
|
}; |
|
static void ComputeStatsArea(const Polyhedron& p, Stats& stats) |
|
{ |
|
MeanStd<double> mean; |
|
stats.min = FLT_MAX; |
|
stats.max = 0; |
|
for (Facet_const_iterator fi = p.facets_begin(); fi!=p.facets_end(); fi++) { |
|
const double tmpArea(fi->area()); |
|
if (stats.min > tmpArea) |
|
stats.min = tmpArea; |
|
if (stats.max < tmpArea) |
|
stats.max = tmpArea; |
|
mean.Update(tmpArea); |
|
} |
|
stats.avg = mean.GetMean(); |
|
stats.stdDev = mean.GetStdDev(); |
|
} |
|
static void ComputeStatsEdge(const Polyhedron& p, Stats& stats) |
|
{ |
|
MeanStd<double> mean; |
|
stats.min = FLT_MAX; |
|
stats.max = 0; |
|
for (Edge_const_iterator ei = p.edges_begin(); ei!=p.edges_end(); ei++) { |
|
const double tmpEdge(v_norm(ei->vertex()->point() - ei->prev()->vertex()->point())); |
|
if (stats.min > tmpEdge) |
|
stats.min = tmpEdge; |
|
if (stats.max < tmpEdge) |
|
stats.max = tmpEdge; |
|
mean.Update(tmpEdge); |
|
} |
|
stats.avg = mean.GetMean(); |
|
stats.stdDev = mean.GetStdDev(); |
|
} |
|
static void ComputeStatsLaplacian(const Polyhedron& p, Stats& stats) |
|
{ |
|
MeanStd<double> mean; |
|
stats.min = FLT_MAX; |
|
stats.max = 0; |
|
for (Vertex_const_iterator vi = p.vertices_begin(); vi !=p.vertices_end(); vi++) { |
|
double tmpNorm(v_norm(vi->laplacian)); |
|
if (vi->laplacian*vi->normal<0.f) |
|
tmpNorm = -tmpNorm; |
|
if (stats.min > tmpNorm) |
|
stats.min = tmpNorm; |
|
if (stats.max < tmpNorm) |
|
stats.max = tmpNorm; |
|
mean.Update(tmpNorm); |
|
} |
|
stats.avg = mean.GetMean(); |
|
stats.stdDev = mean.GetStdDev(); |
|
} |
|
|
|
inline double OppositeAngle(Vertex::Halfedge_handle h) { |
|
ASSERT(h->facet()->is_triangle()); |
|
return v_angle(h->vertex()->point() - h->next()->vertex()->point(), h->prev()->vertex()->point() - h->next()->vertex()->point()); |
|
} |
|
|
|
inline bool CanCollapseCenterVertex(Vertex::Vertex_handle v) { |
|
if (!v->is_trivalent()) return false; |
|
if (v->isBorder()) return false; |
|
return (v->halfedge()->prev()->opposite()->facet() != v->halfedge()->opposite()->next()->opposite()->facet()); |
|
} |
|
|
|
#define REPLACE_POINT(V,P1,P2,PMIDDLE) (((V==P1)||(V==P2)) ? (PMIDDLE) : (V)) |
|
static bool CanCollapseEdge(Vertex::Halfedge_handle v0v1) |
|
{ |
|
if (v0v1->is_border_edge()) |
|
return false; |
|
Vertex::Halfedge_handle v1v0 = v0v1->opposite(); |
|
if (v0v1->next()->opposite()->facet() == v1v0->prev()->opposite()->facet()) |
|
return false; |
|
Vertex::Vertex_handle v0 = v0v1->vertex(); |
|
if (v0->isBorder()) |
|
return false; |
|
Vertex::Vertex_handle v1 = v1v0->vertex(); |
|
if (v1->isBorder()) |
|
return false; |
|
|
|
Vertex::Vertex_handle vl, vr; |
|
Vertex::Halfedge_handle h1, h2; |
|
if (!v0v1->is_border()) { |
|
vl = v0v1->next()->vertex(); |
|
h1 = v0v1->next(); |
|
h2 = v0v1->next()->next(); |
|
if (h1->is_border() || h2->is_border()) |
|
return false; |
|
} |
|
if (!v1v0->is_border()) { |
|
vr = v1v0->next()->vertex(); |
|
h1 = v1v0->next(); |
|
h2 = v1v0->next()->next(); |
|
if (h1->is_border() || h2->is_border()) |
|
return false; |
|
} |
|
// if vl and vr are equal or both invalid -> fail |
|
if (vl == vr) |
|
return false; |
|
|
|
HV_circulator c, d; |
|
|
|
// test intersection of the one-rings of v0 and v1 |
|
c = v0->vertex_begin(); d = c; |
|
CGAL_For_all(c, d) |
|
c->opposite()->vertex()->flags.unset(Vertex::FLG_EULER); |
|
|
|
c = v1->vertex_begin(); d = c; |
|
CGAL_For_all(c, d) |
|
c->opposite()->vertex()->flags.set(Vertex::FLG_EULER); |
|
|
|
c = v0->vertex_begin(); d = c; |
|
CGAL_For_all(c, d) { |
|
Vertex::Vertex_handle vTmp =c->opposite()->vertex(); |
|
if (vTmp->flags.isSet(Vertex::FLG_EULER) && (vTmp!=vl) && (vTmp!=vr)) |
|
return false; |
|
} |
|
|
|
// test weather when performing the edge collapse we change the signed area of any triangle |
|
#if STRONG_EDGE_COLLAPSE_CHECK==1 |
|
Point p0 = v0->point(); |
|
Point p1 = v1->point(); |
|
Point p_middle = p0 + (p1 - p0) / 2; |
|
Point t1, t2, t3; |
|
Vector a1, a2; |
|
for (int x=0; x<2; x++) { |
|
if (x==0) { |
|
c = v0->vertex_begin(); d = c; |
|
} else { |
|
c = v1->vertex_begin(); d = c; |
|
} |
|
CGAL_For_all(c, d) { |
|
t1 = c->vertex()->point(); |
|
t2 = c->next()->vertex()->point(); |
|
t3 = c->next()->next()->vertex()->point(); |
|
a1 = CGAL::cross_product(t2-t1, t3-t2); |
|
t1 = REPLACE_POINT(t1, p0, p1, p_middle); |
|
t2 = REPLACE_POINT(t2, p0, p1, p_middle); |
|
t3 = REPLACE_POINT(t3, p0, p1, p_middle); |
|
a2 = CGAL::cross_product(t2-t1, t3-t2); |
|
|
|
if ((v_norm(a2) != 0) && (v_angle(a1, a2) > PI/2)) |
|
return false; |
|
} |
|
} |
|
#endif |
|
return true; |
|
} |
|
static void CollapseEdge(Polyhedron& p, Vertex::Halfedge_handle h) |
|
{ |
|
Vertex::Halfedge_handle h1 = h->next(); |
|
Vertex::Halfedge_handle h2 = h->opposite()->prev(); |
|
Point p1 = h->vertex()->point(); |
|
Point p2 = h->opposite()->vertex()->point(); |
|
Point p3 = p1 + (p2-p1) /2; |
|
size_t degree_p1 = h->vertex()->vertex_degree(); |
|
size_t degree_p2 = h->opposite()->vertex()->vertex_degree(); |
|
|
|
#if 0 |
|
if (h->vertex()->isBorder()) |
|
p3=p1; |
|
else if (h->opposite()->vertex()->isBorder()) |
|
p3=p2; |
|
else |
|
#endif |
|
if (degree_p1 > degree_p2) |
|
p3=p1; |
|
else |
|
p3=p2; |
|
|
|
h->vertex()->point() = p3; |
|
|
|
p.join_facet(h1->opposite()); |
|
p.join_facet(h2->opposite()); |
|
p.join_vertex(h); |
|
} |
|
|
|
static bool CanFlipEdge(Vertex::Halfedge_handle h) |
|
{ |
|
if (h->is_border_edge()) return false; |
|
const Vertex::Halfedge_handle null_h; |
|
if ((h->next() == null_h) || (h->prev() == null_h) || (h->opposite() == null_h) || (h->opposite()->next() == null_h)) return false; |
|
Vertex::Vertex_handle v0 = h->next()->vertex(); |
|
Vertex::Vertex_handle v1 = h->opposite()->next()->vertex(); |
|
|
|
v0->flags.unset(Vertex::FLG_EULER); |
|
|
|
HV_circulator c = v1->vertex_begin(); |
|
HV_circulator d = c; |
|
CGAL_For_all(c, d) |
|
c->opposite()->vertex()->flags.set(Vertex::FLG_EULER); |
|
|
|
if (v0->flags.isSet(Vertex::FLG_EULER)) return false; |
|
|
|
// check if it increases the quality overall |
|
double a1 = OppositeAngle(h); |
|
double a2 = OppositeAngle(h->next()); |
|
double a3 = OppositeAngle(h->next()->next()); |
|
double b1 = OppositeAngle(h->opposite()); |
|
double b2 = OppositeAngle(h->opposite()->next()); |
|
double b3 = OppositeAngle(h->opposite()->next()->next()); |
|
|
|
if ((a1*a1 + b1*b1) / (a2*a2 + a3*a3 + b2*b2 + b3*b3) < 1.01) return false; |
|
|
|
Vector v_perp_1 = CGAL::cross_product(h->vertex()->point() - h->next()->vertex()->point(), h->vertex()->point() - h->prev()->vertex()->point()); |
|
//Vector v_perp_2 = CGAL::cross_product(h->opposite()->vertex()->point()-h->opposite()->next()->vertex()->point(),h->opposite()->vertex()->point()-h->opposite()->prev()->vertex()->point()); |
|
Vector v_perp_2 = CGAL::cross_product(h->opposite()->next()->vertex()->point() - h->opposite()->vertex()->point(), h->vertex()->point()-h->prev()->vertex()->point()); |
|
if (v_angle(v_perp_1, v_perp_2) > D2R(20)) return false; |
|
|
|
return (h->next()->opposite()->facet() != h->opposite()->prev()->opposite()->facet()) && |
|
(h->prev()->opposite()->facet() != h->opposite()->next()->opposite()->facet()) && |
|
(CGAL::circulator_size(h->opposite()->vertex_begin()) >= 3) && |
|
(CGAL::circulator_size(h->vertex_begin()) >= 3); |
|
} |
|
inline void FlipEdge(Polyhedron& p, Vertex::Halfedge_handle h) { |
|
p.flip_edge(h); |
|
} |
|
|
|
#if SPLIT_BORDER_EDGES>0 |
|
inline bool CanSplitEdge(Vertex::Halfedge_handle& h) { |
|
if (h->vertex()->point() == h->opposite()->vertex()->point()) |
|
return false; |
|
if (h->face() == Vertex::Face_handle()) |
|
h = h->opposite(); |
|
return true; |
|
} |
|
#else |
|
inline bool CanSplitEdge(Vertex::Halfedge_handle h) { |
|
if (h->is_border_edge()) return false; |
|
if (h->facet() == h->opposite()->facet()) return false; |
|
ASSERT(h->facet()->is_triangle() && h->opposite()->facet()->is_triangle()); |
|
return (h->vertex()->point() != h->opposite()->vertex()->point()); |
|
} |
|
#endif |
|
// 1 - middle ; 2-projection of the 3rd vertex |
|
static void SplitEdge(Polyhedron& p, Vertex::Halfedge_handle h, int mode=1) |
|
{ |
|
ASSERT(mode == 1 || mode == 2); |
|
Point p1 = h->vertex()->point(); |
|
Point p2 = h->opposite()->vertex()->point(); |
|
Point p3 = h->next()->vertex()->point(); |
|
|
|
Point p_midddle; |
|
if (mode==1) { // middle |
|
const double ratio(0.5); |
|
p_midddle = p1 + (p2-p1) * ratio; |
|
} else { // projection of the 3rd vertex |
|
const double ratio(v_norm(p3-p2) * cos(OppositeAngle(h->next())) / v_norm(p1-p2)); |
|
p_midddle = p2 + (p1-p2) * ratio; |
|
} |
|
|
|
Vertex::Halfedge_handle hnew = p.split_edge(h); |
|
hnew->vertex()->point() = p_midddle; |
|
|
|
p.split_facet(hnew, h->next()); |
|
#if SPLIT_BORDER_EDGES>0 |
|
if (h->opposite()->face() != Vertex::Face_handle()) |
|
#endif |
|
p.split_facet(h->opposite(), hnew->opposite()->next()); |
|
} |
|
|
|
|
|
// mode : 0 - min, 1 - avg, 2- max |
|
static float ComputeVertexStatistics(Vertex& v, int mode) |
|
{ |
|
ASSERT((mode>=0) && (mode <=2)); |
|
if (v.vertex_degree()==0) return 0; |
|
HV_circulator h = v.vertex_begin(); |
|
float edge_stats((float)edge_size(h)); |
|
int no_h(1); |
|
do { |
|
switch (mode) { |
|
case 0: edge_stats = MINF((float)edge_size(h), edge_stats); break; |
|
case 1: edge_stats += (float)edge_size(h), ++no_h; break; |
|
case 2: edge_stats = MAXF((float)edge_size(h), edge_stats); break; |
|
} |
|
} while (++h != v.vertex_begin()); |
|
if (mode==1) edge_stats = (edge_stats/no_h); |
|
return edge_stats; |
|
} |
|
|
|
static int ImproveVertexValence(Polyhedron& p, int valence_mode=2) |
|
{ |
|
int total_no_ops(0); |
|
switch (valence_mode) { |
|
case 1: { |
|
//erase all the center triangles! |
|
for (Vertex_iterator vi=p.vertices_begin(); vi!=p.vertices_end(); ++vi) { |
|
Vertex::Vertex_handle old_vi = vi; |
|
if (CanCollapseCenterVertex(old_vi)) |
|
p.erase_center_vertex(old_vi->halfedge()); |
|
} |
|
for (Vertex_iterator vi=p.vertices_begin(); vi!=p.vertices_end(); ) { |
|
Vertex::Vertex_handle old_vi = vi; |
|
vi++; |
|
size_t degree = old_vi->vertex_degree(); |
|
//std::cout << "degree" << degree << std::endl; |
|
//if (CanCollapseCenterVertex(old_vi)) |
|
// p.erase_center_vertex(old_vi->halfedge()); |
|
//else |
|
if (degree==4) { |
|
double edge_stats = ComputeVertexStatistics(*old_vi, 0); //min |
|
//std::cout << edge_stats << std::endl; |
|
HV_circulator c, d; |
|
c = old_vi->vertex_begin(); |
|
float current_edge_stats; |
|
current_edge_stats = (float)edge_size(c); |
|
|
|
while (edge_stats!=current_edge_stats) { |
|
//std::cout << "current edge stats:" << current_edge_stats << std::endl; |
|
c++; |
|
current_edge_stats = (float)edge_size(c); |
|
} |
|
d = c; |
|
//bool collapsed(false); |
|
CGAL_For_all(c, d) { |
|
if (CanCollapseEdge(c->opposite())) { |
|
//if ((c->opposite()->vertex()==vi) && (vi!=p.vertices_end())) vi++; |
|
CollapseEdge(p, c->opposite()); |
|
//collapsed = true; |
|
total_no_ops++; |
|
break; |
|
} |
|
} |
|
//if (!collapsed) std::cout << "could not collapse edge!" << std::endl; |
|
} |
|
} |
|
} break; |
|
|
|
case 2: { |
|
int iters(0), no_ops; |
|
do { |
|
iters++; |
|
no_ops = 0; |
|
for (Edge_iterator ei=p.edges_begin(); ei!=p.edges_end(); ++ei) { |
|
if (ei->is_border_edge()) |
|
continue; |
|
int d1_1 = (int)ei->vertex()->vertex_degree(); |
|
int d1_2 = (int)ei->opposite()->vertex()->vertex_degree(); |
|
int d2_1 = (int)ei->next()->vertex()->vertex_degree(); |
|
int d2_2 = (int)ei->opposite()->next()->vertex()->vertex_degree(); |
|
Vertex::Halfedge_handle h = ei; |
|
if (((d1_1+d1_2) - (d2_1+d2_2) > 2) && CanFlipEdge(h)) { |
|
FlipEdge(p, h); |
|
no_ops++; |
|
} |
|
} |
|
//FixDegeneracy(p, 0.2,150); |
|
total_no_ops += no_ops; |
|
} while ((no_ops>0) && (iters<1)); |
|
} break; |
|
|
|
case 3: { |
|
int iters(0), no_ops; |
|
do { |
|
iters++; |
|
no_ops = 0; |
|
for (Edge_iterator ei=p.edges_begin(); ei!=p.edges_end(); ++ei) { |
|
if (ei->is_border_edge()) |
|
continue; |
|
Point p1=ei->vertex()->point(); |
|
Point p2=ei->next()->vertex()->point(); |
|
Point p3=ei->prev()->vertex()->point(); |
|
Point p4=ei->opposite()->next()->vertex()->point(); |
|
|
|
float cost1((float)MINF(MINF(MINF(MINF(MINF(p_angle(p3, p1, p2), p_angle(p1, p3, p2)), p_angle(p4, p1, p3)), p_angle(p4, p3, p1)), p_angle(p1, p4, p2)), p_angle(p1, p2, p3))); |
|
float cost2((float)MINF(MINF(MINF(MINF(MINF(p_angle(p1, p2, p4), p_angle(p1, p4, p2)), p_angle(p3, p4, p2)), p_angle(p4, p2, p3)), p_angle(p4, p1, p2)), p_angle(p4, p3, p2))); |
|
|
|
Vertex::Halfedge_handle h = ei; |
|
if ((cost2 > cost1) && CanFlipEdge(h)) { |
|
FlipEdge(p, h); |
|
no_ops++; |
|
} |
|
} |
|
//FixDegeneracy(p, 0.2,150); |
|
total_no_ops += no_ops; |
|
} while ((no_ops>0) && (iters<1)); |
|
} break; |
|
} |
|
return total_no_ops; |
|
} |
|
|
|
|
|
static void UpdateMeshData(Polyhedron& p); |
|
|
|
// Description: |
|
// It iterates through all the mesh vertices and it tries to fix degenerate triangles. |
|
// There are conditions that check for large and small angles. |
|
// Parameters: |
|
// - degenerateAngleDeg |
|
// - for large angles: if an angle is bigger than degenerateAngleDeg. |
|
// - a good values to use is typically 170 |
|
// - collapseRatio |
|
// - for small angles: given the corresponding edges (a,b,c) in all permutations, if (a/b < collapseRatio) & (a/c < collapseRatio) |
|
// - a good value to use is 0.1 |
|
static int FixDegeneracy(Polyhedron& p, double collapseRatio, double degenerateAngleDeg) |
|
{ |
|
DEBUG_LEVEL(3, "Fix degeneracy: %g collapse-ratio, %g degenerate-angle", collapseRatio, degenerateAngleDeg); |
|
double edge[3]; |
|
int no_ops(0), counter(0); |
|
Vertex::Halfedge_handle edge_h[3]; |
|
for (Facet_iterator fi = p.facets_begin(); fi!=p.facets_end(); ) { |
|
counter++; |
|
if (fi->triangle().is_degenerate()) { |
|
const double avg_edge(fi->edgeStatistics(1)); |
|
DEBUG_LEVEL(3, "Degenerate angle: %g (avg edge %g)", v_angle(fi->get_point(0)-fi->get_point(1), fi->get_point(0)-fi->get_point(2)), avg_edge); |
|
const double delta(avg_edge*0.01); |
|
fi->halfedge()->vertex()->point() = fi->halfedge()->vertex()->point() + Vector(0, 0, delta); |
|
fi->halfedge()->next()->vertex()->point() = fi->halfedge()->next()->vertex()->point() + Vector(0, delta, 0); |
|
fi->halfedge()->next()->next()->vertex()->point() = fi->halfedge()->next()->next()->vertex()->point() + Vector(delta, 0, 0); |
|
} |
|
|
|
// collect facet statistics |
|
HF_circulator hf = fi->facet_begin(); |
|
if (hf == NULL) continue; |
|
int i(0); |
|
do { |
|
ASSERT(ISFINITE(v_norm(hf->vertex()->point() - CGAL::ORIGIN))); |
|
ASSERT(i < 3); // a triangular mesh |
|
edge_h[i] = hf; |
|
edge[i++] = v_norm(hf->vertex()->point() - hf->prev()->vertex()->point()); |
|
} while (++hf !=fi->facet_begin()); |
|
|
|
fi++; |
|
//we should have the 3 sizes of the edges by now |
|
for (i=0; i<3; i++) { |
|
if ((edge[i]/edge[(i+1)%3] < collapseRatio) && (edge[i]/edge[(i+2)%3] < collapseRatio)) { |
|
if (CanCollapseEdge(edge_h[i])) { |
|
while ((fi!=p.facets_end()) && (fi==edge_h[i]->opposite()->facet())) fi++; |
|
CollapseEdge(p, edge_h[i]); |
|
no_ops++; |
|
break; |
|
} |
|
#if 0 |
|
if (CanCollapseEdge(edge_h[i]->opposite())) { |
|
while ((fi!=p.facets_end()) && (fi==edge_h[i]->facet())) fi++; |
|
CollapseEdge(p, edge_h[i]->opposite()); |
|
no_ops++; |
|
break; |
|
} |
|
#endif |
|
} else { |
|
const double tmpAngle(R2D(OppositeAngle(edge_h[i]))); |
|
if (tmpAngle > degenerateAngleDeg && CanFlipEdge(edge_h[i])) { |
|
FlipEdge(p, edge_h[i]); |
|
no_ops++; |
|
break; |
|
} |
|
#if 0 |
|
if (tmpAngle > degenerateAngleDeg && CanSplitEdge(edge_h[i])) { |
|
SplitEdge(p, edge_h[i], 2); |
|
if (CanCollapseEdge(edge_h[i]->prev())) { |
|
while ((fi!=p.facets_end()) && (fi==edge_h[i]->prev()->opposite()->facet())) fi++; |
|
CollapseEdge(p, edge_h[i]->prev()); |
|
no_ops++; |
|
} |
|
no_ops++; |
|
break; |
|
} |
|
#endif |
|
} |
|
} |
|
} |
|
if (no_ops) |
|
UpdateMeshData(p); |
|
return no_ops; |
|
} |
|
static void FixAllDegeneracy(Polyhedron& p, double collapseRatio, double degenerateAngleDeg) |
|
{ |
|
int runs(0); |
|
do { |
|
if (FixDegeneracy(p, collapseRatio, degenerateAngleDeg) == 0) |
|
break; |
|
ImproveVertexValence(p); |
|
} while (runs++ < 3); |
|
} |
|
|
|
|
|
class MeshConnectedComponent { |
|
public: |
|
Vertex::Facet_handle start_facet; |
|
int size; |
|
float area; |
|
float edge_min, edge_avg, edge_max; |
|
bool is_open; |
|
MeshConnectedComponent() { |
|
start_facet = NULL; |
|
size=0; |
|
edge_min=FLT_MAX; |
|
edge_max=0; |
|
edge_avg=0; |
|
area=0.f; |
|
is_open=false; |
|
} |
|
void setParams(Vertex::Facet_handle start_facet_, int size_) { |
|
start_facet = start_facet_; |
|
size = size_; |
|
} |
|
void updateStats(float edge_size) { |
|
edge_max=MAXF(edge_max, edge_size); |
|
edge_min=MINF(edge_min, edge_size); |
|
edge_avg+=edge_size; |
|
} |
|
}; |
|
static void ComputeConnectedComponents(Polyhedron& p, std::vector<MeshConnectedComponent>& connected_components) |
|
{ |
|
connected_components.clear(); |
|
std::queue<Vertex::Facet_handle> facet_queue; |
|
MeshConnectedComponent lastComponent; |
|
|
|
// reset the facet status |
|
for (Facet_iterator i = p.facets_begin(); i != p.facets_end(); ++i) |
|
i->removal_status = 'U'; |
|
|
|
std::cout << "Connected components of: "; |
|
// traverse the mesh via facets |
|
for (Facet_iterator i = p.facets_begin(); i != p.facets_end(); ++i) { |
|
if (i->removal_status=='U') { // start a new component |
|
lastComponent.setParams(i, 0); |
|
i->removal_status='V'; // it is now visited |
|
facet_queue.push(i); |
|
while (!facet_queue.empty()) { // fill the current component |
|
Vertex::Facet_handle f = facet_queue.front(); facet_queue.pop(); |
|
lastComponent.size++; |
|
HF_circulator h = f->facet_begin(); |
|
do { |
|
if (h->is_border_edge()) continue; |
|
lastComponent.is_open=true; |
|
float edge_size = (float)v_norm(h->vertex()->point() - h->prev()->vertex()->point()); |
|
lastComponent.updateStats(edge_size); |
|
lastComponent.area += (float)f->area(); |
|
Vertex::Facet_handle opposite_f = h->opposite()->facet(); |
|
if ((opposite_f!=Vertex::Facet_handle()) && (opposite_f->removal_status=='U')) { |
|
opposite_f->removal_status='V'; // it is now visited |
|
facet_queue.push(opposite_f); |
|
} |
|
|
|
} while (++h != f->facet_begin()); |
|
} // done traversing the current component |
|
lastComponent.edge_avg/=lastComponent.size*3; |
|
connected_components.push_back(lastComponent); |
|
std::cout << lastComponent.size << " faces "; |
|
} // found a new component |
|
} // done traversing the mesh |
|
std::cout << "(" << connected_components.size() << " components)" << std::endl; |
|
} |
|
static void RemoveConnectedComponents(Polyhedron& p, int size_threshold, float edge_threshold) |
|
{ |
|
std::vector<MeshConnectedComponent> connected_components; |
|
ComputeConnectedComponents(p, connected_components); |
|
for (std::vector<MeshConnectedComponent>::iterator vi=connected_components.begin(); vi!=connected_components.end(); ) { |
|
if ((vi->size<=size_threshold) || (vi->edge_max<=edge_threshold) || ((vi->area<edge_threshold*edge_threshold*PI*2) && (vi->is_open==false))) { |
|
p.erase_connected_component(vi->start_facet->facet_begin()); |
|
vi = connected_components.erase(vi); |
|
} else |
|
vi++; |
|
} |
|
} |
|
|
|
// mode : 0 - tangential; 1 - across the normal |
|
inline Vector ComputeVectorComponent(Vector n, Vector v, int mode) |
|
{ |
|
ASSERT((mode>=0) && (mode<2)); |
|
Vector across_normal(n*(n*v)); |
|
if (mode==1) |
|
return across_normal; |
|
else |
|
return v - across_normal; |
|
} |
|
|
|
static void Smooth(Polyhedron& p, double delta, int mode=0) |
|
{ |
|
// 0 - both components; |
|
// 1 - tangential; |
|
// 2 - normal; |
|
// 3 - second order; |
|
// 4 - combined; |
|
// 5 - tangential only if bigger than normal; |
|
// 6 - both components - laplacian_avg; |
|
ASSERT((mode>=0) && (mode<=6)); |
|
Stats laplacian; |
|
if (mode==6) |
|
ComputeStatsLaplacian(p, laplacian); |
|
for (Vertex_iterator vi=p.vertices_begin(); vi!=p.vertices_end(); vi++) { |
|
Vector displacement; |
|
switch (mode) { |
|
case 0: displacement = vi->laplacian*delta/*vert->getMeanCurvatureFlow().Norm()*/; break; |
|
case 1: displacement = ComputeVectorComponent(vi->normal, vi->laplacian, 0) *delta; break; |
|
case 2: displacement = ComputeVectorComponent(vi->normal, vi->laplacian, 1) *delta; break; |
|
case 3: displacement = vi->laplacian_deriv*delta; break; |
|
case 4: displacement = vi->laplacian*delta - vi->laplacian_deriv*delta; break; |
|
case 5: { |
|
Vector d_tan(ComputeVectorComponent(vi->normal, vi->laplacian, 0)*delta); |
|
Vector d_norm(ComputeVectorComponent(vi->normal, vi->laplacian, 1)*delta); |
|
displacement = (v_norm(d_tan) > 2*v_norm(d_norm) ? d_tan : Vector(0, 0, 0)); |
|
} break; |
|
case 6: displacement = vi->laplacian *delta - vi->normal*laplacian.avg*delta; break; |
|
} |
|
vi->move(displacement); |
|
} |
|
UpdateMeshData(p); |
|
} |
|
|
|
|
|
// Description: |
|
// - The goal of this method is to ensure that all the edges of the mesh are within the interval [epsilonMin,epsilonMax]. |
|
// In order to do so, edge collapses and edge split operations are performed. |
|
// - The method also attempts to fix degeneracies by invoking FixDegeneracy(collapseRatio,degenerate_angle_deg) and performs some local smoothing, based on the operating mode. |
|
// Parameters: |
|
// - [epsilonMin, epsilonMax] - the desired edge interval (negative if to be used as multiplier of the initial mean edge length) |
|
// - collapseRatio, degenerate_angle_deg - parameters used to invoke FixDegeneracy (see function for more details) |
|
// - mode : 0 - fixDegeneracy=No smoothing=Yes; |
|
// 1 - fixDegeneracy=Yes smoothing=Yes; (default) |
|
// 10 - fixDegeneracy=Yes smoothing=No; |
|
// - max_iter (default=30) - maximum number of iterations to be performed; since there is no guarantee that one operations (such as a collapse, for example) |
|
// will not in turn generate new degeneracies, operations are being performed on the mesh in an iterative fashion. |
|
static void EnsureEdgeSize(Polyhedron& p, double epsilonMin, double epsilonMax, double collapseRatio, double degenerate_angle_deg, int mode, int max_iters, int comp_size_threshold) |
|
{ |
|
if (mode>0) |
|
FixDegeneracy(p, collapseRatio, degenerate_angle_deg); |
|
|
|
#if ENSURE_MIN_AREA>0 |
|
Stats area; |
|
ComputeStatsArea(p, area); |
|
const float thArea((float)area.avg/ENSURE_MIN_AREA); |
|
#endif |
|
|
|
Stats edge; |
|
ComputeStatsEdge(p, edge); |
|
if (epsilonMin < 0) |
|
epsilonMin = edge.avg * (-epsilonMin); |
|
if (epsilonMax < 0) |
|
epsilonMax = MAXF(edge.avg * (-epsilonMax), epsilonMin * 2); |
|
DEBUG_LEVEL(3, "Ensuring edge size in [%g, %g] with edges currently in [%g, %g]", epsilonMin, epsilonMax, edge.min, edge.max); |
|
|
|
typedef TIndexScore<Vertex::Halfedge_handle, float> EdgeScore; |
|
typedef CLISTDEF0(EdgeScore) EdgeScoreArr; |
|
EdgeScoreArr bigEdges(0, 1024); |
|
int iters(0), total_no_ops(0), no_ops(1); |
|
while ((edge.min<epsilonMin || edge.max>epsilonMax) && (no_ops>0) && (++iters<max_iters)) { |
|
no_ops = 0; |
|
if (iters > 1) |
|
ComputeStatsEdge(p, edge); |
|
|
|
// process big edges |
|
ASSERT(bigEdges.empty()); |
|
for (Halfedge_iterator h = p.edges_begin(); h != p.edges_end(); ++h, ++h) { |
|
ASSERT(++Halfedge_iterator(h) == h->opposite()); |
|
const double edgeSize(edge_size(h)); |
|
if (edgeSize > epsilonMax) |
|
bigEdges.emplace_back(h, (float)edgeSize); |
|
} |
|
DEBUG_LEVEL(3, "Big edges: %u", bigEdges.size()); |
|
bigEdges.Sort(); // process big edges first |
|
for (EdgeScoreArr::IDX i=0; i<bigEdges.size(); ++i) { |
|
Vertex::Halfedge_handle h(bigEdges[i].idx); |
|
if (!CanSplitEdge(h)) |
|
continue; |
|
#if CURVATURE_TH>0 |
|
const float avg_mean_curv(MAXF(ABS(h->vertex()->mean_curvature), ABS(h->prev()->vertex()->mean_curvature))); |
|
if (avg_mean_curv < CURVATURE_TH) |
|
continue; |
|
#endif |
|
#if ENSURE_MIN_AREA>0 |
|
if (h->facet()->area() < thArea) |
|
continue; |
|
#endif |
|
SplitEdge(p, h); |
|
no_ops++; |
|
} |
|
bigEdges.Empty(); |
|
|
|
// process small edges |
|
Vertex::Halfedge_handle h; |
|
Facet_iterator f(p.facets_begin()); |
|
while (f != p.facets_end()) { |
|
const double minEdge(f->edgeMin(h)); |
|
f++; |
|
if (minEdge < epsilonMin && CanCollapseEdge(h)) { |
|
while ((f!=p.facets_end()) && ((f==h->opposite()->facet()) || (f==h->facet()))) f++; |
|
CollapseEdge(p, h); |
|
no_ops++; |
|
} |
|
} |
|
|
|
if (mode <= 10) |
|
ImproveVertexValence(p); |
|
if (mode == 10) |
|
FixDegeneracy(p, collapseRatio, degenerate_angle_deg); |
|
|
|
UpdateMeshData(p); |
|
if (mode < 10) |
|
Smooth(p, 0.1, 1); |
|
|
|
total_no_ops += no_ops; |
|
} |
|
if (mode > 0) { |
|
FixAllDegeneracy(p, collapseRatio, degenerate_angle_deg); |
|
if (mode <10) |
|
Smooth(p, 0.1, 1); |
|
} |
|
|
|
if (comp_size_threshold > 0) |
|
RemoveConnectedComponents(p, comp_size_threshold, (float)edge.min*2); |
|
|
|
#if TD_VERBOSE != TD_VERBOSE_OFF |
|
if (VERBOSITY_LEVEL > 2) { |
|
ComputeStatsEdge(p, edge); |
|
VERBOSE("Edge size in [%g, %g] (requested in [%g, %g]): %d ops, %d iters", edge.min, edge.max, epsilonMin, epsilonMax, total_no_ops, iters); |
|
} |
|
#endif |
|
} |
|
|
|
static void ComputeVertexNormals(Polyhedron& p) |
|
{ |
|
for (Vertex_iterator vi = p.vertices_begin(); vi!=p.vertices_end(); vi++) |
|
vi->normal = CGAL::NULL_VECTOR; |
|
for (Facet_iterator fi = p.facets_begin(); fi!=p.facets_end(); fi++) { |
|
const Vector t(fi->normal()); |
|
Vector& n0(fi->get_vertex(0)->normal); n0 = n0 + t; |
|
Vector& n1(fi->get_vertex(1)->normal); n1 = n1 + t; |
|
Vector& n2(fi->get_vertex(2)->normal); n2 = n2 + t; |
|
} |
|
for (Vertex_iterator vi = p.vertices_begin(); vi!=p.vertices_end(); vi++) { |
|
Vector& normal(vi->normal); |
|
const double nrm(v_norm(normal)); |
|
#if ROBUST_NORMALS==0 |
|
if (nrm != 0) |
|
normal = normal / nrm; |
|
#else |
|
// only temporarily set here, it will be set in normal when computing the robust measure |
|
vi->laplacian_deriv = (nrm != 0 ? (normal / nrm) : CGAL::NULL_VECTOR); |
|
#endif |
|
} |
|
} |
|
#if ROBUST_NORMALS>0 |
|
static std::vector< std::pair<Vertex*, int> > GetRingNeighbourhood(Vertex& v, int ring_size, bool include_original) { |
|
std::map<Vertex*, int> neigh_map; |
|
std::map<Vertex*, int>::iterator iter; |
|
|
|
std::queue<Vertex*> elems; |
|
std::vector< std::pair<Vertex*, int> > result; |
|
|
|
// add base level |
|
elems.push(&v); |
|
neigh_map[&v]=0; |
|
|
|
if (ring_size < 0) return result; |
|
|
|
while (elems.size() > 0) { |
|
Vertex* el = elems.front(); elems.pop(); |
|
if ((el != &v) || include_original) |
|
result.push_back(std::pair<Vertex*, int>(el, neigh_map[el])); |
|
if (neigh_map[el]==ring_size) continue; |
|
//circulate one ring neighborhood |
|
HV_circulator c = el->vertex_begin(); |
|
HV_circulator d = c; |
|
CGAL_For_all(c, d) { |
|
Vertex* next_el = &(*(c->opposite()->vertex())); |
|
iter=neigh_map.find(next_el); |
|
if (iter == neigh_map.end()) { // if the vertex has not been already taken |
|
elems.push(next_el); |
|
neigh_map[next_el]=neigh_map[el]+1; |
|
} |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
static void ComputeVertexRobustNormal(Vertex& v, int maxRings) |
|
{ |
|
std::vector< std::pair<Vertex*, int> > neighs(GetRingNeighbourhood(v, maxRings, true)); |
|
Vector& normal(v.normal); |
|
normal = CGAL::NULL_VECTOR; |
|
for (std::vector< std::pair<Vertex*, int> >::const_iterator n_it=neighs.cbegin(); n_it!=neighs.cend(); ++n_it) { |
|
Vertex* neigh_v = n_it->first; |
|
//const float w = n_it->second / maxRings; |
|
normal = normal + neigh_v->laplacian_deriv;//*w; |
|
} |
|
const double nrm(v_norm(normal)); |
|
if (nrm != 0) |
|
normal = normal / nrm; |
|
} |
|
#endif |
|
|
|
static void ComputeVertexLaplacian(Vertex& v) |
|
{ |
|
// formula taken from "Mesh Smoothing via Mean and Median Filtering Applied to Face Normals" |
|
HV_circulator vi = v.vertex_begin(); |
|
ASSERT(vi != NULL); |
|
Vector result_laplacian(0, 0, 0); |
|
#ifndef LAPLACIAN_ROBUST |
|
size_t order = 0; |
|
do { |
|
++order; |
|
if (vi->is_border_edge()) { |
|
v.laplacian = Vector(0, 0, 0); |
|
return; |
|
} |
|
result_laplacian = result_laplacian + (vi->prev()->vertex()->point() - CGAL::ORIGIN); |
|
} while (++vi != v.vertex_begin()); |
|
result_laplacian = result_laplacian/(double)order - (v.point() - CGAL::ORIGIN); |
|
#else |
|
float w_total(0); |
|
Vector e, e_next, e_prev; |
|
do { |
|
e_next = vi->next()->vertex()->point() - vi->vertex()->point(); |
|
e = vi->prev()->vertex()->point() - vi->vertex()->point(); |
|
e_prev = vi->opposite()->next()->vertex()->point() - vi->vertex()->point(); |
|
|
|
float theta_1((float)v_angle(e, e_next)); |
|
float theta_2((float)v_angle(e, e_prev)); |
|
float w((tan(theta_1/2)+tan(theta_2/2))/v_norm(e)); |
|
|
|
w_total += w; |
|
result_laplacian = result_laplacian + w*e; |
|
} while (++vi != v.vertex_begin()); |
|
result_laplacian = result_laplacian / (double)w_total; |
|
#endif |
|
v.laplacian = result_laplacian; |
|
} |
|
static void ComputeVertexLaplacianDeriv(Vertex& v) |
|
{ |
|
HV_circulator vi = v.vertex_begin(); |
|
ASSERT(vi != NULL); |
|
v.laplacian_deriv = Vector(0, 0, 0); |
|
size_t order(0); |
|
do { |
|
++order; |
|
v.laplacian_deriv = v.laplacian_deriv + (vi->prev()->vertex()->laplacian-v.laplacian); |
|
} while (++vi != v.vertex_begin()); |
|
v.laplacian_deriv = v.laplacian_deriv / (double)order; |
|
} |
|
|
|
#if CURVATURE_TH>0 |
|
static void ComputeVertexCurvature(Vertex& v) |
|
{ |
|
float edge_avg(ComputeVertexStatistics(v, 2)); |
|
float mean_curv(ABS((float)v_norm(v.laplacian) / edge_avg)); |
|
if (v.laplacian*v.normal<0) |
|
mean_curv = -mean_curv; |
|
if (!ISFINITE(mean_curv)) |
|
mean_curv = 0; |
|
v.mean_curvature = mean_curv; |
|
} |
|
#endif |
|
|
|
static void UpdateMeshData(Polyhedron& p) |
|
{ |
|
p.normalize_border(); |
|
|
|
// compute vertex normal |
|
ComputeVertexNormals(p); |
|
#if ROBUST_NORMALS>0 |
|
// compute robust vertex normal |
|
for (Vertex_iterator vi=p.vertices_begin(); vi!=p.vertices_end(); vi++) |
|
ComputeVertexRobustNormal(*vi, ROBUST_NORMALS); |
|
#endif |
|
|
|
// compute Laplacians |
|
for (Vertex_iterator vi=p.vertices_begin(); vi!=p.vertices_end(); vi++) { |
|
vi->unsetBorder(); |
|
ComputeVertexLaplacian(*vi); |
|
} |
|
|
|
// compute curvature and Laplacian derivative |
|
for (Vertex_iterator vi=p.vertices_begin(); vi!=p.vertices_end(); vi++) { |
|
#if CURVATURE_TH>0 |
|
ComputeVertexCurvature(*vi); |
|
#endif |
|
ComputeVertexLaplacianDeriv(*vi); |
|
} |
|
|
|
// set border edges |
|
for (Halfedge_iterator hi=p.border_halfedges_begin(); hi!=p.halfedges_end(); hi++) |
|
hi->vertex()->setBorder(); |
|
} |
|
|
|
// a modifier creating a triangle with the incremental builder |
|
template <class HDS, class K> |
|
class TMeshBuilder : public CGAL::Modifier_base<HDS> |
|
{ |
|
public: |
|
const Mesh::VertexArr& vertices; |
|
const Mesh::FaceArr& faces; |
|
bool bProblems; |
|
|
|
TMeshBuilder(const Mesh::VertexArr& _vertices, const Mesh::FaceArr& _faces) : vertices(_vertices), faces(_faces), bProblems(false) {} |
|
|
|
void operator() (HDS& hds) { |
|
typedef typename HDS::Vertex::Point Point; |
|
CGAL::Polyhedron_incremental_builder_3<HDS> B(hds, false); |
|
B.begin_surface(vertices.size(), faces.size()); |
|
// add the vertices |
|
FOREACH(i, vertices) { |
|
const Mesh::Vertex& v = vertices[i]; |
|
B.add_vertex(Point(v.x, v.y, v.z)); |
|
} |
|
// add the facets |
|
#if TD_VERBOSE != TD_VERBOSE_OFF |
|
String msgFaces; |
|
#endif |
|
FOREACH(i, faces) { |
|
const Mesh::Face& f = faces[i]; |
|
if (!B.test_facet(f.ptr(), f.ptr()+3)) { |
|
bProblems = true; |
|
#if TD_VERBOSE != TD_VERBOSE_OFF |
|
if (VERBOSITY_LEVEL > 1) |
|
msgFaces += String::FormatString(" %u", i); |
|
#endif |
|
continue; |
|
} |
|
B.add_facet(f.ptr(), f.ptr()+3); |
|
} |
|
#if TD_VERBOSE != TD_VERBOSE_OFF |
|
if (bProblems) |
|
DEBUG_EXTRA("warning: ignoring the following facet(s) violating the manifold constraint:%s", msgFaces.c_str()); |
|
#endif |
|
if (B.check_unconnected_vertices()) { |
|
DEBUG_EXTRA("warning: remove unconnected vertices"); |
|
B.remove_unconnected_vertices(); |
|
} |
|
B.end_surface(); |
|
} |
|
}; |
|
typedef TMeshBuilder<Polyhedron::HalfedgeDS, Kernel> MeshBuilder; |
|
static bool ImportMesh(Polyhedron& p, const Mesh::VertexArr& vertices, const Mesh::FaceArr& faces) { |
|
MeshBuilder builder(vertices, faces); |
|
p.delegate(builder); |
|
UpdateMeshData(p); |
|
DEBUG_ULTIMATE("Mesh imported: %u vertices, %u facets (%u border edges)", p.size_of_vertices(), p.size_of_facets(), p.size_of_border_edges()); |
|
return true; |
|
} |
|
static bool ExportMesh(const Polyhedron& p, Mesh::VertexArr& vertices, Mesh::FaceArr& faces) { |
|
if (p.size_of_vertices() >= std::numeric_limits<Mesh::VIndex>::max()) |
|
return false; |
|
if (p.size_of_facets() >= std::numeric_limits<Mesh::FIndex>::max()) |
|
return false; |
|
unsigned nCount; |
|
// extract vertices |
|
nCount = 0; |
|
vertices.Resize((Mesh::VIndex)p.size_of_vertices()); |
|
for (Polyhedron::Vertex_const_iterator it=p.vertices_begin(), ite=p.vertices_end(); it!=ite; ++it) { |
|
Mesh::Vertex& v = vertices[nCount++]; |
|
v.x = (float)CGAL::to_double(it->point().x()); |
|
v.y = (float)CGAL::to_double(it->point().y()); |
|
v.z = (float)CGAL::to_double(it->point().z()); |
|
} |
|
// extract the faces |
|
nCount = 0; |
|
faces.Resize((Mesh::FIndex)p.size_of_facets()); |
|
CGAL::Inverse_index<Polyhedron::Vertex_const_iterator> index(p.vertices_begin(), p.vertices_end()); |
|
for (Polyhedron::Face_const_iterator it=p.facets_begin(), ite=p.facets_end(); it!=ite; ++it) { |
|
ASSERT(it->is_triangle()); |
|
Polyhedron::Halfedge_around_facet_const_circulator hc = it->facet_begin(); |
|
ASSERT(CGAL::circulator_size(hc) == 3); |
|
Mesh::Face& facet = faces[nCount++]; |
|
#if 0 |
|
Polyhedron::Halfedge_around_facet_const_circulator hc_end = hc; |
|
unsigned i(0); |
|
do { |
|
facet[i++] = (Mesh::FIndex)index[Polyhedron::Vertex_const_iterator(hc->vertex())]; |
|
} while (++hc != hc_end); |
|
#else |
|
for (int i=0; i<3; ++i, ++hc) |
|
facet[i] = (Mesh::FIndex)index[Polyhedron::Vertex_const_iterator(hc->vertex())]; |
|
#endif |
|
} |
|
DEBUG_ULTIMATE("Mesh exported: %u vertices, %u facets (%u border edges)", p.size_of_vertices(), p.size_of_facets(), p.size_of_border_edges()); |
|
return true; |
|
} |
|
} // namespace CLN |
|
|
|
void Mesh::EnsureEdgeSize(float epsilonMin, float epsilonMax, float collapseRatio, float degenerate_angle_deg, int mode, int max_iters) |
|
{ |
|
CLN::Polyhedron p; |
|
CLN::ImportMesh(p, vertices, faces); |
|
Release(); |
|
CLN::EnsureEdgeSize(p, epsilonMin, epsilonMax, collapseRatio, degenerate_angle_deg, mode, max_iters, 0); |
|
CLN::ExportMesh(p, vertices, faces); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
// subdivide mesh faces if its projection area |
|
// is bigger than the given number of pixels |
|
void Mesh::Subdivide(const AreaArr& maxAreas, uint32_t maxArea) |
|
{ |
|
ASSERT(vertexFaces.size() == vertices.size()); |
|
|
|
// each face that needs to split, remember for each edge the new vertex index |
|
// (each new vertex index corresponds to the edge opposed to the existing vertex index) |
|
struct SplitFace { |
|
VIndex idxVert[3]; |
|
bool bSplit; |
|
enum {NO_VERT = (VIndex)-1}; |
|
inline SplitFace() : bSplit(false) { memset(idxVert, 0xFF, sizeof(VIndex)*3); } |
|
static VIndex FindSharedEdge(const Face& f, const Face& a) { |
|
for (int i=0; i<2; ++i) { |
|
const VIndex v(f[i]); |
|
if (v != a[0] && v != a[1] && v != a[2]) |
|
return i; |
|
} |
|
ASSERT(f[2] != a[0] && f[2] != a[1] && f[2] != a[2]); |
|
return 2; |
|
} |
|
}; |
|
typedef std::unordered_map<FIndex,SplitFace> FacetSplitMap; |
|
|
|
// used to find adjacent face |
|
typedef Mesh::FacetCountMap FacetCountMap; |
|
|
|
// for each image, compute the projection area of visible faces |
|
FacetSplitMap mapSplits; mapSplits.reserve(faces.size()); |
|
FacetCountMap mapFaces; mapFaces.reserve(12*3); |
|
vertices.Reserve(vertices.size()*2); |
|
faces.Reserve(faces.size()*3); |
|
const uint32_t maxAreaTh(2*maxArea); |
|
FOREACH(f, maxAreas) { |
|
const AreaArr::Type area(maxAreas[f]); |
|
if (area <= maxAreaTh) |
|
continue; |
|
// split face in four triangles |
|
// by adding a new vertex at the middle of each edge |
|
faces.ReserveExtra(4); |
|
Face& newface = faces.AddEmpty(); // defined by the three new vertices |
|
const Face& face = faces[(FIndex)f]; |
|
SplitFace& split = mapSplits[(FIndex)f]; |
|
for (int i=0; i<3; ++i) { |
|
// if the current edge was already split, used the existing vertex |
|
if (split.idxVert[i] != SplitFace::NO_VERT) { |
|
newface[i] = split.idxVert[i]; |
|
continue; |
|
} |
|
// create a new vertex at the middle of the current edge |
|
// (current edge is the opposite edge to the current vertex index) |
|
split.idxVert[i] = newface[i] = vertices.size(); |
|
vertices.emplace_back((vertices[face[(i+1)%3]]+vertices[face[(i+2)%3]])*0.5f); |
|
} |
|
// create the last three faces, defined by one old and two new vertices |
|
for (int i=0; i<3; ++i) { |
|
Face& nf = faces.AddEmpty(); |
|
nf[0] = face[i]; |
|
nf[1] = newface[(i+2)%3]; |
|
nf[2] = newface[(i+1)%3]; |
|
} |
|
split.bSplit = true; |
|
// find all three adjacent faces and inform them of the split |
|
ASSERT(mapFaces.empty()); |
|
for (int i=0; i<3; ++i) { |
|
const Mesh::FaceIdxArr& vf = vertexFaces[face[i]]; |
|
FOREACHPTR(pFace, vf) |
|
++mapFaces[*pFace].count; |
|
} |
|
for (const auto& fc: mapFaces) { |
|
ASSERT(fc.second.count <= 2 || (fc.second.count == 3 && fc.first == f)); |
|
if (fc.second.count != 2) |
|
continue; |
|
if (fc.first < f && maxAreas[fc.first] > maxAreaTh) { |
|
// already fully split, nothing to do |
|
ASSERT(mapSplits[fc.first].idxVert[SplitFace::FindSharedEdge(faces[fc.first], face)] == newface[SplitFace::FindSharedEdge(face, faces[fc.first])]); |
|
continue; |
|
} |
|
const VIndex idxVertex(newface[SplitFace::FindSharedEdge(face, faces[fc.first])]); |
|
VIndex& idxSplit = mapSplits[fc.first].idxVert[SplitFace::FindSharedEdge(faces[fc.first], face)]; |
|
ASSERT(idxSplit == SplitFace::NO_VERT || idxSplit == idxVertex); |
|
idxSplit = idxVertex; |
|
} |
|
mapFaces.clear(); |
|
} |
|
|
|
// add all faces partially split |
|
int indices[3]; |
|
for (const auto& s: mapSplits) { |
|
const SplitFace& split = s.second; |
|
if (split.bSplit) |
|
continue; |
|
int count(0); |
|
for (int i=0; i<3; ++i) { |
|
if (split.idxVert[i] != SplitFace::NO_VERT) |
|
indices[count++] = i; |
|
} |
|
ASSERT(count > 0); |
|
faces.ReserveExtra(4); |
|
const Face& face = faces[s.first]; |
|
switch (count) { |
|
case 1: { |
|
// one edge is split; create two triangles |
|
const int i(indices[0]); |
|
Face& nf0 = faces.AddEmpty(); |
|
nf0[0] = split.idxVert[i]; |
|
nf0[1] = face[(i+2)%3]; |
|
nf0[2] = face[i]; |
|
Face& nf1 = faces.AddEmpty(); |
|
nf1[0] = split.idxVert[i]; |
|
nf1[1] = face[i]; |
|
nf1[2] = face[(i+1)%3]; |
|
break; } |
|
case 2: { |
|
// two edges are split; create three triangles |
|
const int i0(indices[0]); |
|
const int i1(indices[1]); |
|
Face& nf0 = faces.AddEmpty(); |
|
Face& nf1 = faces.AddEmpty(); |
|
Face& nf2 = faces.AddEmpty(); |
|
if (i0==0) { |
|
if (i1==1) { |
|
nf0[0] = split.idxVert[1]; |
|
nf0[1] = split.idxVert[0]; |
|
nf0[2] = face[2]; |
|
nf1[0] = face[0]; |
|
nf1[1] = face[1]; |
|
nf1[2] = split.idxVert[0]; |
|
nf2[0] = face[0]; |
|
nf2[1] = split.idxVert[0]; |
|
nf2[2] = split.idxVert[1]; |
|
} else { |
|
nf0[0] = split.idxVert[2]; |
|
nf0[1] = face[1]; |
|
nf0[2] = split.idxVert[0]; |
|
nf1[0] = face[0]; |
|
nf1[1] = split.idxVert[2]; |
|
nf1[2] = face[2]; |
|
nf2[0] = split.idxVert[2]; |
|
nf2[1] = split.idxVert[0]; |
|
nf2[2] = face[2]; |
|
} |
|
} else { |
|
ASSERT(i0==1 && i1==2); |
|
nf0[0] = face[0]; |
|
nf0[1] = split.idxVert[2]; |
|
nf0[2] = split.idxVert[1]; |
|
nf1[0] = split.idxVert[1]; |
|
nf1[1] = face[1]; |
|
nf1[2] = face[2]; |
|
nf2[0] = split.idxVert[2]; |
|
nf2[1] = face[1]; |
|
nf2[2] = split.idxVert[1]; |
|
} |
|
break; } |
|
case 3: { |
|
// all three edges are split; create four triangles |
|
// create the new triangle in the middle |
|
Face& newface = faces.AddEmpty(); |
|
newface[0] = split.idxVert[0]; |
|
newface[1] = split.idxVert[1]; |
|
newface[2] = split.idxVert[2]; |
|
// create the last three faces, defined by one old and two new vertices |
|
for (int i=0; i<3; ++i) { |
|
Face& nf = faces.AddEmpty(); |
|
nf[0] = face[i]; |
|
nf[1] = newface[(i+2)%3]; |
|
nf[2] = newface[(i+1)%3]; |
|
} |
|
break; } |
|
} |
|
} |
|
|
|
// remove all faces that split |
|
ASSERT(faces.size()-(faces.capacity()/3)/*initial size*/ > mapSplits.size()); |
|
for (const auto& s: mapSplits) |
|
faces.RemoveAt(s.first); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
// decimate mesh by removing the given list of vertices |
|
//#define DECIMATE_JOINHOLES // not finished |
|
void Mesh::Decimate(VertexIdxArr& verticesRemove) |
|
{ |
|
ASSERT(vertices.size() == vertexFaces.size()); |
|
FaceIdxArr facesRemove(0, verticesRemove.size()*8); |
|
#ifdef DECIMATE_JOINHOLES |
|
cList<VertexIdxArr> holes; |
|
#endif |
|
FOREACHPTR(pIdxV, verticesRemove) { |
|
const VIndex idxV(*pIdxV); |
|
ASSERT(idxV < vertices.size()); |
|
// create the list of consecutive vertices around selected vertex |
|
VertexIdxArr verts; |
|
{ |
|
FaceIdxArr& vf(vertexFaces[idxV]); |
|
if (vf.empty()) |
|
continue; |
|
const FIndex n(vf.size()); |
|
facesRemove.Join(vf); |
|
ASSERT(verts.empty()); |
|
{ |
|
// add vertices of the first face |
|
const Face& f = faces[vf.front()]; |
|
const uint32_t i(FindVertex(f, idxV)); |
|
verts.Insert(f[(i+1)%3]); |
|
verts.Insert(f[(i+2)%3]); |
|
vf.RemoveAt(0); |
|
} |
|
while (verts.size() < n) { |
|
// find the face that contains our vertex and the last added vertex |
|
const VIndex idxVL(verts.Last()); |
|
FOREACH(idxF, vf) { |
|
const Face& f = faces[vf[idxF]]; |
|
ASSERT(FindVertex(f, idxV) != NO_ID); |
|
const uint32_t i(FindVertex(f, idxVL)); |
|
if (i == NO_ID) |
|
continue; |
|
// add the missing vertex at the end |
|
ASSERT(f[(i+2)%3] == idxV); |
|
const FIndex idxVN(f[(i+1)%3]); |
|
ASSERT(verts.front() != idxVN); |
|
verts.Insert(idxVN); |
|
vf.RemoveAt(idxF); |
|
goto NEXT_FACE_FORWARD; |
|
} |
|
#ifndef DECIMATE_JOINHOLES |
|
vf.Release(); |
|
goto NEXT_VERTEX; |
|
NEXT_FACE_FORWARD:; |
|
} |
|
vf.Release(); |
|
#else |
|
break; |
|
NEXT_FACE_FORWARD:; |
|
} |
|
while (!vf.empty()) { |
|
// find the face that contains our vertex and the first added vertex |
|
const VIndex idxVF(verts.front()); |
|
FOREACH(idxF, vf) { |
|
const Face& f = faces[vf[idxF]]; |
|
ASSERT(FindVertex(f, idxV) != NO_ID); |
|
const uint32_t i(FindVertex(f, idxVF)); |
|
if (i == NO_ID) |
|
continue; |
|
// add the missing vertex at the beginning |
|
ASSERT(f[(i+1)%3] == idxV); |
|
const FIndex idxVP(f[(i+2)%3]); |
|
ASSERT(verts.Last() != idxVP || vf.size() == 1); |
|
if (verts.Last() != idxVP) |
|
verts.InsertAt(0, idxVP); |
|
vf.RemoveAt(idxF); |
|
goto NEXT_FACE_BACKWARD; |
|
} |
|
vf.Release(); |
|
goto NEXT_VERTEX; |
|
NEXT_FACE_BACKWARD:; |
|
} |
|
#endif |
|
} |
|
// remove the deleted faces from each vertex face list |
|
FOREACHPTR(pV, verts) { |
|
FaceIdxArr& vf(vertexFaces[*pV]); |
|
RFOREACH(i, vf) { |
|
const Face& f = faces[vf[i]]; |
|
if (FindVertex(f, idxV) != NO_ID) |
|
vf.RemoveAt(i); |
|
} |
|
} |
|
#ifdef DECIMATE_JOINHOLES |
|
// find the hole that contains the vertex to be deleted |
|
FOREACHPTR(pHole, holes) { |
|
const VIndex idxVH(pHole->Find(idxV)); |
|
if (idxVH == VertexIdxArr::NO_INDEX) |
|
continue; |
|
// extend the hole with the new loop vertices |
|
VertexIdxArr& hole(*pHole); |
|
hole.RemoveAtMove(idxVH); |
|
const VIndex idxS((idxVH+hole.size()-1)%hole.size()); |
|
const VIndex idxL(verts.Find(hole[idxS])); |
|
ASSERT(idxL != VertexIdxArr::NO_INDEX); |
|
ASSERT(verts[(idxL+verts.size()-1)%verts.size()] == hole[(idxS+1)%hole.size()]); |
|
const VIndex n(verts.size()-2); |
|
for (VIndex v=1; v<=n; ++v) |
|
hole.InsertAt(idxS+v, verts[(idxL+v)%verts.size()]); |
|
goto NEXT_VERTEX; |
|
} |
|
// or create a new hole |
|
if (verts.size() < 3) |
|
continue; |
|
verts.Swap(holes.AddEmpty()); |
|
#else |
|
// close the holes defined by the complete loop of consecutive vertices |
|
// (the loop can be opened, cause some of the vertices can be on the border) |
|
if (verts.size() > 2) |
|
CloseHoleQuality(verts); |
|
#endif |
|
NEXT_VERTEX:; |
|
} |
|
#ifndef _RELEASE |
|
// check all removed vertices are completely disconnected from the mesh |
|
FOREACHPTR(pIdxV, verticesRemove) |
|
ASSERT(vertexFaces[*pIdxV].empty()); |
|
#endif |
|
|
|
// remove deleted faces |
|
RemoveFaces(facesRemove, true); |
|
|
|
// remove deleted vertices |
|
RemoveVertices(verticesRemove); |
|
|
|
#ifdef DECIMATE_JOINHOLES |
|
// close the holes defined by the complete loop of consecutive vertices |
|
// (the loop can be opened, cause some of the vertices can be on the border) |
|
FOREACHPTR(pHole, holes) { |
|
ASSERT(pHole->size() > 2); |
|
CloseHoleQuality(*pHole); |
|
} |
|
#endif |
|
|
|
#ifndef _RELEASE |
|
// check all faces see valid vertices |
|
for (const Face& face: faces) |
|
for (int v=0; v<3; ++v) |
|
ASSERT(face[v] < vertices.size()); |
|
#endif |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
// given a hole defined by a complete loop of consecutive vertices, |
|
// split it recursively in two halves till the splits becomes a face |
|
void Mesh::CloseHole(VertexIdxArr& split0) |
|
{ |
|
ASSERT(split0.size() >= 3); |
|
if (split0.size() == 3) { |
|
const FIndex idxF(faces.size()); |
|
faces.emplace_back(split0[0], split0[1], split0[2]); |
|
for (int v=0; v<3; ++v) { |
|
#ifndef _RELEASE |
|
FaceIdxArr indices; |
|
GetAdjVertexFaces(split0[v], split0[(v+1)%3], indices); |
|
ASSERT(indices.size() < 2); |
|
indices.Empty(); |
|
GetAdjVertexFaces(split0[v], split0[(v+2)%3], indices); |
|
ASSERT(indices.size() < 2); |
|
#endif |
|
vertexFaces[split0[v]].Insert(idxF); |
|
} |
|
return; |
|
} |
|
const VIndex i(split0.size() >> 1); |
|
const VIndex j(split0.size()-i); |
|
VertexIdxArr split1(0, j+1); |
|
split1.Join(split0.data()+i, j); |
|
split1.emplace_back(split0.front()); |
|
split0.RemoveLast(j-1); |
|
CloseHole(split0); |
|
CloseHole(split1); |
|
} |
|
|
|
// given a hole defined by a complete loop of consecutive vertices, |
|
// fills it using an heap to choose the best candidate face to be added |
|
void Mesh::CloseHoleQuality(VertexIdxArr& verts) |
|
{ |
|
struct CandidateFace |
|
{ |
|
Face face; |
|
float angle; |
|
float dihedral; |
|
float aspectRatio; |
|
|
|
CandidateFace() {} |
|
// the vertices of the given face must be in the order they appear on the border of the hole |
|
// (the middle face vertex must be between the first and third on the border) |
|
CandidateFace(VIndex v0, VIndex v1, VIndex v2, const Mesh& mesh) : face(v0,v1,v2) { |
|
const Normal n(mesh.FaceNormal(face)); |
|
// compute the angle between the two existing edges of the face |
|
// (the angle computation takes into account the case of reversed face) |
|
angle = ACOS(ComputeAngle(mesh.vertices[face[1]].ptr(), mesh.vertices[face[0]].ptr(), mesh.vertices[face[2]].ptr())); |
|
if (n.dot(mesh.VertexNormal(face[1])) < 0) |
|
angle = float(2*M_PI) - angle; |
|
// compute quality as a composition of dihedral angle and area/sum(edge^2); |
|
// the dihedral angle uses the normal of the edge faces |
|
// which are possible not to exist if the edges are on the border |
|
FaceIdxArr indices; |
|
mesh.GetAdjVertexFaces(face[2], face[0], indices); |
|
if (indices.size() > 1) { |
|
aspectRatio = -1; |
|
return; |
|
} |
|
indices.Empty(); |
|
mesh.GetAdjVertexFaces(face[0], face[1], indices); |
|
if (indices.size() > 1) { |
|
aspectRatio = -1; |
|
return; |
|
} |
|
const FIndex i0(indices.size()); |
|
mesh.GetAdjVertexFaces(face[1], face[2], indices); |
|
if (indices.size()-i0 > 1) { |
|
aspectRatio = -1; |
|
return; |
|
} |
|
if (indices.empty()) |
|
dihedral = FD2R(33.f); |
|
else { |
|
const Normal n0(mesh.FaceNormal(mesh.faces[indices[0]])); |
|
if (indices.size() == 1) |
|
dihedral = ACOS(ComputeAngle(n.ptr(), n0.ptr())); |
|
else { |
|
const Normal n1(mesh.FaceNormal(mesh.faces[indices[1]])); |
|
dihedral = MAXF(ACOS(ComputeAngle(n.ptr(), n0.ptr())), ACOS(ComputeAngle(n.ptr(), n1.ptr()))); |
|
} |
|
} |
|
aspectRatio = ComputeTriangleQuality(mesh.vertices[face[0]], mesh.vertices[face[1]], mesh.vertices[face[2]]); |
|
} |
|
|
|
inline operator const Face&() const { return face; } |
|
inline bool IsConcave() const { return angle > (float)M_PI; } |
|
inline float GetQuality() const { return aspectRatio - 0.3f/*diedral weight*/*(dihedral/(float)M_PI); } |
|
|
|
// In the heap, by default, we retrieve the LARGEST value, |
|
// so if we need the ear with minimal dihedral angle, we must reverse the sign of the comparison. |
|
// The concave elements must be all in the end of the heap, sorted accordingly, |
|
// So if only one of the two ear is Concave that one is always the minimum one. |
|
inline bool operator < (const CandidateFace& c) const { |
|
if ( IsConcave() && !c.IsConcave()) return true; |
|
if (!IsConcave() && c.IsConcave()) return false; |
|
return GetQuality() < c.GetQuality(); |
|
} |
|
}; |
|
|
|
// create the initial list of new possible face along the edge of the hole |
|
ASSERT(verts.size() > 2); |
|
cList<CandidateFace> candidateFaces(0, verts.size()); |
|
FOREACH(v, verts) { |
|
if (candidateFaces.emplace_back(verts[v], verts[(v+1)%verts.size()], verts[(v+2)%verts.size()], *this).aspectRatio < 0) |
|
candidateFaces.RemoveLast(); |
|
} |
|
candidateFaces.Sort(); |
|
|
|
// add new faces until there are only two vertices left |
|
while(true) { |
|
// add the best candidate face |
|
ASSERT(!candidateFaces.empty()); |
|
const Face& candidateFace = candidateFaces.Last(); |
|
ASSERT(verts.Find(candidateFace[0]) != VertexIdxArr::NO_INDEX); |
|
ASSERT(verts.Find(candidateFace[1]) != VertexIdxArr::NO_INDEX); |
|
ASSERT(verts.Find(candidateFace[2]) != VertexIdxArr::NO_INDEX); |
|
const FIndex idxF(faces.size()); |
|
faces.Insert(candidateFace); |
|
for (int v=0; v<3; ++v) { |
|
#ifndef _RELEASE |
|
FaceIdxArr indices; |
|
GetAdjVertexFaces(candidateFace[v], candidateFace[(v+1)%3], indices); |
|
ASSERT(indices.size() < 2); |
|
indices.Empty(); |
|
GetAdjVertexFaces(candidateFace[v], candidateFace[(v+2)%3], indices); |
|
ASSERT(indices.size() < 2); |
|
#endif |
|
vertexFaces[candidateFace[v]].Insert(idxF); |
|
} |
|
if (verts.size() <= 3) |
|
break; |
|
const VIndex idxV(verts.Find(candidateFace[1])); |
|
// remove all candidate face containing this vertex |
|
{ |
|
candidateFaces.RemoveLast(); |
|
const VIndex idxVert(verts[idxV]); |
|
int n(0); |
|
RFOREACH(c, candidateFaces) |
|
if (FindVertex(candidateFaces[c].face, idxVert) != NO_ID) { |
|
candidateFaces.RemoveAtMove(c); |
|
if (++n == 2) |
|
break; |
|
} |
|
} |
|
// insert the two new candidate faces |
|
const VIndex idxB(idxV+verts.size()); |
|
const VIndex idxVB2(verts[(idxB-2)%verts.size()]); |
|
const VIndex idxVB1(verts[(idxB-1)%verts.size()]); |
|
const VIndex idxVF1(verts[(idxV+1)%verts.size()]); |
|
const VIndex idxVF2(verts[(idxV+2)%verts.size()]); |
|
{ |
|
const CandidateFace newCandidateFace(idxVB2, idxVB1, idxVF1, *this); |
|
if (newCandidateFace.aspectRatio >= 0) |
|
candidateFaces.InsertSort(newCandidateFace); |
|
} |
|
{ |
|
const CandidateFace newCandidateFace(idxVB1, idxVF1, idxVF2, *this); |
|
if (newCandidateFace.aspectRatio >= 0) |
|
candidateFaces.InsertSort(newCandidateFace); |
|
} |
|
verts.RemoveAtMove(idxV); |
|
} |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
// crop mesh such that none of its faces is touching or outside the given bounding-box |
|
void Mesh::RemoveFacesOutside(const OBB3f& obb) { |
|
ASSERT(obb.IsValid()); |
|
VertexIdxArr vertexRemove; |
|
FOREACH(i, vertices) |
|
if (!obb.Intersects(vertices[i])) |
|
vertexRemove.emplace_back(i); |
|
if (!vertexRemove.empty()) { |
|
if (vertices.size() != vertexFaces.size()) |
|
ListIncidenteFaces(); |
|
RemoveVertices(vertexRemove, true); |
|
} |
|
} |
|
|
|
// remove the given list of faces |
|
void Mesh::RemoveFaces(FaceIdxArr& facesRemove, bool bUpdateLists) |
|
{ |
|
facesRemove.Sort(); |
|
FIndex idxLast(FaceIdxArr::NO_INDEX); |
|
if (!bUpdateLists || vertexFaces.empty()) { |
|
RFOREACHPTR(pIdxF, facesRemove) { |
|
const FIndex idxF(*pIdxF); |
|
if (idxLast == idxF) |
|
continue; |
|
faces.RemoveAt(idxF); |
|
if (!faceTexcoords.empty()) |
|
faceTexcoords.RemoveAt(idxF * 3, 3); |
|
idxLast = idxF; |
|
} |
|
} else { |
|
ASSERT(vertices.size() == vertexFaces.size()); |
|
RFOREACHPTR(pIdxF, facesRemove) { |
|
const FIndex idxF(*pIdxF); |
|
if (idxLast == idxF) |
|
continue; |
|
{ |
|
// remove face from vertex face list |
|
const Face& face = faces[idxF]; |
|
for (int v=0; v<3; ++v) { |
|
const VIndex idxV(face[v]); |
|
FaceIdxArr& vf(vertexFaces[idxV]); |
|
const FIndex idx(vf.Find(idxF)); |
|
if (idx != FaceIdxArr::NO_INDEX) |
|
vf.RemoveAt(idx); |
|
} |
|
} |
|
const FIndex idxFM(faces.size()-1); |
|
if (idxF < idxFM) { |
|
// update all vertices of the moved face |
|
const Face& face = faces[idxFM]; |
|
for (int v=0; v<3; ++v) { |
|
const VIndex idxV(face[v]); |
|
FaceIdxArr& vf(vertexFaces[idxV]); |
|
const FIndex idx(vf.Find(idxFM)); |
|
if (idx != FaceIdxArr::NO_INDEX) |
|
vf[idx] = idxF; |
|
} |
|
} |
|
faces.RemoveAt(idxF); |
|
if (!faceTexcoords.empty()) |
|
faceTexcoords.RemoveAt(idxF * 3, 3); |
|
idxLast = idxF; |
|
} |
|
} |
|
vertexVertices.Release(); |
|
} |
|
|
|
// remove the given list of vertices, together with all faces containing them |
|
void Mesh::RemoveVertices(VertexIdxArr& vertexRemove, bool bUpdateLists) |
|
{ |
|
ASSERT(vertices.size() == vertexFaces.size()); |
|
vertexRemove.Sort(); |
|
VIndex idxLast(VertexIdxArr::NO_INDEX); |
|
if (!bUpdateLists) { |
|
RFOREACHPTR(pIdxV, vertexRemove) { |
|
const VIndex idxV(*pIdxV); |
|
if (idxLast == idxV) |
|
continue; |
|
const VIndex idxVM(vertices.size()-1); |
|
if (idxV < idxVM) { |
|
// update all faces of the moved vertex |
|
const FaceIdxArr& vf(vertexFaces[idxVM]); |
|
FOREACHPTR(pIdxF, vf) |
|
GetVertex(faces[*pIdxF], idxVM) = idxV; |
|
} |
|
vertexFaces.RemoveAt(idxV); |
|
vertices.RemoveAt(idxV); |
|
idxLast = idxV; |
|
} |
|
return; |
|
} |
|
FaceIdxArr facesRemove; |
|
RFOREACHPTR(pIdxV, vertexRemove) { |
|
const VIndex idxV(*pIdxV); |
|
if (idxLast == idxV) |
|
continue; |
|
const VIndex idxVM(vertices.size()-1); |
|
if (idxV < idxVM) { |
|
// update all faces of the moved vertex |
|
const FaceIdxArr& vf(vertexFaces[idxVM]); |
|
FOREACHPTR(pIdxF, vf) |
|
GetVertex(faces[*pIdxF], idxVM) = idxV; |
|
} |
|
if (!vertexFaces.empty()) { |
|
facesRemove.Join(vertexFaces[idxV]); |
|
vertexFaces.RemoveAt(idxV); |
|
} |
|
if (!vertexVertices.empty()) |
|
vertexVertices.RemoveAt(idxV); |
|
vertices.RemoveAt(idxV); |
|
idxLast = idxV; |
|
} |
|
if (!facesRemove.empty()) |
|
RemoveFaces(facesRemove); |
|
} |
|
|
|
// remove all vertices that are not assigned to any face |
|
// (require vertexFaces) |
|
Mesh::VIndex Mesh::RemoveUnreferencedVertices(bool bUpdateLists) |
|
{ |
|
ASSERT(vertices.size() == vertexFaces.size()); |
|
VertexIdxArr vertexRemove; |
|
FOREACH(idxV, vertexFaces) { |
|
if (vertexFaces[idxV].empty()) |
|
vertexRemove.push_back(idxV); |
|
} |
|
if (vertexRemove.empty()) |
|
return 0; |
|
RemoveVertices(vertexRemove, bUpdateLists); |
|
return vertexRemove.size(); |
|
} |
|
|
|
// convert textured mesh to store texture coordinates per vertex instead of per face |
|
void Mesh::ConvertTexturePerVertex(Mesh& mesh) const |
|
{ |
|
ASSERT(HasTexture()); |
|
mesh.vertices = vertices; |
|
mesh.faces.resize(faces.size()); |
|
mesh.faceTexcoords.resize(vertices.size()); |
|
if (!faceTexindices.empty()) |
|
mesh.faceTexindices.resize(vertices.size()); |
|
|
|
VertexIdxArr mapVertices(vertices.size(), vertices.size()*3/2); |
|
mapVertices.Memset(0xff); |
|
FOREACH(idxF, faces) { |
|
// face vertices inside a patch are simply copied; |
|
// face vertices on the patch boundary are duplicated, |
|
// with the same position, but different texture coordinates |
|
const Face& face = faces[idxF]; |
|
Face& newface = mesh.faces[idxF]; |
|
const TexIndex ti = !faceTexindices.empty() ? faceTexindices[idxF] : 0; |
|
for (int i=0; i<3; ++i) { |
|
const TexCoord& tc = faceTexcoords[idxF*3+i]; |
|
VIndex idxV(face[i]); |
|
while (true) { |
|
VIndex& idxVT = mapVertices[idxV]; |
|
if (idxVT == NO_ID) { |
|
// vertex seen for the first time, so just copy it |
|
mesh.faceTexcoords[newface[i] = idxVT = idxV] = tc; |
|
if (!faceTexindices.empty()) |
|
mesh.faceTexindices[newface[i] = idxVT = idxV] = ti; |
|
break; |
|
} |
|
// vertex already seen in an other face, check the texture coordinates |
|
if (mesh.faceTexcoords[idxV] == tc) { |
|
// texture coordinates equal, patch interior vertex, link to it |
|
newface[i] = idxV; |
|
break; |
|
} |
|
if (idxVT == idxV) { |
|
// duplicate vertex, copy position, but update its texture coordinates |
|
mapVertices.emplace_back(newface[i] = idxVT = mesh.vertices.size()); |
|
mesh.vertices.emplace_back(vertices[face[i]]); |
|
mesh.faceTexcoords.emplace_back(tc); |
|
if (!faceTexindices.empty()) |
|
mesh.faceTexindices.emplace_back(ti); |
|
break; |
|
} |
|
// continue with the next linked vertex which share the position, |
|
// but use different texture coordinates |
|
idxV = idxVT; |
|
} |
|
} |
|
} |
|
mesh.texturesDiffuse = texturesDiffuse; |
|
} // ConvertTexturePerVertex |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// estimate the ground-plane as the plane agreeing with most vertices |
|
// - sampleMesh: uniformly samples points on the mesh (0 - disabled, <0 - number of points, >0 - sample density per square unit) |
|
// - planeThreshold: threshold used to estimate the ground plane (0 - auto) |
|
Planef Mesh::EstimateGroundPlane(const ImageArr& images, float sampleMesh, float planeThreshold, const String& fileExportPlane) const |
|
{ |
|
ASSERT(!IsEmpty()); |
|
PointCloud pointcloud; |
|
if (sampleMesh != 0) { |
|
// create the point cloud by sampling the mesh |
|
if (sampleMesh > 0) |
|
SamplePoints(sampleMesh, 0, pointcloud); |
|
else |
|
SamplePoints(ROUND2INT<unsigned>(-sampleMesh), pointcloud); |
|
} else { |
|
// create the point cloud containing all vertices |
|
for (const Vertex& X: vertices) |
|
pointcloud.points.emplace_back(X); |
|
} |
|
return pointcloud.EstimateGroundPlane(images, planeThreshold, fileExportPlane); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// computes the centroid of the given mesh face |
|
Mesh::Vertex Mesh::ComputeCentroid(FIndex idxFace) const |
|
{ |
|
const Face& face = faces[idxFace]; |
|
return (vertices[face[0]] + vertices[face[1]] + vertices[face[2]]) * (Type(1)/Type(3)); |
|
} |
|
|
|
// computes the area of the given mesh face |
|
Mesh::Type Mesh::ComputeArea(FIndex idxFace) const |
|
{ |
|
const Face& face = faces[idxFace]; |
|
return ComputeTriangleArea(vertices[face[0]], vertices[face[1]], vertices[face[2]]); |
|
} |
|
|
|
// computes the area of the mesh surface as the sum of the signed areas of its faces |
|
REAL Mesh::ComputeArea() const |
|
{ |
|
REAL area(0); |
|
for (const Face& face: faces) |
|
area += ComputeTriangleArea(vertices[face[0]], vertices[face[1]], vertices[face[2]]); |
|
return area; |
|
} |
|
|
|
// computes the signed volume of the domain bounded by the mesh surface |
|
// (note: valid only for closed and orientable manifolds) |
|
REAL Mesh::ComputeVolume() const |
|
{ |
|
REAL volume(0); |
|
for (const Face& face: faces) |
|
volume += ComputeTriangleVolume(vertices[face[0]], vertices[face[1]], vertices[face[2]]); |
|
return volume; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// project mesh to the given camera plane |
|
void Mesh::SamplePoints(unsigned numberOfPoints, PointCloud& pointcloud) const |
|
{ |
|
// total mesh surface |
|
const REAL area(ComputeArea()); |
|
if (area < ZEROTOLERANCE<float>()) { |
|
pointcloud.Release(); |
|
return; |
|
} |
|
const REAL samplingDensity(numberOfPoints / area); |
|
return SamplePoints(samplingDensity, numberOfPoints, pointcloud); |
|
} |
|
void Mesh::SamplePoints(REAL samplingDensity, PointCloud& pointcloud) const |
|
{ |
|
// compute the total area to deduce the number of points |
|
const REAL area(ComputeArea()); |
|
const unsigned theoreticNumberOfPoints(CEIL2INT<unsigned>(area * samplingDensity)); |
|
return SamplePoints(samplingDensity, theoreticNumberOfPoints, pointcloud); |
|
} |
|
void Mesh::SamplePoints(REAL samplingDensity, unsigned mumPointsTheoretic, PointCloud& pointcloud) const |
|
{ |
|
ASSERT(!IsEmpty()); |
|
pointcloud.Release(); |
|
if (mumPointsTheoretic > 0) { |
|
pointcloud.points.reserve(mumPointsTheoretic); |
|
if (HasTexture()) |
|
pointcloud.colors.reserve(mumPointsTheoretic); |
|
} |
|
|
|
// for each triangle |
|
std::mt19937 rnd((std::random_device())()); |
|
std::uniform_real_distribution<REAL> dist(0,1); |
|
FOREACH(idxFace, faces) { |
|
const Face& face = faces[idxFace]; |
|
|
|
// vertices (OAB) |
|
const Vertex& O = vertices[face[0]]; |
|
const Vertex& A = vertices[face[1]]; |
|
const Vertex& B = vertices[face[2]]; |
|
|
|
// edges (OA and OB) |
|
const Vertex u(A - O); |
|
const Vertex v(B - O); |
|
|
|
// compute triangle area |
|
const REAL area(norm(u.cross(v)) * REAL(0.5)); |
|
|
|
// deduce the number of points to generate on this face |
|
const REAL fPointsToAdd(area*samplingDensity); |
|
unsigned pointsToAdd(static_cast<unsigned>(fPointsToAdd)); |
|
|
|
// take care of the remaining fractional part; |
|
// add a point with the same probability as its (relative) area |
|
const REAL fracPart(fPointsToAdd - static_cast<REAL>(pointsToAdd)); |
|
if (dist(rnd) <= fracPart) |
|
pointsToAdd++; |
|
|
|
for (unsigned i = 0; i < pointsToAdd; ++i) { |
|
// generate random points as in: |
|
// "Generating random points in triangles", Greg Turk; |
|
// in A. S. Glassner, editor, Graphics Gems, pages 24-28. Academic Press, 1990 |
|
REAL x(dist(rnd)); |
|
REAL y(dist(rnd)); |
|
|
|
// test if the generated point lies on the right side of (AB) |
|
if (x + y > REAL(1)) { |
|
x = REAL(1) - x; |
|
y = REAL(1) - y; |
|
} |
|
|
|
// compute position |
|
pointcloud.points.emplace_back(O + static_cast<Vertex::Type>(x)*u + static_cast<Vertex::Type>(y)*v); |
|
|
|
if (HasTexture()) { |
|
// compute color |
|
const FIndex idxTexCoord(idxFace*3); |
|
const TexCoord& TO = faceTexcoords[idxTexCoord+0]; |
|
const TexCoord& TA = faceTexcoords[idxTexCoord+1]; |
|
const TexCoord& TB = faceTexcoords[idxTexCoord+2]; |
|
const TexIndex& TI = faceTexindices[idxFace]; |
|
const TexCoord xt(TO + static_cast<TexCoord::Type>(x)*(TA - TO) + static_cast<TexCoord::Type>(y)*(TB - TO)); |
|
pointcloud.colors.emplace_back(texturesDiffuse[TI].sampleSafe(xt)); |
|
} |
|
} |
|
} |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// project mesh to the given camera plane |
|
void Mesh::Project(const Camera& camera, DepthMap& depthMap) const |
|
{ |
|
struct RasterMesh : TRasterMesh<RasterMesh> { |
|
typedef TRasterMesh<RasterMesh> Base; |
|
RasterMesh(const VertexArr& _vertices, const Camera& _camera, DepthMap& _depthMap) |
|
: Base(_vertices, _camera, _depthMap) {} |
|
}; |
|
RasterMesh rasterer(vertices, camera, depthMap); |
|
RasterMesh::Triangle triangle; |
|
RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); |
|
rasterer.Clear(); |
|
for (const Face& facet: faces) |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
void Mesh::Project(const Camera& camera, DepthMap& depthMap, Image8U3& image) const |
|
{ |
|
ASSERT(!faceTexcoords.empty() && !texturesDiffuse.empty()); |
|
struct RasterMesh : TRasterMesh<RasterMesh> { |
|
typedef TRasterMesh<RasterMesh> Base; |
|
const Mesh& mesh; |
|
Image8U3& image; |
|
FIndex idxFaceTex; |
|
TexCoord xt; |
|
RasterMesh(const Mesh& _mesh, const Camera& _camera, DepthMap& _depthMap, Image8U3& _image) |
|
: Base(_mesh.vertices, _camera, _depthMap), mesh(_mesh), image(_image) {} |
|
inline void Clear() { |
|
Base::Clear(); |
|
image.memset(0); |
|
} |
|
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; |
|
xt = mesh.faceTexcoords[idxFaceTex+0] * pbary[0]; |
|
xt += mesh.faceTexcoords[idxFaceTex+1] * pbary[1]; |
|
xt += mesh.faceTexcoords[idxFaceTex+2] * pbary[2]; |
|
const auto texIdx = mesh.faceTexindices[idxFaceTex / 3]; |
|
image(pt) = mesh.texturesDiffuse[texIdx].sampleSafe(xt); |
|
} |
|
} |
|
}; |
|
if (image.size() != depthMap.size()) |
|
image.create(depthMap.size()); |
|
RasterMesh rasterer(*this, camera, depthMap, image); |
|
RasterMesh::Triangle triangle; |
|
RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); |
|
rasterer.Clear(); |
|
FOREACH(idxFace, faces) { |
|
const Face& facet = faces[idxFace]; |
|
rasterer.idxFaceTex = idxFace*3; |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
} |
|
// project mesh to the given camera plane, computing also the normal-map (in camera space) |
|
void Mesh::Project(const Camera& camera, DepthMap& depthMap, NormalMap& normalMap) const |
|
{ |
|
ASSERT(vertexNormals.size() == vertices.size()); |
|
struct RasterMesh : TRasterMesh<RasterMesh> { |
|
typedef TRasterMesh<RasterMesh> Base; |
|
const Mesh& mesh; |
|
NormalMap& normalMap; |
|
const Face::Type* idxVerts; |
|
const Matrix3x3f R; |
|
RasterMesh(const Mesh& _mesh, const Camera& _camera, DepthMap& _depthMap, NormalMap& _normalMap) |
|
: Base(_mesh.vertices, _camera, _depthMap), mesh(_mesh), normalMap(_normalMap), R(camera.R) {} |
|
inline void Clear() { |
|
Base::Clear(); |
|
normalMap.memset(0); |
|
} |
|
inline void Project(const Face& facet, TriangleRasterizer& tr) { |
|
idxVerts = facet.ptr(); |
|
Base::Project(facet, tr); |
|
} |
|
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 == Depth(0) || depth > z) { |
|
depth = z; |
|
normalMap(pt) = R * normalized( |
|
mesh.vertexNormals[idxVerts[0]] * pbary[0]+ |
|
mesh.vertexNormals[idxVerts[1]] * pbary[1]+ |
|
mesh.vertexNormals[idxVerts[2]] * pbary[2] |
|
); |
|
} |
|
} |
|
}; |
|
if (normalMap.size() != depthMap.size()) |
|
normalMap.create(depthMap.size()); |
|
RasterMesh rasterer(*this, camera, depthMap, normalMap); |
|
RasterMesh::Triangle triangle; |
|
RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); |
|
rasterer.Clear(); |
|
// render the entire mesh |
|
for (const Face& facet: faces) |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
// project mesh to the given camera plane using orthographic projection |
|
void Mesh::ProjectOrtho(const Camera& camera, DepthMap& depthMap) const |
|
{ |
|
struct RasterMesh : TRasterMesh<RasterMesh> { |
|
typedef TRasterMesh<RasterMesh> Base; |
|
RasterMesh(const VertexArr& _vertices, const Camera& _camera, DepthMap& _depthMap) |
|
: Base(_vertices, _camera, _depthMap) {} |
|
inline bool ProjectVertex(const Mesh::Vertex& pt, int v, Triangle& t) { |
|
return (t.ptc[v] = camera.TransformPointW2C(Cast<REAL>(pt))).z > 0 && |
|
depthMap.isInsideWithBorder<float,3>(t.pti[v] = camera.TransformPointOrthoC2I(t.ptc[v])); |
|
} |
|
void Raster(const ImageRef& pt, const Triangle& t, const Point3f& bary) { |
|
const Depth z(ComputeDepth(t, bary)); |
|
ASSERT(z > Depth(0)); |
|
Depth& depth = depthMap(pt); |
|
if (depth == 0 || depth > z) |
|
depth = z; |
|
} |
|
}; |
|
RasterMesh rasterer(vertices, camera, depthMap); |
|
RasterMesh::Triangle triangle; |
|
RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); |
|
rasterer.Clear(); |
|
for (const Face& facet: faces) |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
void Mesh::ProjectOrtho(const Camera& camera, DepthMap& depthMap, Image8U3& image) const |
|
{ |
|
ASSERT(!faceTexcoords.empty() && !texturesDiffuse.empty()); |
|
struct RasterMesh : TRasterMesh<RasterMesh> { |
|
typedef TRasterMesh<RasterMesh> Base; |
|
const Mesh& mesh; |
|
Image8U3& image; |
|
FIndex idxFaceTex; |
|
TexCoord xt; |
|
RasterMesh(const Mesh& _mesh, const Camera& _camera, DepthMap& _depthMap, Image8U3& _image) |
|
: Base(_mesh.vertices, _camera, _depthMap), mesh(_mesh), image(_image) {} |
|
inline void Clear() { |
|
Base::Clear(); |
|
image.memset(0); |
|
} |
|
inline bool ProjectVertex(const Mesh::Vertex& pt, int v, Triangle& t) { |
|
return (t.ptc[v] = camera.TransformPointW2C(Cast<REAL>(pt))).z > 0 && |
|
depthMap.isInsideWithBorder<float,3>(t.pti[v] = camera.TransformPointOrthoC2I(t.ptc[v])); |
|
} |
|
void Raster(const ImageRef& pt, const Triangle& t, const Point3f& bary) { |
|
const Depth z(ComputeDepth(t, bary)); |
|
ASSERT(z > Depth(0)); |
|
Depth& depth = depthMap(pt); |
|
if (depth == 0 || depth > z) { |
|
depth = z; |
|
xt = mesh.faceTexcoords[idxFaceTex+0] * bary[0]; |
|
xt += mesh.faceTexcoords[idxFaceTex+1] * bary[1]; |
|
xt += mesh.faceTexcoords[idxFaceTex+2] * bary[2]; |
|
auto texIdx = mesh.faceTexindices[idxFaceTex / 3]; |
|
image(pt) = mesh.texturesDiffuse[texIdx].sampleSafe(xt); |
|
} |
|
} |
|
}; |
|
if (image.size() != depthMap.size()) |
|
image.create(depthMap.size()); |
|
RasterMesh rasterer(*this, camera, depthMap, image); |
|
RasterMesh::Triangle triangle; |
|
RasterMesh::TriangleRasterizer triangleRasterizer(triangle, rasterer); |
|
rasterer.Clear(); |
|
FOREACH(idxFace, faces) { |
|
const Face& facet = faces[idxFace]; |
|
rasterer.idxFaceTex = idxFace*3; |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
} |
|
// assuming the mesh is properly oriented, ortho-project it to a camera looking from top to down |
|
void Mesh::ProjectOrthoTopDown(unsigned resolution, Image8U3& image, Image8U& mask, Point3& center) const |
|
{ |
|
ASSERT(!IsEmpty() && !texturesDiffuse.empty()); |
|
// initialize camera |
|
const AABB3f box(vertices.data(), vertices.size()); |
|
const Point3 size(Vertex(box.GetSize())*1.01f/*border*/); |
|
center = Vertex(box.GetCenter()); |
|
Camera camera; |
|
camera.R.SetFromDirUp(Vec3(Point3(0,0,-1)), Vec3(Point3(0,1,0))); |
|
camera.C = center; |
|
camera.C.z += size.z; |
|
camera.K = KMatrix::IDENTITY; |
|
if (size.x > size.y) { |
|
image.create(CEIL2INT(size.y*(resolution-1)/size.x), (int)resolution); |
|
camera.K(0,0) = camera.K(1,1) = (resolution-1)/size.x; |
|
} else { |
|
image.create((int)resolution, CEIL2INT(size.x*(resolution-1)/size.y)); |
|
camera.K(0,0) = camera.K(1,1) = (resolution-1)/size.y; |
|
} |
|
camera.K(0,2) = (REAL)(image.width()-1)/2; |
|
camera.K(1,2) = (REAL)(image.height()-1)/2; |
|
// project mesh |
|
DepthMap depthMap(image.size()); |
|
ProjectOrtho(camera, depthMap, image); |
|
// create mask for the valid image pixels |
|
if (mask.size() != depthMap.size()) |
|
mask.create(depthMap.size()); |
|
for (int r=0; r<mask.rows; ++r) |
|
for (int c=0; c<mask.cols; ++c) |
|
mask(r,c) = depthMap(r,c) > 0 ? 255 : 0; |
|
// compute 3D coordinates for the image center |
|
const ImageRef xCenter(image.width()/2, image.height()/2); |
|
const Depth depthCenter(depthMap(xCenter)); |
|
center = camera.TransformPointI2W(Point3(xCenter.x, xCenter.y, depthCenter > 0 ? depthCenter : camera.C.z-center.z)); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// split mesh into sub-meshes such that each has maxArea |
|
bool Mesh::Split(FacesChunkArr& chunks, float maxArea) |
|
{ |
|
TD_TIMER_STARTD(); |
|
Octree octree; |
|
FacesInserter::CreateOctree(octree, *this); |
|
FloatArr areas(faces.size()); |
|
FOREACH(i, faces) |
|
areas[i] = ComputeArea(i); |
|
struct AreaInserter { |
|
const FloatArr& areas; |
|
float area; |
|
inline void operator() (const Octree::IDX_TYPE* indices, Octree::SIZE_TYPE size) { |
|
FOREACHRAWPTR(pIdx, indices, size) |
|
area += areas[*pIdx]; |
|
} |
|
inline float PopArea() { |
|
const float a(area); |
|
area = 0; |
|
return a; |
|
} |
|
} areaEstimator{areas, 0.f}; |
|
struct ChunkInserter { |
|
const Octree& octree; |
|
FacesChunkArr& chunks; |
|
void operator() (const Octree::CELL_TYPE& parentCell, Octree::Type parentRadius, const UnsignedArr& children) { |
|
ASSERT(!children.empty()); |
|
FaceChunk& chunk = chunks.AddEmpty(); |
|
struct Inserter { |
|
FaceIdxArr& faces; |
|
inline void operator() (const Octree::IDX_TYPE* indices, Octree::SIZE_TYPE size) { |
|
faces.Join(indices, size); |
|
} |
|
} inserter{chunk.faces}; |
|
if (children.size() == 1) { |
|
octree.CollectCells(parentCell.GetChild(children.front()), inserter); |
|
chunk.box = parentCell.GetChildAabb(children.front(), parentRadius); |
|
} else { |
|
chunk.box.Reset(); |
|
for (unsigned c: children) { |
|
octree.CollectCells(parentCell.GetChild(c), inserter); |
|
chunk.box.Insert(parentCell.GetChildAabb(c, parentRadius)); |
|
} |
|
} |
|
if (chunk.faces.empty()) |
|
chunks.RemoveLast(); |
|
} |
|
} chunkInserter{octree, chunks}; |
|
octree.SplitVolume(maxArea, areaEstimator, chunkInserter); |
|
if (chunks.size() < 2) |
|
return false; |
|
DEBUG_EXTRA("Mesh split (%g max-area): %u chunks (%s)", maxArea, chunks.size(), TD_TIMER_GET_FMT().c_str()); |
|
return true; |
|
} // Split |
|
/*----------------------------------------------------------------*/ |
|
|
|
// extract the sub-mesh corresponding to the given chunk of faces |
|
Mesh Mesh::SubMesh(const FaceIdxArr& chunk) const |
|
{ |
|
ASSERT(!chunk.empty()); |
|
Mesh mesh; |
|
mesh.vertices = vertices; |
|
mesh.faces.reserve(chunk.size()); |
|
if (!faceTexcoords.empty()) |
|
mesh.faceTexcoords.reserve(chunk.size()*3); |
|
for (FIndex idxFace: chunk) { |
|
mesh.faces.emplace_back(faces[idxFace]); |
|
if (!faceTexcoords.empty()) { |
|
const TexCoord* tri = faceTexcoords.data()+idxFace*3; |
|
for (int i = 0; i < 3; ++i) |
|
mesh.faceTexcoords.emplace_back(tri[i]); |
|
} |
|
} |
|
mesh.ListIncidenteFaces(); |
|
mesh.RemoveUnreferencedVertices(); |
|
mesh.FixNonManifold(); |
|
return mesh; |
|
} // SubMesh |
|
/*----------------------------------------------------------------*/ |
|
|
|
// extract one sub-mesh for each texture, i.e. for each value of faceTexindices; |
|
std::vector<Mesh> Mesh::SplitMeshPerTextureBlob() const { |
|
|
|
ASSERT(HasTexture()); |
|
if (texturesDiffuse.size() == 1) |
|
return {*this}; |
|
ASSERT(faceTexindices.size() == faces.size()); |
|
std::vector<Mesh> submeshes; |
|
submeshes.reserve(texturesDiffuse.size()); |
|
FOREACH(texId, texturesDiffuse) { |
|
FaceIdxArr chunk; |
|
FOREACH(idxFace, faceTexindices) { |
|
if (faceTexindices[idxFace] == texId) |
|
chunk.push_back(idxFace); |
|
} |
|
Mesh submesh = SubMesh(chunk); |
|
submesh.texturesDiffuse.emplace_back(texturesDiffuse[texId]); |
|
submeshes.emplace_back(std::move(submesh)); |
|
} |
|
return submeshes; |
|
} |
|
|
|
|
|
// transfer the texture of this mesh to the new mesh; |
|
// the two meshes should be aligned and the new mesh to have UV-coordinates |
|
#if USE_MESH_INT == USE_MESH_BVH |
|
struct FaceBox { |
|
Eigen::AlignedBox3f box; |
|
Mesh::FIndex idxFace; |
|
}; |
|
inline Eigen::AlignedBox3f bounding_box(const FaceBox& faceBox) { |
|
return faceBox.box; |
|
} |
|
#endif |
|
bool Mesh::TransferTexture(Mesh& mesh, const FaceIdxArr& faceSubsetIndices, unsigned borderSize, unsigned textureSize) |
|
{ |
|
ASSERT(HasTexture() && mesh.HasTextureCoordinates()); |
|
if (mesh.texturesDiffuse.empty()) { |
|
// create the texture at specified resolution and |
|
// scale the UV-coordinates to the new resolution (assuming normalized coordinates) |
|
mesh.texturesDiffuse.emplace_back(textureSize, textureSize).memset(0); |
|
for (TexCoord& tex: mesh.faceTexcoords) { |
|
ASSERT(tex.x <= 1 && tex.y <= 1); |
|
tex *= (Mesh::Type)textureSize; |
|
} |
|
} |
|
Image8U mask(mesh.texturesDiffuse.back().size(), uint8_t(255)); |
|
const FIndex num_faces(faceSubsetIndices.empty() ? mesh.faces.size() : faceSubsetIndices.size()); |
|
if (vertices == mesh.vertices && faces == mesh.faces) { |
|
// the two meshes are identical, only the texture coordinates are different; |
|
// directly transfer the texture onto the new coordinates |
|
#ifdef MESH_USE_OPENMP |
|
#pragma omp parallel for schedule(dynamic) |
|
for (int_t i=0; i<(int_t)num_faces; ++i) { |
|
const FIndex idx((FIndex)i); |
|
#else |
|
FOREACHRAW(idx, num_faces) { |
|
#endif |
|
const FIndex idxFace(faceSubsetIndices.empty() ? idx : faceSubsetIndices[idx]); |
|
struct RasterTriangle { |
|
const Mesh& meshRef; |
|
Mesh& meshTrg; |
|
Image8U& mask; |
|
const TexCoord* tri; |
|
const TexIndex texId; |
|
inline cv::Size Size() const { return meshTrg.texturesDiffuse[0].size(); } |
|
inline void operator()(const ImageRef& pt, const Point3f& bary) { |
|
ASSERT(meshTrg.texturesDiffuse[texId].isInside(pt)); |
|
const TexCoord x(tri[0]*bary.x + tri[1]*bary.y + tri[2]*bary.z); |
|
const Pixel8U color(meshRef.texturesDiffuse[texId].sample(x)); |
|
meshTrg.texturesDiffuse[texId](pt) = color; |
|
mask(pt) = 0; |
|
} |
|
} data{*this, mesh, mask, faceTexcoords.data()+idxFace*3, mesh.faceTexindices[idxFace]}; |
|
// render triangle and for each pixel interpolate the color |
|
// from the triangle corners using barycentric coordinates |
|
const TexCoord* tri = mesh.faceTexcoords.data()+idxFace*3; |
|
Image8U::RasterizeTriangleBary<TexCoord::Type,RasterTriangle,false>(tri[0], tri[1], tri[2], data); |
|
} |
|
} else { |
|
// the two meshes are different, transfer the texture by finding the closest point |
|
// on the two surfaces |
|
if (vertexFaces.size() != vertices.size()) |
|
ListIncidenteFaces(); |
|
if (mesh.vertexNormals.size() != mesh.vertices.size()) |
|
mesh.ComputeNormalVertices(); |
|
#if USE_MESH_INT == USE_MESH_BVH |
|
std::vector<FaceBox> boxes; |
|
boxes.reserve(faces.size()); |
|
FOREACH(idxFace, faces) |
|
boxes.emplace_back([this](FIndex idxFace) { |
|
const Face& face = faces[idxFace]; |
|
Eigen::AlignedBox3f box; |
|
box.extend<Eigen::Vector3f>(vertices[face[0]]); |
|
box.extend<Eigen::Vector3f>(vertices[face[1]]); |
|
box.extend<Eigen::Vector3f>(vertices[face[2]]); |
|
return FaceBox{box, idxFace}; |
|
} (idxFace)); |
|
typedef Eigen::KdBVH<Type,3,FaceBox> BVH; |
|
BVH tree(boxes.begin(), boxes.end()); |
|
#endif |
|
struct IntersectRayMesh { |
|
const Mesh& mesh; |
|
const Ray3f& ray; |
|
IndexDist pick; |
|
IntersectRayMesh(const Mesh& _mesh, const Ray3f& _ray) |
|
: mesh(_mesh), ray(_ray) { |
|
#if USE_MESH_INT == USE_MESH_BF |
|
FOREACH(idxFace, mesh.faces) |
|
IntersectsRayFace(idxFace); |
|
#endif |
|
} |
|
inline void IntersectsRayFace(FIndex idxFace) { |
|
const Face& face = mesh.faces[idxFace]; |
|
Type dist; |
|
if (ray.Intersects<false>(Triangle3f( |
|
mesh.vertices[face.x], mesh.vertices[face.y], mesh.vertices[face.z]), &dist)) { |
|
if (pick.dist > ABS(dist)) { |
|
pick.dist = ABS(dist); |
|
pick.idx = idxFace; |
|
} |
|
} |
|
} |
|
#if USE_MESH_INT == USE_MESH_BVH |
|
inline bool intersectVolume(const BVH::Volume &volume) { |
|
return ray.Intersects(AABB3f(volume.min(), volume.max())); |
|
} |
|
inline bool intersectObject(const BVH::Object &object) { |
|
IntersectsRayFace(object.idxFace); |
|
return false; |
|
} |
|
#endif |
|
}; |
|
#if USE_MESH_INT == USE_MESH_BF || USE_MESH_INT == USE_MESH_BVH |
|
#elif USE_MESH_INT == USE_MESH_OCTREE |
|
const Octree octree(vertices, [](Octree::IDX_TYPE size, Octree::Type /*radius*/) { |
|
return size > 8; |
|
}); |
|
struct OctreeIntersectRayMesh : IntersectRayMesh { |
|
OctreeIntersectRayMesh(const Octree& octree, const Mesh& _mesh, const Ray3f& _ray) |
|
: IntersectRayMesh(_mesh, _ray) { |
|
octree.Collect(*this, *this); |
|
} |
|
inline bool Intersects(const Octree::POINT_TYPE& center, Octree::Type radius) const { |
|
return ray.Intersects(AABB3f(center, radius)); |
|
} |
|
void operator() (const Octree::IDX_TYPE* idices, Octree::IDX_TYPE size) { |
|
// store all contained faces only once |
|
std::unordered_set<FIndex> set; |
|
FOREACHRAWPTR(pIdx, idices, size) { |
|
const VIndex idxVertex((VIndex)*pIdx); |
|
const FaceIdxArr& faces = mesh.vertexFaces[idxVertex]; |
|
set.insert(faces.begin(), faces.end()); |
|
} |
|
// test face intersection and keep the closest |
|
for (FIndex idxFace : set) |
|
IntersectsRayFace(idxFace); |
|
} |
|
}; |
|
#endif |
|
#ifdef MESH_USE_OPENMP |
|
#pragma omp parallel for schedule(dynamic) |
|
for (int_t i=0; i<(int_t)num_faces; ++i) { |
|
const FIndex idx((FIndex)i); |
|
#else |
|
FOREACHRAW(idx, num_faces) { |
|
#endif |
|
const FIndex idxFace(faceSubsetIndices.empty() ? idx : faceSubsetIndices[idx]); |
|
struct RasterTriangle { |
|
#if USE_MESH_INT == USE_MESH_OCTREE |
|
const Octree& octree; |
|
#elif USE_MESH_INT == USE_MESH_BVH |
|
BVH& tree; |
|
#endif |
|
const Mesh& meshRef; |
|
Mesh& meshTrg; |
|
Image8U& mask; |
|
const Face& face; |
|
const TexIndex texId; |
|
inline cv::Size Size() const { return meshTrg.texturesDiffuse.back().size(); } |
|
inline void operator()(const ImageRef& pt, const Point3f& bary) { |
|
ASSERT(meshTrg.texturesDiffuse[texId].isInside(pt)); |
|
const Vertex X(meshTrg.vertices[face.x]*bary.x |
|
+ meshTrg.vertices[face.y]*bary.y |
|
+ meshTrg.vertices[face.z]*bary.z); |
|
const Normal N(normalized(meshTrg.vertexNormals[face.x]*bary.x |
|
+ meshTrg.vertexNormals[face.y]*bary.y |
|
+ meshTrg.vertexNormals[face.z]*bary.z)); |
|
const Ray3f ray(X, N); |
|
#if USE_MESH_INT == USE_MESH_BF |
|
const IntersectRayMesh intRay(meshRef, ray); |
|
#elif USE_MESH_INT == USE_MESH_BVH |
|
IntersectRayMesh intRay(meshRef, ray); |
|
Eigen::BVIntersect(tree, intRay); |
|
#else |
|
const OctreeIntersectRayMesh intRay(octree, meshRef, ray); |
|
#endif |
|
if (intRay.pick.IsValid()) { |
|
const FIndex refIdxFace((FIndex)intRay.pick.idx); |
|
const Face& refFace = meshRef.faces[refIdxFace]; |
|
const Vertex refX(ray.GetPoint((Type)intRay.pick.dist)); |
|
const Vertex baryRef(CorrectBarycentricCoordinates(BarycentricCoordinatesUV(meshRef.vertices[refFace[0]], meshRef.vertices[refFace[1]], meshRef.vertices[refFace[2]], refX))); |
|
const TexCoord* tri = meshRef.faceTexcoords.data()+refIdxFace*3; |
|
const TexCoord x(tri[0]*baryRef.x + tri[1]*baryRef.y + tri[2]*baryRef.z); |
|
const Pixel8U color(meshRef.texturesDiffuse[texId].sample(x)); |
|
meshTrg.texturesDiffuse.back()(pt) = color; |
|
mask(pt) = 0; |
|
} |
|
} |
|
#if USE_MESH_INT == USE_MESH_BF |
|
} data{*this, mesh, mask, mesh.faces[idxFace], mesh.GetFaceTextureIndex(idxFace)}; |
|
#elif USE_MESH_INT == USE_MESH_BVH |
|
} data{tree, *this, mesh, mask, mesh.faces[idxFace], mesh.GetFaceTextureIndex(idxFace)}; |
|
#else |
|
} data{octree, *this, mesh, mask, mesh.faces[idxFace], mesh.GetFaceTextureIndex(idxFace)}; |
|
#endif |
|
// render triangle and for each pixel interpolate the color |
|
// from the triangle corners using barycentric coordinates |
|
const TexCoord* tri = mesh.faceTexcoords.data()+idxFace*3; |
|
Image8U::RasterizeTriangleBary<TexCoord::Type,RasterTriangle,false>(tri[0], tri[1], tri[2], data); |
|
} |
|
} |
|
// fill border |
|
if (borderSize > 0) { |
|
ASSERT(mask.size().area() == mesh.texturesDiffuse[0].size().area()); |
|
const int border(static_cast<int>(borderSize)); |
|
CLISTDEF0(int) idx_valid_pixels; |
|
idx_valid_pixels.push_back(-1); |
|
ASSERT(mask.isContinuous()); |
|
const int size(mask.size().area()); |
|
for (int i=0; i<size; ++i) |
|
if (!mask(i)) |
|
idx_valid_pixels.push_back(i); |
|
Image32F dists; cv::Mat_<int32_t> labels; |
|
cv::distanceTransform(mask, dists, labels, cv::DIST_L1, 3, cv::DIST_LABEL_PIXEL); |
|
ASSERT(mesh.texturesDiffuse[0].isContinuous()); |
|
for (int i=0; i<size; ++i) { |
|
const int dist = static_cast<int>(dists(i)); |
|
if (dist > 0 && dist <= border) { |
|
const int label(labels(i)); |
|
const int idx_closest_pixel(idx_valid_pixels[label]); |
|
mesh.texturesDiffuse[0](i) = mesh.texturesDiffuse[0](idx_closest_pixel); |
|
} |
|
} |
|
} |
|
return true; |
|
} // TransferTexture |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
#ifdef _USE_CUDA |
|
CUDA::KernelRT Mesh::kernelComputeFaceNormal; |
|
|
|
bool Mesh::InitKernels(int device) |
|
{ |
|
// initialize CUDA device if needed |
|
if (CUDA::devices.IsEmpty() && CUDA::initDevice(device) != CUDA_SUCCESS) |
|
return false; |
|
|
|
// initialize CUDA kernels |
|
if (!kernelComputeFaceNormal.IsValid()) { |
|
// kernel used to compute face normal, given the array of face vertices and vertex positions |
|
STATIC_ASSERT(sizeof(Vertex) == sizeof(float)*3); |
|
STATIC_ASSERT(sizeof(Face) == sizeof(VIndex)*3 && sizeof(VIndex) == sizeof(uint32_t)); |
|
STATIC_ASSERT(sizeof(Normal) == sizeof(float)*3); |
|
#define FUNC "ComputeFaceNormal" |
|
LPCSTR const szKernel = |
|
".version 3.2\n" |
|
".target sm_20\n" |
|
".address_size 64\n" |
|
"\n" |
|
".visible .entry " FUNC "(\n" |
|
" .param .u64 .ptr param_1, // array vertices (float*3 * numVertices)\n" |
|
" .param .u64 .ptr param_2, // array faces (uint32_t*3 * numFaces)\n" |
|
" .param .u64 .ptr param_3, // array normals (float*3 * numFaces) [out]\n" |
|
" .param .u32 param_4 // numFaces = numNormals (uint32_t)\n" |
|
")\n" |
|
"{\n" |
|
" .reg .f32 %f<32>;\n" |
|
" .reg .pred %p<2>;\n" |
|
" .reg .u32 %r<17>;\n" |
|
" .reg .u64 %rl<18>;\n" |
|
"\n" |
|
" ld.param.u64 %rl4, [param_1];\n" |
|
" ld.param.u64 %rl5, [param_2];\n" |
|
" ld.param.u64 %rl6, [param_3];\n" |
|
" ld.param.u32 %r2, [param_4];\n" |
|
" cvta.to.global.u64 %rl1, %rl6;\n" |
|
" cvta.to.global.u64 %rl2, %rl4;\n" |
|
" cvta.to.global.u64 %rl3, %rl5;\n" |
|
" mov.u32 %r3, %ntid.x;\n" |
|
" mov.u32 %r4, %ctaid.x;\n" |
|
" mov.u32 %r5, %tid.x;\n" |
|
" mad.lo.u32 %r1, %r3, %r4, %r5;\n" |
|
" setp.ge.u32 %p1, %r1, %r2;\n" |
|
" @%p1 bra BB00_1;\n" |
|
"\n" |
|
" mul.lo.u32 %r6, %r1, 3;\n" |
|
" mul.wide.u32 %rl7, %r6, 4;\n" |
|
" add.u64 %rl8, %rl3, %rl7;\n" |
|
" ld.global.u32 %r8, [%rl8];\n" |
|
" mul.lo.u32 %r10, %r8, 3;\n" |
|
" mul.wide.u32 %rl11, %r10, 4;\n" |
|
" add.u64 %rl12, %rl2, %rl11;\n" |
|
" ld.global.u32 %r11, [%rl8+4];\n" |
|
" mul.lo.u32 %r13, %r11, 3;\n" |
|
" mul.wide.u32 %rl13, %r13, 4;\n" |
|
" add.u64 %rl14, %rl2, %rl13;\n" |
|
" ld.global.u32 %r14, [%rl8+8];\n" |
|
" mul.lo.u32 %r16, %r14, 3;\n" |
|
" mul.wide.u32 %rl15, %r16, 4;\n" |
|
" add.u64 %rl16, %rl2, %rl15;\n" |
|
"\n" |
|
" ld.global.f32 %f1, [%rl14];\n" |
|
" ld.global.f32 %f2, [%rl12];\n" |
|
" sub.f32 %f3, %f1, %f2;\n" |
|
" ld.global.f32 %f4, [%rl14+4];\n" |
|
" ld.global.f32 %f5, [%rl12+4];\n" |
|
" sub.f32 %f6, %f4, %f5;\n" |
|
" ld.global.f32 %f7, [%rl14+8];\n" |
|
" ld.global.f32 %f8, [%rl12+8];\n" |
|
" sub.f32 %f9, %f7, %f8;\n" |
|
" ld.global.f32 %f10, [%rl16];\n" |
|
" sub.f32 %f11, %f10, %f2;\n" |
|
" ld.global.f32 %f12, [%rl16+4];\n" |
|
" sub.f32 %f13, %f12, %f5;\n" |
|
" ld.global.f32 %f14, [%rl16+8];\n" |
|
" sub.f32 %f15, %f14, %f8;\n" |
|
"\n" |
|
" mul.f32 %f16, %f6, %f15;\n" |
|
" neg.f32 %f17, %f9;\n" |
|
" fma.rn.f32 %f18, %f17, %f13, %f16;\n" |
|
" mul.f32 %f19, %f9, %f11;\n" |
|
" neg.f32 %f20, %f3;\n" |
|
" fma.rn.f32 %f21, %f20, %f15, %f19;\n" |
|
" mul.f32 %f22, %f3, %f13;\n" |
|
" neg.f32 %f23, %f6;\n" |
|
"\n" |
|
" fma.rn.f32 %f24, %f23, %f11, %f22;\n" |
|
" mul.f32 %f25, %f21, %f21;\n" |
|
" fma.rn.f32 %f26, %f18, %f18, %f25;\n" |
|
" fma.rn.f32 %f27, %f24, %f24, %f26;\n" |
|
"\n" |
|
" sqrt.rn.f32 %f28, %f27;\n" |
|
" div.rn.f32 %f29, %f18, %f28;\n" |
|
" div.rn.f32 %f30, %f21, %f28;\n" |
|
" div.rn.f32 %f31, %f24, %f28;\n" |
|
"\n" |
|
" add.u64 %rl17, %rl1, %rl7;\n" |
|
" st.global.f32 [%rl17], %f29;\n" |
|
" st.global.f32 [%rl17+4], %f30;\n" |
|
" st.global.f32 [%rl17+8], %f31;\n" |
|
"\n" |
|
" BB00_1:\n" |
|
" ret;\n" |
|
"}\n"; |
|
if (kernelComputeFaceNormal.Reset(szKernel, FUNC) != CUDA_SUCCESS) |
|
return false; |
|
ASSERT(kernelComputeFaceNormal.IsValid()); |
|
#undef FUNC |
|
} |
|
|
|
return true; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
#endif |
|
|
|
|
|
#ifdef _USE_OPENMP |
|
// test mesh projection on the image using multi-threaded and single-threaded rasterization |
|
bool MVS::TestMeshProjectionMT(const Mesh& mesh, const Image& image) { |
|
// used to render the mesh |
|
typedef TImage<cuint32_t> FaceMap; |
|
struct RasterMesh : TRasterMesh<RasterMesh> { |
|
typedef TRasterMesh<RasterMesh> Base; |
|
FaceMap& faceMap; |
|
RasterMesh(const Mesh::VertexArr& _vertices, const Camera& _camera, DepthMap& _depthMap, FaceMap& _faceMap) |
|
: Base(_vertices, _camera, _depthMap), faceMap(_faceMap) {} |
|
void Clear() { |
|
Base::Clear(); |
|
faceMap.memset((uint8_t)NO_ID); |
|
} |
|
void Raster(const ImageRef& pt, const Triangle& t, const Point3f& bary, Mesh::FIndex idxFace) { |
|
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; |
|
faceMap(pt) = idxFace; |
|
} |
|
} |
|
}; |
|
struct TriangleRasterizer { |
|
RasterMesh* rasterizer; |
|
RasterMesh::Triangle triangle; |
|
Mesh::FIndex idxFace; |
|
inline cv::Size Size() const { |
|
return rasterizer->Size(); |
|
} |
|
inline void operator()(const ImageRef& pt, const Point3f& bary) const { |
|
rasterizer->Raster(pt, triangle, bary, idxFace); |
|
} |
|
}; |
|
// project mesh on the image |
|
DepthMap depthMapMT(image.GetSize()); |
|
FaceMap faceMapMT(image.GetSize()); |
|
{ // multi-threaded rasterization |
|
RasterMesh rasterer(mesh.vertices, image.camera, depthMapMT, faceMapMT); |
|
TriangleRasterizer triangleRasterizer{&rasterer}; |
|
rasterer.Clear(); |
|
#pragma omp parallel for firstprivate(triangleRasterizer) schedule(dynamic) |
|
for (int_t i=0; i<(int_t)mesh.faces.size(); ++i) { |
|
const Mesh::FIndex idxFace = (Mesh::FIndex)i; |
|
const Mesh::Face& facet = mesh.faces[idxFace]; |
|
triangleRasterizer.idxFace = idxFace; |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
} |
|
DepthMap depthMapST(image.GetSize()); |
|
FaceMap faceMapST(image.GetSize()); |
|
{ // single-threaded rasterization |
|
RasterMesh rasterer(mesh.vertices, image.camera, depthMapST, faceMapST); |
|
TriangleRasterizer triangleRasterizer{&rasterer}; |
|
rasterer.Clear(); |
|
FOREACH(idxFace, mesh.faces) { |
|
const Mesh::Face& facet = mesh.faces[idxFace]; |
|
triangleRasterizer.idxFace = idxFace; |
|
rasterer.Project(facet, triangleRasterizer); |
|
} |
|
} |
|
// compare results |
|
unsigned numDiffDepths(0), numDiffFaces(0); |
|
for (int y = 0; y<depthMapST.rows; ++y) { |
|
for (int x = 0; x<depthMapST.cols; ++x) { |
|
const Depth depthMT = depthMapMT(y,x); |
|
const Depth depthST = depthMapST(y,x); |
|
if (depthMT != depthST) |
|
++numDiffDepths; |
|
const cuint32_t faceMT = faceMapMT(y,x); |
|
const cuint32_t faceST = faceMapST(y,x); |
|
if (faceMT != faceST) |
|
++numDiffFaces; |
|
} |
|
} |
|
VERBOSE("Mesh rasterization: %u different depths, %u different faces", numDiffDepths, numDiffFaces); |
|
return numDiffDepths == 0 && numDiffFaces == 0; |
|
} |
|
/*----------------------------------------------------------------*/ |
|
#endif // _USE_OPENMP
|