#include <limits>
#include "./impl.h"
#include "./parallel.h"
#include "./tri_dist.h"
namespace {
using namespace manifold;
struct CurvatureAngles {
VecView<double> meanCurvature;
VecView<double> gaussianCurvature;
VecView<double> area;
VecView<double> degree;
VecView<const Halfedge> halfedge;
VecView<const vec3> vertPos;
VecView<const vec3> triNormal;
void operator()(size_t tri) {
vec3 edge[3];
vec3 edgeLength(0.0);
for (int i : {0, 1, 2}) {
const int startVert = halfedge[3 * tri + i].startVert;
const int endVert = halfedge[3 * tri + i].endVert;
edge[i] = vertPos[endVert] - vertPos[startVert];
edgeLength[i] = la::length(edge[i]);
edge[i] /= edgeLength[i];
const int neighborTri = halfedge[3 * tri + i].pairedHalfedge / 3;
const double dihedral =
0.25 * edgeLength[i] *
std::asin(la::dot(la::cross(triNormal[tri], triNormal[neighborTri]),
edge[i]));
AtomicAdd(meanCurvature[startVert], dihedral);
AtomicAdd(meanCurvature[endVert], dihedral);
AtomicAdd(degree[startVert], 1.0);
}
vec3 phi;
phi[0] = std::acos(-la::dot(edge[2], edge[0]));
phi[1] = std::acos(-la::dot(edge[0], edge[1]));
phi[2] = kPi - phi[0] - phi[1];
const double area3 = edgeLength[0] * edgeLength[1] *
la::length(la::cross(edge[0], edge[1])) / 6;
for (int i : {0, 1, 2}) {
const int vert = halfedge[3 * tri + i].startVert;
AtomicAdd(gaussianCurvature[vert], -phi[i]);
AtomicAdd(area[vert], area3);
}
}
};
struct UpdateProperties {
VecView<ivec3> triProp;
VecView<double> properties;
VecView<uint8_t> counters;
VecView<const double> oldProperties;
VecView<const Halfedge> halfedge;
VecView<const double> meanCurvature;
VecView<const double> gaussianCurvature;
const int oldNumProp;
const int numProp;
const int gaussianIdx;
const int meanIdx;
void operator()(const size_t tri) {
for (const int i : {0, 1, 2}) {
const int vert = halfedge[3 * tri + i].startVert;
if (oldNumProp == 0) {
triProp[tri][i] = vert;
}
const int propVert = triProp[tri][i];
auto old = std::atomic_exchange(
reinterpret_cast<std::atomic<uint8_t>*>(&counters[propVert]),
static_cast<uint8_t>(1));
if (old == 1) continue;
for (int p = 0; p < oldNumProp; ++p) {
properties[numProp * propVert + p] =
oldProperties[oldNumProp * propVert + p];
}
if (gaussianIdx >= 0) {
properties[numProp * propVert + gaussianIdx] = gaussianCurvature[vert];
}
if (meanIdx >= 0) {
properties[numProp * propVert + meanIdx] = meanCurvature[vert];
}
}
}
};
struct CheckHalfedges {
VecView<const Halfedge> halfedges;
bool operator()(size_t edge) const {
const Halfedge halfedge = halfedges[edge];
if (halfedge.startVert == -1 || halfedge.endVert == -1) return true;
if (halfedge.pairedHalfedge == -1) return false;
const Halfedge paired = halfedges[halfedge.pairedHalfedge];
bool good = true;
good &= paired.pairedHalfedge == static_cast<int>(edge);
good &= halfedge.startVert != halfedge.endVert;
good &= halfedge.startVert == paired.endVert;
good &= halfedge.endVert == paired.startVert;
return good;
}
};
struct CheckCCW {
VecView<const Halfedge> halfedges;
VecView<const vec3> vertPos;
VecView<const vec3> triNormal;
const double tol;
bool operator()(size_t face) const {
if (halfedges[3 * face].pairedHalfedge < 0) return true;
const mat2x3 projection = GetAxisAlignedProjection(triNormal[face]);
vec2 v[3];
for (int i : {0, 1, 2})
v[i] = projection * vertPos[halfedges[3 * face + i].startVert];
int ccw = CCW(v[0], v[1], v[2], std::abs(tol));
bool check = tol > 0 ? ccw >= 0 : ccw == 0;
#ifdef MANIFOLD_DEBUG
if (tol > 0 && !check) {
vec2 v1 = v[1] - v[0];
vec2 v2 = v[2] - v[0];
double area = v1.x * v2.y - v1.y * v2.x;
double base2 = std::max(la::dot(v1, v1), la::dot(v2, v2));
double base = std::sqrt(base2);
vec3 V0 = vertPos[halfedges[3 * face].startVert];
vec3 V1 = vertPos[halfedges[3 * face + 1].startVert];
vec3 V2 = vertPos[halfedges[3 * face + 2].startVert];
vec3 norm = la::cross(V1 - V0, V2 - V0);
printf(
"Tri %ld does not match normal, approx height = %g, base = %g\n"
"tol = %g, area2 = %g, base2*tol2 = %g\n"
"normal = %g, %g, %g\n"
"norm = %g, %g, %g\nverts: %d, %d, %d\n",
static_cast<long>(face), area / base, base, tol, area * area,
base2 * tol * tol, triNormal[face].x, triNormal[face].y,
triNormal[face].z, norm.x, norm.y, norm.z,
halfedges[3 * face].startVert, halfedges[3 * face + 1].startVert,
halfedges[3 * face + 2].startVert);
}
#endif
return check;
}
};
}
namespace manifold {
bool Manifold::Impl::IsManifold() const {
if (halfedge_.size() == 0) return true;
return all_of(countAt(0_uz), countAt(halfedge_.size()),
CheckHalfedges({halfedge_}));
}
bool Manifold::Impl::Is2Manifold() const {
if (halfedge_.size() == 0) return true;
if (!IsManifold()) return false;
Vec<Halfedge> halfedge(halfedge_);
stable_sort(halfedge.begin(), halfedge.end());
return all_of(
countAt(0_uz), countAt(2 * NumEdge() - 1), [&halfedge](size_t edge) {
const Halfedge h = halfedge[edge];
if (h.startVert == -1 && h.endVert == -1 && h.pairedHalfedge == -1)
return true;
return h.startVert != halfedge[edge + 1].startVert ||
h.endVert != halfedge[edge + 1].endVert;
});
}
#ifdef MANIFOLD_DEBUG
std::mutex dump_lock;
#endif
bool Manifold::Impl::IsSelfIntersecting() const {
const double epsilonSq = epsilon_ * epsilon_;
Vec<Box> faceBox;
Vec<uint32_t> faceMorton;
GetFaceBoxMorton(faceBox, faceMorton);
SparseIndices collisions = collider_.Collisions<true>(faceBox.cview());
const bool verbose = ManifoldParams().verbose;
return !all_of(countAt(0), countAt(collisions.size()), [&](size_t i) {
size_t x = collisions.Get(i, false);
size_t y = collisions.Get(i, true);
std::array<vec3, 3> tri_x, tri_y;
for (int i : {0, 1, 2}) {
tri_x[i] = vertPos_[halfedge_[3 * x + i].startVert];
tri_y[i] = vertPos_[halfedge_[3 * y + i].startVert];
}
for (int i : {0, 1, 2})
for (int j : {0, 1, 2})
if (distance2(tri_x[i], tri_y[j]) <= epsilonSq) return true;
if (DistanceTriangleTriangleSquared(tri_x, tri_y) == 0.0) {
std::array<vec3, 3> tmp_x, tmp_y;
for (int i : {0, 1, 2}) tmp_x[i] = tri_x[i] + epsilon_ * faceNormal_[y];
if (DistanceTriangleTriangleSquared(tmp_x, tri_y) > 0.0) return true;
for (int i : {0, 1, 2}) tmp_x[i] = tri_x[i] - epsilon_ * faceNormal_[y];
if (DistanceTriangleTriangleSquared(tmp_x, tri_y) > 0.0) return true;
for (int i : {0, 1, 2}) tmp_y[i] = tri_y[i] + epsilon_ * faceNormal_[x];
if (DistanceTriangleTriangleSquared(tri_x, tmp_y) > 0.0) return true;
for (int i : {0, 1, 2}) tmp_y[i] = tri_y[i] - epsilon_ * faceNormal_[x];
if (DistanceTriangleTriangleSquared(tri_x, tmp_y) > 0.0) return true;
#ifdef MANIFOLD_DEBUG
if (verbose) {
dump_lock.lock();
std::cout << "intersecting:" << std::endl;
for (int i : {0, 1, 2}) std::cout << tri_x[i] << " ";
std::cout << std::endl;
for (int i : {0, 1, 2}) std::cout << tri_y[i] << " ";
std::cout << std::endl;
dump_lock.unlock();
}
#endif
return false;
}
return true;
});
}
bool Manifold::Impl::MatchesTriNormals() const {
if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;
return all_of(countAt(0_uz), countAt(NumTri()),
CheckCCW({halfedge_, vertPos_, faceNormal_, 2 * epsilon_}));
}
int Manifold::Impl::NumDegenerateTris() const {
if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;
return count_if(
countAt(0_uz), countAt(NumTri()),
CheckCCW({halfedge_, vertPos_, faceNormal_, -1 * epsilon_ / 2}));
}
double Manifold::Impl::GetProperty(Property prop) const {
ZoneScoped;
if (IsEmpty()) return 0;
auto Volume = [this](size_t tri) {
const vec3 v = vertPos_[halfedge_[3 * tri].startVert];
vec3 crossP = la::cross(vertPos_[halfedge_[3 * tri + 1].startVert] - v,
vertPos_[halfedge_[3 * tri + 2].startVert] - v);
return la::dot(crossP, v) / 6.0;
};
auto Area = [this](size_t tri) {
const vec3 v = vertPos_[halfedge_[3 * tri].startVert];
return la::length(
la::cross(vertPos_[halfedge_[3 * tri + 1].startVert] - v,
vertPos_[halfedge_[3 * tri + 2].startVert] - v)) /
2.0;
};
double value = 0;
double valueCompensation = 0;
for (size_t i = 0; i < NumTri(); ++i) {
const double value1 = prop == Property::SurfaceArea ? Area(i) : Volume(i);
const double t = value + value1;
valueCompensation += (value - t) + value1;
value = t;
}
value += valueCompensation;
return value;
}
void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {
ZoneScoped;
if (IsEmpty()) return;
if (gaussianIdx < 0 && meanIdx < 0) return;
Vec<double> vertMeanCurvature(NumVert(), 0);
Vec<double> vertGaussianCurvature(NumVert(), kTwoPi);
Vec<double> vertArea(NumVert(), 0);
Vec<double> degree(NumVert(), 0);
auto policy = autoPolicy(NumTri(), 1e4);
for_each(policy, countAt(0_uz), countAt(NumTri()),
CurvatureAngles({vertMeanCurvature, vertGaussianCurvature, vertArea,
degree, halfedge_, vertPos_, faceNormal_}));
for_each_n(policy, countAt(0), NumVert(),
[&vertMeanCurvature, &vertGaussianCurvature, &vertArea,
°ree](const int vert) {
const double factor = degree[vert] / (6 * vertArea[vert]);
vertMeanCurvature[vert] *= factor;
vertGaussianCurvature[vert] *= factor;
});
const int oldNumProp = NumProp();
const int numProp = std::max(oldNumProp, std::max(gaussianIdx, meanIdx) + 1);
const Vec<double> oldProperties = meshRelation_.properties;
meshRelation_.properties = Vec<double>(numProp * NumPropVert(), 0);
meshRelation_.numProp = numProp;
if (meshRelation_.triProperties.size() == 0) {
meshRelation_.triProperties.resize(NumTri());
}
const Vec<uint8_t> counters(NumPropVert(), 0);
for_each_n(
policy, countAt(0_uz), NumTri(),
UpdateProperties({meshRelation_.triProperties, meshRelation_.properties,
counters, oldProperties, halfedge_, vertMeanCurvature,
vertGaussianCurvature, oldNumProp, numProp, gaussianIdx,
meanIdx}));
}
void Manifold::Impl::CalculateBBox() {
bBox_.min =
reduce(vertPos_.begin(), vertPos_.end(),
vec3(std::numeric_limits<double>::infinity()), [](auto a, auto b) {
if (std::isnan(a.x)) return b;
if (std::isnan(b.x)) return a;
return la::min(a, b);
});
bBox_.max = reduce(vertPos_.begin(), vertPos_.end(),
vec3(-std::numeric_limits<double>::infinity()),
[](auto a, auto b) {
if (std::isnan(a.x)) return b;
if (std::isnan(b.x)) return a;
return la::max(a, b);
});
}
bool Manifold::Impl::IsFinite() const {
return transform_reduce(
vertPos_.begin(), vertPos_.end(), true,
[](bool a, bool b) { return a && b; },
[](auto v) { return la::all(la::isfinite(v)); });
}
bool Manifold::Impl::IsIndexInBounds(VecView<const ivec3> triVerts) const {
ivec2 minmax = transform_reduce(
triVerts.begin(), triVerts.end(),
ivec2(std::numeric_limits<int>::max(), std::numeric_limits<int>::min()),
[](auto a, auto b) {
a[0] = std::min(a[0], b[0]);
a[1] = std::max(a[1], b[1]);
return a;
},
[](auto tri) {
return ivec2(std::min(tri[0], std::min(tri[1], tri[2])),
std::max(tri[0], std::max(tri[1], tri[2])));
});
return minmax[0] >= 0 && minmax[1] < static_cast<int>(NumVert());
}
double Manifold::Impl::MinGap(const Manifold::Impl& other,
double searchLength) const {
ZoneScoped;
Vec<Box> faceBoxOther;
Vec<uint32_t> faceMortonOther;
other.GetFaceBoxMorton(faceBoxOther, faceMortonOther);
transform(faceBoxOther.begin(), faceBoxOther.end(), faceBoxOther.begin(),
[searchLength](const Box& box) {
return Box(box.min - vec3(searchLength),
box.max + vec3(searchLength));
});
SparseIndices collisions = collider_.Collisions(faceBoxOther.cview());
double minDistanceSquared = transform_reduce(
countAt(0_uz), countAt(collisions.size()), searchLength * searchLength,
[](double a, double b) { return std::min(a, b); },
[&collisions, this, &other](int i) {
const int tri = collisions.Get(i, 1);
const int triOther = collisions.Get(i, 0);
std::array<vec3, 3> p;
std::array<vec3, 3> q;
for (const int j : {0, 1, 2}) {
p[j] = vertPos_[halfedge_[3 * tri + j].startVert];
q[j] = other.vertPos_[other.halfedge_[3 * triOther + j].startVert];
}
return DistanceTriangleTriangleSquared(p, q);
});
return sqrt(minDistanceSquared);
};
}