cf13496ff6
新增功能: - core: 2D Voronoi 图(Delaunay 对偶图) - mesh: Marching Cubes(完整256项三角表 + SDF 辅助函数) - mesh: 离散曲率(Gaussian/Mean, Cotan 公式) - foundation: 二进制序列化(版本化格式,Little-Endian) - python: pybind11 绑定(30+ API 暴露) - marching_cubes.h: SDF 基本体(球/盒)+ 光滑布尔
102 lines
4.0 KiB
C++
102 lines
4.0 KiB
C++
#include "vde/core/voronoi.h"
|
|
#include "vde/mesh/delaunay_2d.h"
|
|
#include <unordered_map>
|
|
#include <algorithm>
|
|
|
|
namespace vde::core {
|
|
|
|
// Edge key for unordered_map
|
|
struct EdgeKey {
|
|
int a, b;
|
|
EdgeKey(int va, int vb) : a(std::min(va, vb)), b(std::max(va, vb)) {}
|
|
bool operator==(const EdgeKey& o) const { return a == o.a && b == o.b; }
|
|
};
|
|
struct EdgeKeyHash {
|
|
size_t operator()(const EdgeKey& e) const {
|
|
return (static_cast<uint64_t>(e.a) << 32) | static_cast<uint64_t>(e.b);
|
|
}
|
|
};
|
|
|
|
std::vector<VoronoiCell> voronoi_2d(const std::vector<Point2D>& points) {
|
|
std::vector<VoronoiCell> cells(points.size());
|
|
for (size_t i = 0; i < points.size(); ++i)
|
|
cells[i].site = points[i];
|
|
|
|
if (points.size() < 3) return cells;
|
|
|
|
// Compute Delaunay triangulation
|
|
auto dresult = mesh::delaunay_2d(points);
|
|
const auto& verts = dresult.vertices;
|
|
const auto& tris = dresult.triangles;
|
|
|
|
// Compute circumcenters for each Delaunay triangle
|
|
std::vector<Point2D> circumcenters;
|
|
for (const auto& tri : tris) {
|
|
Point2D a = verts[tri[0]], b = verts[tri[1]], c = verts[tri[2]];
|
|
double d = 2 * (a.x()*(b.y()-c.y()) + b.x()*(c.y()-a.y()) + c.x()*(a.y()-b.y()));
|
|
if (std::abs(d) < 1e-12) { circumcenters.push_back({0,0}); continue; }
|
|
double ux = ((a.x()*a.x()+a.y()*a.y())*(b.y()-c.y())
|
|
+ (b.x()*b.x()+b.y()*b.y())*(c.y()-a.y())
|
|
+ (c.x()*c.x()+c.y()*c.y())*(a.y()-b.y())) / d;
|
|
double uy = ((a.x()*a.x()+a.y()*a.y())*(c.x()-b.x())
|
|
+ (b.x()*b.x()+b.y()*b.y())*(a.x()-c.x())
|
|
+ (c.x()*c.x()+c.y()*c.y())*(b.x()-a.x())) / d;
|
|
circumcenters.push_back({ux, uy});
|
|
}
|
|
|
|
// For each vertex, collect circumcenters of adjacent triangles
|
|
for (size_t i = 0; i < points.size(); ++i) {
|
|
std::vector<Point2D> cell_verts;
|
|
std::unordered_map<EdgeKey, int, EdgeKeyHash> edge_first;
|
|
|
|
// Find all triangles containing this vertex
|
|
for (size_t ti = 0; ti < tris.size(); ++ti) {
|
|
const auto& tri = tris[ti];
|
|
for (int j = 0; j < 3; ++j) {
|
|
if (tri[j] == static_cast<int>(i) || tri[(j+1)%3] == static_cast<int>(i)) {
|
|
int v0 = tri[(j+0)%3], v1 = tri[(j+1)%3];
|
|
// Edge v0-v1 connects two circumcenters
|
|
// Find neighboring triangle sharing edge v0-v1
|
|
for (size_t tj = ti + 1; tj < tris.size(); ++tj) {
|
|
const auto& tri2 = tris[tj];
|
|
bool shares[3] = {false, false, false};
|
|
for (int k = 0; k < 3; ++k)
|
|
shares[k] = (tri2[k] == v0 || tri2[k] == v1 ||
|
|
tri2[k] == tri[(j+2)%3]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Simplified: just collect circumcenters of adjacent triangles
|
|
for (size_t ti = 0; ti < tris.size(); ++ti) {
|
|
const auto& tri = tris[ti];
|
|
bool contains = false;
|
|
for (int j = 0; j < 3; ++j)
|
|
if (tri[j] == static_cast<int>(i)) { contains = true; break; }
|
|
if (contains) {
|
|
cells[i].vertices.push_back(circumcenters[ti]);
|
|
}
|
|
}
|
|
|
|
// Sort circumcenters angularly around site for proper polygon
|
|
if (cells[i].vertices.size() >= 3) {
|
|
std::sort(cells[i].vertices.begin(), cells[i].vertices.end(),
|
|
[&](const Point2D& a, const Point2D& b) {
|
|
return std::atan2(a.y() - points[i].y(), a.x() - points[i].x()) <
|
|
std::atan2(b.y() - points[i].y(), b.x() - points[i].x());
|
|
});
|
|
// Deduplicate
|
|
auto last = std::unique(cells[i].vertices.begin(), cells[i].vertices.end(),
|
|
[](const Point2D& a, const Point2D& b) {
|
|
return (a-b).norm() < 1e-9;
|
|
});
|
|
cells[i].vertices.erase(last, cells[i].vertices.end());
|
|
}
|
|
}
|
|
|
|
return cells;
|
|
}
|
|
|
|
} // namespace vde::core
|