feat: P2 Alpha Shapes + 网格参数化
CI / Build & Test (push) Failing after 16m49s
CI / Release Build (push) Failing after 38s

- mesh: Alpha Shapes (点云→表面)
- mesh: Tutte 嵌入参数化
- mesh: LSCM 保角参数化
- 新增 4 文件,更新 CMake
This commit is contained in:
ViewDesignEngine
2026-07-23 07:20:01 +00:00
parent c226b60a32
commit 2c4387aba5
5 changed files with 204 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
#pragma once
#include "vde/core/point.h"
#include "vde/mesh/delaunay_3d.h"
#include <vector>
namespace vde::mesh {
/// Alpha Shapes: extract surface from point cloud
/// For alpha → ∞, returns convex hull; for alpha → 0, returns all faces
/// @param alpha Radius threshold: faces with circumradius > alpha are removed
TetrahedronMesh alpha_shapes(const std::vector<Point3D>& points, double alpha);
/// Convert alpha shapes tet mesh to triangle surface
std::pair<std::vector<Point3D>, std::vector<std::array<int,3>>>
alpha_shapes_surface(const TetrahedronMesh& tet_mesh);
} // namespace vde::mesh
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include "vde/mesh/halfedge_mesh.h"
#include "vde/core/point.h"
#include <vector>
namespace vde::mesh {
/// Tutte embedding (harmonic parameterization) for a mesh with fixed boundary
/// Boundary vertices are mapped to a circle; interior vertices are solved via Laplacian
/// Returns per-vertex UV coordinates as Point2D
[[nodiscard]] std::vector<Point2D> tutte_parameterization(const HalfedgeMesh& mesh);
/// LSCM (Least Squares Conformal Maps) — simplified version
[[nodiscard]] std::vector<Point2D> lscm_parameterization(const HalfedgeMesh& mesh);
} // namespace vde::mesh
+2
View File
@@ -63,6 +63,8 @@ add_library(vde_mesh STATIC
mesh/mesh_boolean.cpp
mesh/mesh_quality.cpp
mesh/mesh_repair.cpp
mesh/alpha_shapes.cpp
mesh/mesh_parameterize.cpp
mesh/geodesic.cpp
mesh/mesh_curvature.cpp
mesh/marching_cubes.cpp
+89
View File
@@ -0,0 +1,89 @@
#include "vde/mesh/alpha_shapes.h"
#include <unordered_set>
#include <algorithm>
#include <cmath>
namespace vde::mesh {
namespace {
struct Triangle {
int a, b, c;
bool operator==(const Triangle& o) const {
std::array<int,3> t1={a,b,c}, t2={o.a,o.b,o.c};
std::sort(t1.begin(),t1.end()); std::sort(t2.begin(),t2.end());
return t1==t2;
}
};
struct TriHash {
size_t operator()(const Triangle& t) const {
std::array<int,3> a={t.a,t.b,t.c}; std::sort(a.begin(),a.end());
return (static_cast<uint64_t>(a[0])<<40)^(static_cast<uint64_t>(a[1])<<20)^a[2];
}
};
double circumradius(const Point3D& a, const Point3D& b, const Point3D& c) {
double ab=(a-b).norm(), bc=(b-c).norm(), ca=(c-a).norm();
double s=(ab+bc+ca)*0.5;
double area=std::sqrt(std::max(0.0,s*(s-ab)*(s-bc)*(s-ca)));
if (area<1e-12) return std::numeric_limits<double>::max();
return (ab*bc*ca)/(4.0*area);
}
} // namespace
TetrahedronMesh alpha_shapes(const std::vector<Point3D>& points, double alpha) {
auto tet_mesh = delaunay_3d(points);
// Filter tetrahedra: remove if has face with circumradius > alpha
if (alpha <= 0) return tet_mesh;
std::vector<std::array<int,4>> filtered;
for (const auto& tet : tet_mesh.tetrahedra) {
Point3D a=points[tet[0]], b=points[tet[1]], c=points[tet[2]], d=points[tet[3]];
bool keep = true;
// Check all 4 faces
std::array<std::array<int,3>,4> faces = {{
{tet[0],tet[1],tet[2]},{tet[0],tet[1],tet[3]},
{tet[0],tet[2],tet[3]},{tet[1],tet[2],tet[3]}
}};
for (const auto& f : faces) {
double cr = circumradius(points[f[0]], points[f[1]], points[f[2]]);
if (cr > alpha) { keep = false; break; }
}
if (keep) filtered.push_back(tet);
}
TetrahedronMesh result;
result.vertices = points;
result.tetrahedra = filtered;
return result;
}
std::pair<std::vector<Point3D>, std::vector<std::array<int,3>>>
alpha_shapes_surface(const TetrahedronMesh& tet_mesh) {
// Extract boundary faces (faces that appear exactly once)
std::unordered_map<Triangle, int, TriHash> face_count;
for (const auto& tet : tet_mesh.tetrahedra) {
std::array<Triangle,4> faces = {{
{tet[0],tet[1],tet[2]},{tet[0],tet[1],tet[3]},
{tet[0],tet[2],tet[3]},{tet[1],tet[2],tet[3]}
}};
for (const auto& f : faces) face_count[f]++;
}
std::vector<Point3D> result_verts;
std::vector<std::array<int,3>> result_tris;
for (const auto& [tri, count] : face_count) {
if (count != 1) continue; // Internal face, skip
result_tris.push_back({tri.a, tri.b, tri.c});
}
result_verts = tet_mesh.vertices;
return {result_verts, result_tris};
}
} // namespace vde::mesh
+80
View File
@@ -0,0 +1,80 @@
#include "vde/mesh/mesh_parameterize.h"
#include <Eigen/Sparse>
#include <Eigen/SparseLU>
#include <cmath>
#include <unordered_set>
#include <algorithm>
namespace vde::mesh {
std::vector<Point2D> tutte_parameterization(const HalfedgeMesh& mesh) {
int nv = static_cast<int>(mesh.num_vertices());
std::vector<Point2D> uv(nv, Point2D(0,0));
if (nv < 3) return uv;
// Identify boundary vertices
std::vector<int> boundary;
for (int i = 0; i < nv; ++i)
if (mesh.is_boundary_vertex(i)) boundary.push_back(i);
if (boundary.size() < 3) return uv;
// Map boundary to circle
for (size_t i = 0; i < boundary.size(); ++i) {
double angle = 2.0 * M_PI * i / boundary.size();
uv[boundary[i]] = Point2D(std::cos(angle), std::sin(angle));
}
// Build Laplacian system: L * u = 0, with boundary fixed
using SpMat = Eigen::SparseMatrix<double>;
SpMat L(nv, nv);
std::vector<Eigen::Triplet<double>> triplets;
for (int vi = 0; vi < nv; ++vi) {
if (std::find(boundary.begin(), boundary.end(), vi) != boundary.end()) {
triplets.emplace_back(vi, vi, 1.0);
continue;
}
auto fis = mesh.vertex_faces(vi);
std::unordered_set<int> neighbors;
for (int fi : fis) {
auto vis = mesh.face_vertices(fi);
for (int vj : vis)
if (vj != vi) neighbors.insert(vj);
}
double deg = static_cast<double>(neighbors.size());
if (deg < 1) { triplets.emplace_back(vi, vi, 1.0); continue; }
triplets.emplace_back(vi, vi, deg);
for (int nj : neighbors)
triplets.emplace_back(vi, nj, -1.0);
}
L.setFromTriplets(triplets.begin(), triplets.end());
// Right-hand side
Eigen::VectorXd bx(nv), by(nv);
for (int i = 0; i < nv; ++i) {
bx(i) = uv[i].x();
by(i) = uv[i].y();
}
Eigen::SparseLU<SpMat> solver(L);
if (solver.info() == Eigen::Success) {
Eigen::VectorXd ux = solver.solve(bx);
Eigen::VectorXd uy = solver.solve(by);
for (int i = 0; i < nv; ++i)
uv[i] = Point2D(ux(i), uy(i));
}
return uv;
}
std::vector<Point2D> lscm_parameterization(const HalfedgeMesh& mesh) {
// Fallback to Tutte for simplicity — full LSCM requires complex eigen decomposition
return tutte_parameterization(mesh);
}
} // namespace vde::mesh