Path: blob/master/thirdparty/manifold/src/properties.cpp
9903 views
// Copyright 2021 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include <limits>1516#if MANIFOLD_PAR == 117#include <tbb/combinable.h>18#endif1920#include "impl.h"21#include "parallel.h"22#include "tri_dist.h"2324namespace {25using namespace manifold;2627struct CurvatureAngles {28VecView<double> meanCurvature;29VecView<double> gaussianCurvature;30VecView<double> area;31VecView<double> degree;32VecView<const Halfedge> halfedge;33VecView<const vec3> vertPos;34VecView<const vec3> triNormal;3536void operator()(size_t tri) {37vec3 edge[3];38vec3 edgeLength(0.0);39for (int i : {0, 1, 2}) {40const int startVert = halfedge[3 * tri + i].startVert;41const int endVert = halfedge[3 * tri + i].endVert;42edge[i] = vertPos[endVert] - vertPos[startVert];43edgeLength[i] = la::length(edge[i]);44edge[i] /= edgeLength[i];45const int neighborTri = halfedge[3 * tri + i].pairedHalfedge / 3;46const double dihedral =470.25 * edgeLength[i] *48std::asin(la::dot(la::cross(triNormal[tri], triNormal[neighborTri]),49edge[i]));50AtomicAdd(meanCurvature[startVert], dihedral);51AtomicAdd(meanCurvature[endVert], dihedral);52AtomicAdd(degree[startVert], 1.0);53}5455vec3 phi;56phi[0] = std::acos(-la::dot(edge[2], edge[0]));57phi[1] = std::acos(-la::dot(edge[0], edge[1]));58phi[2] = kPi - phi[0] - phi[1];59const double area3 = edgeLength[0] * edgeLength[1] *60la::length(la::cross(edge[0], edge[1])) / 6;6162for (int i : {0, 1, 2}) {63const int vert = halfedge[3 * tri + i].startVert;64AtomicAdd(gaussianCurvature[vert], -phi[i]);65AtomicAdd(area[vert], area3);66}67}68};6970struct CheckHalfedges {71VecView<const Halfedge> halfedges;7273bool operator()(size_t edge) const {74const Halfedge halfedge = halfedges[edge];75if (halfedge.startVert == -1 || halfedge.endVert == -1) return true;76if (halfedge.pairedHalfedge == -1) return false;7778const Halfedge paired = halfedges[halfedge.pairedHalfedge];79bool good = true;80good &= paired.pairedHalfedge == static_cast<int>(edge);81good &= halfedge.startVert != halfedge.endVert;82good &= halfedge.startVert == paired.endVert;83good &= halfedge.endVert == paired.startVert;84return good;85}86};8788struct CheckCCW {89VecView<const Halfedge> halfedges;90VecView<const vec3> vertPos;91VecView<const vec3> triNormal;92const double tol;9394bool operator()(size_t face) const {95if (halfedges[3 * face].pairedHalfedge < 0) return true;9697const mat2x3 projection = GetAxisAlignedProjection(triNormal[face]);98vec2 v[3];99for (int i : {0, 1, 2})100v[i] = projection * vertPos[halfedges[3 * face + i].startVert];101102const int ccw = CCW(v[0], v[1], v[2], std::abs(tol));103return tol > 0 ? ccw >= 0 : ccw == 0;104}105};106} // namespace107108namespace manifold {109110/**111* Returns true if this manifold is in fact an oriented even manifold and all of112* the data structures are consistent.113*/114bool Manifold::Impl::IsManifold() const {115if (halfedge_.size() == 0) return true;116return all_of(countAt(0_uz), countAt(halfedge_.size()),117CheckHalfedges({halfedge_}));118}119120/**121* Returns true if this manifold is in fact an oriented 2-manifold and all of122* the data structures are consistent.123*/124bool Manifold::Impl::Is2Manifold() const {125if (halfedge_.size() == 0) return true;126if (!IsManifold()) return false;127128Vec<Halfedge> halfedge(halfedge_);129stable_sort(halfedge.begin(), halfedge.end());130131return all_of(132countAt(0_uz), countAt(2 * NumEdge() - 1), [&halfedge](size_t edge) {133const Halfedge h = halfedge[edge];134if (h.startVert == -1 && h.endVert == -1 && h.pairedHalfedge == -1)135return true;136return h.startVert != halfedge[edge + 1].startVert ||137h.endVert != halfedge[edge + 1].endVert;138});139}140141#ifdef MANIFOLD_DEBUG142std::mutex dump_lock;143#endif144145/**146* Returns true if this manifold is self-intersecting.147* Note that this is not checking for epsilon-validity.148*/149bool Manifold::Impl::IsSelfIntersecting() const {150const double ep = 2 * epsilon_;151const double epsilonSq = ep * ep;152Vec<Box> faceBox;153Vec<uint32_t> faceMorton;154GetFaceBoxMorton(faceBox, faceMorton);155156std::atomic<bool> intersecting(false);157158auto f = [&](int tri0, int tri1) {159std::array<vec3, 3> triVerts0, triVerts1;160for (int i : {0, 1, 2}) {161triVerts0[i] = vertPos_[halfedge_[3 * tri0 + i].startVert];162triVerts1[i] = vertPos_[halfedge_[3 * tri1 + i].startVert];163}164// if triangles tri0 and tri1 share a vertex, return true to skip the165// check. we relax the sharing criteria a bit to allow for at most166// distance epsilon squared167for (int i : {0, 1, 2})168for (int j : {0, 1, 2})169if (distance2(triVerts0[i], triVerts1[j]) <= epsilonSq) return;170171if (DistanceTriangleTriangleSquared(triVerts0, triVerts1) == 0.0) {172// try to move the triangles around the normal of the other face173std::array<vec3, 3> tmp0, tmp1;174for (int i : {0, 1, 2}) tmp0[i] = triVerts0[i] + ep * faceNormal_[tri1];175if (DistanceTriangleTriangleSquared(tmp0, triVerts1) > 0.0) return;176for (int i : {0, 1, 2}) tmp0[i] = triVerts0[i] - ep * faceNormal_[tri1];177if (DistanceTriangleTriangleSquared(tmp0, triVerts1) > 0.0) return;178for (int i : {0, 1, 2}) tmp1[i] = triVerts1[i] + ep * faceNormal_[tri0];179if (DistanceTriangleTriangleSquared(triVerts0, tmp1) > 0.0) return;180for (int i : {0, 1, 2}) tmp1[i] = triVerts1[i] - ep * faceNormal_[tri0];181if (DistanceTriangleTriangleSquared(triVerts0, tmp1) > 0.0) return;182183#ifdef MANIFOLD_DEBUG184if (ManifoldParams().verbose > 0) {185dump_lock.lock();186std::cout << "intersecting:" << std::endl;187for (int i : {0, 1, 2}) std::cout << triVerts0[i] << " ";188std::cout << std::endl;189for (int i : {0, 1, 2}) std::cout << triVerts1[i] << " ";190std::cout << std::endl;191dump_lock.unlock();192}193#endif194intersecting.store(true);195}196};197198auto recorder = MakeSimpleRecorder(f);199collider_.Collisions<true>(faceBox.cview(), recorder);200201return intersecting.load();202}203204/**205* Returns true if all triangles are CCW relative to their triNormals_.206*/207bool Manifold::Impl::MatchesTriNormals() const {208if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;209return all_of(countAt(0_uz), countAt(NumTri()),210CheckCCW({halfedge_, vertPos_, faceNormal_, 2 * epsilon_}));211}212213/**214* Returns the number of triangles that are colinear within epsilon_.215*/216int Manifold::Impl::NumDegenerateTris() const {217if (halfedge_.size() == 0 || faceNormal_.size() != NumTri()) return true;218return count_if(219countAt(0_uz), countAt(NumTri()),220CheckCCW({halfedge_, vertPos_, faceNormal_, -1 * epsilon_ / 2}));221}222223double Manifold::Impl::GetProperty(Property prop) const {224ZoneScoped;225if (IsEmpty()) return 0;226227auto Volume = [this](size_t tri) {228const vec3 v = vertPos_[halfedge_[3 * tri].startVert];229vec3 crossP = la::cross(vertPos_[halfedge_[3 * tri + 1].startVert] - v,230vertPos_[halfedge_[3 * tri + 2].startVert] - v);231return la::dot(crossP, v) / 6.0;232};233234auto Area = [this](size_t tri) {235const vec3 v = vertPos_[halfedge_[3 * tri].startVert];236return la::length(237la::cross(vertPos_[halfedge_[3 * tri + 1].startVert] - v,238vertPos_[halfedge_[3 * tri + 2].startVert] - v)) /2392.0;240};241242// Kahan summation243double value = 0;244double valueCompensation = 0;245for (size_t i = 0; i < NumTri(); ++i) {246const double value1 = prop == Property::SurfaceArea ? Area(i) : Volume(i);247const double t = value + value1;248valueCompensation += (value - t) + value1;249value = t;250}251value += valueCompensation;252return value;253}254255void Manifold::Impl::CalculateCurvature(int gaussianIdx, int meanIdx) {256ZoneScoped;257if (IsEmpty()) return;258if (gaussianIdx < 0 && meanIdx < 0) return;259Vec<double> vertMeanCurvature(NumVert(), 0);260Vec<double> vertGaussianCurvature(NumVert(), kTwoPi);261Vec<double> vertArea(NumVert(), 0);262Vec<double> degree(NumVert(), 0);263auto policy = autoPolicy(NumTri(), 1e4);264for_each(policy, countAt(0_uz), countAt(NumTri()),265CurvatureAngles({vertMeanCurvature, vertGaussianCurvature, vertArea,266degree, halfedge_, vertPos_, faceNormal_}));267for_each_n(policy, countAt(0), NumVert(),268[&vertMeanCurvature, &vertGaussianCurvature, &vertArea,269°ree](const int vert) {270const double factor = degree[vert] / (6 * vertArea[vert]);271vertMeanCurvature[vert] *= factor;272vertGaussianCurvature[vert] *= factor;273});274275const int oldNumProp = NumProp();276const int numProp = std::max(oldNumProp, std::max(gaussianIdx, meanIdx) + 1);277const Vec<double> oldProperties = properties_;278properties_ = Vec<double>(numProp * NumPropVert(), 0);279numProp_ = numProp;280281Vec<uint8_t> counters(NumPropVert(), 0);282for_each_n(policy, countAt(0_uz), NumTri(), [&](const size_t tri) {283for (const int i : {0, 1, 2}) {284const Halfedge& edge = halfedge_[3 * tri + i];285const int vert = edge.startVert;286const int propVert = edge.propVert;287288auto old = std::atomic_exchange(289reinterpret_cast<std::atomic<uint8_t>*>(&counters[propVert]),290static_cast<uint8_t>(1));291if (old == 1) continue;292293for (int p = 0; p < oldNumProp; ++p) {294properties_[numProp * propVert + p] =295oldProperties[oldNumProp * propVert + p];296}297298if (gaussianIdx >= 0) {299properties_[numProp * propVert + gaussianIdx] =300vertGaussianCurvature[vert];301}302if (meanIdx >= 0) {303properties_[numProp * propVert + meanIdx] = vertMeanCurvature[vert];304}305}306});307}308309/**310* Calculates the bounding box of the entire manifold, which is stored311* internally to short-cut Boolean operations. Ignores NaNs.312*/313void Manifold::Impl::CalculateBBox() {314bBox_.min =315reduce(vertPos_.begin(), vertPos_.end(),316vec3(std::numeric_limits<double>::infinity()), [](auto a, auto b) {317if (std::isnan(a.x)) return b;318if (std::isnan(b.x)) return a;319return la::min(a, b);320});321bBox_.max = reduce(vertPos_.begin(), vertPos_.end(),322vec3(-std::numeric_limits<double>::infinity()),323[](auto a, auto b) {324if (std::isnan(a.x)) return b;325if (std::isnan(b.x)) return a;326return la::max(a, b);327});328}329330/**331* Determines if all verts are finite. Checking just the bounding box dimensions332* is insufficient as it ignores NaNs.333*/334bool Manifold::Impl::IsFinite() const {335return transform_reduce(336vertPos_.begin(), vertPos_.end(), true,337[](bool a, bool b) { return a && b; },338[](auto v) { return la::all(la::isfinite(v)); });339}340341/**342* Checks that the input triVerts array has all indices inside bounds of the343* vertPos_ array.344*/345bool Manifold::Impl::IsIndexInBounds(VecView<const ivec3> triVerts) const {346ivec2 minmax = transform_reduce(347triVerts.begin(), triVerts.end(),348ivec2(std::numeric_limits<int>::max(), std::numeric_limits<int>::min()),349[](auto a, auto b) {350a[0] = std::min(a[0], b[0]);351a[1] = std::max(a[1], b[1]);352return a;353},354[](auto tri) {355return ivec2(std::min(tri[0], std::min(tri[1], tri[2])),356std::max(tri[0], std::max(tri[1], tri[2])));357});358359return minmax[0] >= 0 && minmax[1] < static_cast<int>(NumVert());360}361362struct MinDistanceRecorder {363using Local = double;364const Manifold::Impl &self, &other;365#if MANIFOLD_PAR == 1366tbb::combinable<double> store = tbb::combinable<double>(367[]() { return std::numeric_limits<double>::infinity(); });368Local& local() { return store.local(); }369double get() {370double result = std::numeric_limits<double>::infinity();371store.combine_each([&](double& val) { result = std::min(result, val); });372return result;373}374#else375double result = std::numeric_limits<double>::infinity();376Local& local() { return result; }377double get() { return result; }378#endif379380void record(int triOther, int tri, double& minDistance) {381std::array<vec3, 3> p;382std::array<vec3, 3> q;383384for (const int j : {0, 1, 2}) {385p[j] = self.vertPos_[self.halfedge_[3 * tri + j].startVert];386q[j] = other.vertPos_[other.halfedge_[3 * triOther + j].startVert];387}388minDistance = std::min(minDistance, DistanceTriangleTriangleSquared(p, q));389}390};391392/*393* Returns the minimum gap between two manifolds. Returns a double between394* 0 and searchLength.395*/396double Manifold::Impl::MinGap(const Manifold::Impl& other,397double searchLength) const {398ZoneScoped;399Vec<Box> faceBoxOther;400Vec<uint32_t> faceMortonOther;401402other.GetFaceBoxMorton(faceBoxOther, faceMortonOther);403404transform(faceBoxOther.begin(), faceBoxOther.end(), faceBoxOther.begin(),405[searchLength](const Box& box) {406return Box(box.min - vec3(searchLength),407box.max + vec3(searchLength));408});409410MinDistanceRecorder recorder{*this, other};411collider_.Collisions<false, Box, MinDistanceRecorder>(faceBoxOther.cview(),412recorder, false);413double minDistanceSquared =414std::min(recorder.get(), searchLength * searchLength);415return sqrt(minDistanceSquared);416};417418} // namespace manifold419420421