mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-17 10:32:20 +00:00
* Update eigen from v3.3.7 to v5.0.1. This updates eigen from v3.3.7 released on December 11, 2018-12-11 to v5.0.1 released on 2025-11-11. There have be a large number of bug-fixes, optimizations, and improvements between these releases. See the details at; https://gitlab.com/libeigen/eigen/-/releases It retains the previous custom minimal `CMakeLists.txt`, and adds a README-OrcaSlicer.md that explains what version and parts of the upstream eigen release have been included, and where the full release can be found. * Update libigl from v2.0.0 (or older) to v2.6.0. This updates libigl from what was probably v2.0.0 released on 2018-10-16 to v2.6.0 released on 2025-05-15. It's possible the old version was even older than that but there is no version indicators in the code and I ran out of patience identifying missing changes and only went back as far as v2.0.0. There have been a large number of bug-fixes, optimizations, and improvements between these versions. See the following for details; https://github.com/libigl/libigl/releases I retained the minimal custom `CMakeLists.txt`, added `README.md` from the libigl distribution which identifies the version, and added a README-OrcaSlicer.md that details the version and parts that have been included. * Update libslic3r for libigl v2.6.0 changes. This updates libslic3r for all changes moving to eigen v5.0.1 and libigl v2.6.0. Despite the large number of updates to both dependencies, no changes were required for the eigen update, and only one change was required for the libigl update. For libigl, `igl::Hit` was changed to a template taking the Scalar type to use. Previously it was hard-coded to `float`, so to minimize possible impact I've updated all places it is used from `igl::Hit` to `igl::Hit<float>`. * Add compiler option `-DNOMINMAX` for libigl with MSVC. MSVC by default defines `min(()` and `max()` macros that break `std::numeric_limits<>::max()`. The upstream cmake that we don't include adds `-DNOMINMAX` for the libigl module when compiling with MSVC, so we need to add the same thing here. * Fix src/libslic3r/TriangleMeshDeal.cpp for the unmodified upstream libigl. This fixes `TriangleMeshDeal.cpp` to work with the unmodified upstream libigl v2.6.0. loop.{h,cpp} implementation. This file and feature was added in PR "BBS Port: Mesh Subdivision" (#12150) which included changes to `loop.{h,cpp}` in the old version of libigl. This PR avoids modifying the included dependencies, and uses the updated upstream versions of those files without any modifications, which requires fixing TriangleMeshDeal.cpp to work with them. In particular, the modifications made to `loop.{h,cpp}` included changing the return type from void to bool, adding additional validation checking of the input meshes, and returning false if they failed validation. These added checks looked unnecessary and would only have caught problems if the input mesh was very corrupt. To make `TriangleMeshDeal.cpp` work without this built-in checking functionality, I removed checking/handling of any `false` return value. There was also a hell of a lot of redundant copying and casting back and forth between float and double, so I cleaned that up. The input and output meshs use floats for the vertexes, and there would be no accuracy benefits from casting to and from doubles for the simple weighted average operations done by igl::loop(). So this just uses `Eigen:Map` to use the original input mesh vertex data directly without requiring any copy or casting. * Move eigen from included `deps_src` to externaly fetched `deps`. This copys what PrusaSlicer did and moved it from an included dependency under `deps_src` to an externaly fetched dependency under `deps`. This requires updating some `CMakeList.txt` configs and removing the old and obsolete `cmake/modules/FindEigen3.cmake`. The details of when this was done in PrusaSlicer and the followup fixes are at; *21116995d7* https://github.com/prusa3d/PrusaSlicer/issues/13608 * https://github.com/prusa3d/PrusaSlicer/pull/13609 *e3c277b9eeFor some reason I don't fully understand this also required fixing `src/slic3r/GUI/GUI_App.cpp` by adding `#include <boost/nowide/cstdio.hpp>` to fix an `error: ‘remove’ is not a member of ‘boost::nowide'`. The main thing I don't understand is how it worked before. Note that this include is in the PrusaSlicer version of this file, but it also significantly deviates from what is currently in OrcaSlicer in many other ways. * Whups... I missed adding the deps/Eigen/Eigen.cmake file... * Tidy some whitespace indenting in CMakeLists.txt. * Ugh... tabs indenting needing fixes. * Change the include order of deps/Eigen. It turns out that although Boost includes some references to Eigen, Eigen also includes some references to Boost for supporting some of it's additional numeric types. I don't think it matters much since we are not using these features, but I think technically its more correct to say Eigen depends on Boost than the other way around, so I've re-ordered them. * Add source for Eigen 5.0.1 download to flatpak yml config. * Add explicit `DEPENDS dep_Boost to deps/Eigen. I missed this before. This ensures we don't rely on include orders to make sure Boost is installed before we configure Eigen. * Add `DEPENDS dep_Boost dep_GMP dep_MPFR` to deps/Eigen. It turns out Eigen can also use GMP and MPFR for multi-precision and multi-precision-rounded numeric types if they are available. Again, I don't think we are using these so it doesn't really matter, but it is technically correct and ensures they are there if we ever do need them. * Fix deps DEPENDENCY ordering for GMP, MPFR, Eigen, and CGAL. I think this is finally correct. Apparently CGAL also optionally depends on Eigen, so the correct dependency order from lowest to highest is GMP, MPFR, Eigen, and CGAL. --------- Co-authored-by: Donovan Baarda <dbaarda@google.com> Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
946 lines
27 KiB
C++
946 lines
27 KiB
C++
// This file is part of libigl, a simple c++ geometry processing library.
|
|
//
|
|
// Copyright (C) 2014 Alec Jacobson <alecjacobson@gmail.com>
|
|
//
|
|
// This Source Code Form is subject to the terms of the Mozilla Public License
|
|
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
|
// obtain one at http://mozilla.org/MPL/2.0/.
|
|
#ifndef IGL_COPYLEFT_CGAL_SELFINTERSECTMESH_H
|
|
#define IGL_COPYLEFT_CGAL_SELFINTERSECTMESH_H
|
|
|
|
#include "CGAL_includes.hpp"
|
|
#include "RemeshSelfIntersectionsParam.h"
|
|
#include "../../unique.h"
|
|
#include "../../default_num_threads.h"
|
|
|
|
#include <Eigen/Dense>
|
|
#include <list>
|
|
#include <map>
|
|
#include <vector>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <cstdio>
|
|
|
|
//#define IGL_SELFINTERSECTMESH_TIMING
|
|
#ifndef IGL_FIRST_HIT_EXCEPTION
|
|
#define IGL_FIRST_HIT_EXCEPTION 10
|
|
#endif
|
|
|
|
// The easiest way to keep track of everything is to use a class
|
|
|
|
namespace igl
|
|
{
|
|
namespace copyleft
|
|
{
|
|
namespace cgal
|
|
{
|
|
/// Class for computing the self-intersections of a mesh
|
|
///
|
|
/// @tparam Kernel is a CGAL kernel like:
|
|
/// CGAL::Exact_predicates_inexact_constructions_kernel
|
|
/// or
|
|
/// CGAL::Exact_predicates_exact_constructions_kernel
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
class SelfIntersectMesh
|
|
{
|
|
typedef
|
|
SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM> Self;
|
|
public:
|
|
// 3D Primitives
|
|
typedef CGAL::Point_3<Kernel> Point_3;
|
|
typedef CGAL::Segment_3<Kernel> Segment_3;
|
|
typedef CGAL::Triangle_3<Kernel> Triangle_3;
|
|
typedef CGAL::Plane_3<Kernel> Plane_3;
|
|
typedef CGAL::Tetrahedron_3<Kernel> Tetrahedron_3;
|
|
// 2D Primitives
|
|
typedef CGAL::Point_2<Kernel> Point_2;
|
|
typedef CGAL::Segment_2<Kernel> Segment_2;
|
|
typedef CGAL::Triangle_2<Kernel> Triangle_2;
|
|
// 2D Constrained Delaunay Triangulation types
|
|
typedef CGAL::Exact_intersections_tag Itag;
|
|
// Axis-align boxes for all-pairs self-intersection detection
|
|
typedef std::vector<Triangle_3> Triangles;
|
|
typedef typename Triangles::iterator TrianglesIterator;
|
|
typedef typename Triangles::const_iterator TrianglesConstIterator;
|
|
typedef
|
|
CGAL::Box_intersection_d::Box_with_handle_d<double,3,TrianglesIterator>
|
|
Box;
|
|
|
|
// Input mesh
|
|
const Eigen::MatrixBase<DerivedV> & V;
|
|
const Eigen::MatrixBase<DerivedF> & F;
|
|
// Number of self-intersecting triangle pairs
|
|
typedef typename DerivedF::Index Index;
|
|
Index count;
|
|
typedef std::vector<std::pair<Index, CGAL::Object>> ObjectList;
|
|
// Using a vector here makes this **not** output sensitive
|
|
Triangles T;
|
|
typedef std::vector<Index> IndexList;
|
|
IndexList lIF;
|
|
// #F-long list of faces with intersections mapping to the order in
|
|
// which they were first found
|
|
std::map<Index,ObjectList> offending;
|
|
// Make a short name for the edge map's key
|
|
typedef std::pair<Index,Index> EMK;
|
|
// Make a short name for the type stored at each edge, the edge map's
|
|
// value
|
|
typedef std::vector<Index> EMV;
|
|
// Make a short name for the edge map
|
|
typedef std::map<EMK,EMV> EdgeMap;
|
|
// Maps edges of offending faces to all incident offending faces
|
|
std::vector<std::pair<TrianglesIterator, TrianglesIterator> >
|
|
candidate_triangle_pairs;
|
|
|
|
public:
|
|
RemeshSelfIntersectionsParam params;
|
|
public:
|
|
/// Constructs (VV,FF) a new mesh with self-intersections of (V,F)
|
|
/// subdivided
|
|
///
|
|
/// @param[in] V #V by 3 list of vertex positions
|
|
/// @param[in] F #F by 3 list of triangle indices into V
|
|
/// @param[in] params parameters
|
|
/// @param[out] VV #VV by 3 list of vertex positions
|
|
/// @param[out] FF #FF by 3 list of triangle indices into VV
|
|
/// @param[out] IF #IF by 2 list of edge indices into VV
|
|
/// @param[out] J #F list of indices into FF of birth parents
|
|
/// @param[out] IM #VV list of indices into V of birth parents
|
|
///
|
|
///
|
|
/// \see remesh_self_intersections.h
|
|
inline SelfIntersectMesh(
|
|
const Eigen::MatrixBase<DerivedV> & V,
|
|
const Eigen::MatrixBase<DerivedF> & F,
|
|
const RemeshSelfIntersectionsParam & params,
|
|
Eigen::PlainObjectBase<DerivedVV> & VV,
|
|
Eigen::PlainObjectBase<DerivedFF> & FF,
|
|
Eigen::PlainObjectBase<DerivedIF> & IF,
|
|
Eigen::PlainObjectBase<DerivedJ> & J,
|
|
Eigen::PlainObjectBase<DerivedIM> & IM);
|
|
private:
|
|
/// Helper function to mark a face as offensive
|
|
///
|
|
/// @param[in] f index of face in F
|
|
inline void mark_offensive(const Index f);
|
|
/// Helper function to count intersections between faces
|
|
///
|
|
/// @param[in] fa index of face A in F
|
|
/// @param[in] fb index of face B in F
|
|
inline void count_intersection( const Index fa, const Index fb);
|
|
/// Helper function for box_intersect. Intersect two triangles A and B,
|
|
/// append the intersection object (point,segment,triangle) to a running
|
|
/// list for A and B
|
|
///
|
|
/// @param[in] A triangle in 3D
|
|
/// @param[in] B triangle in 3D
|
|
/// @param[in] fa index of A in F (and key into offending)
|
|
/// @param[in] fb index of B in F (and key into offending)
|
|
/// @return true only if A intersects B
|
|
///
|
|
inline bool intersect(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb);
|
|
/// Helper function for box_intersect. In the case where A and B have
|
|
/// already been identified to share a vertex, then we only want to
|
|
/// add possible segment intersections. Assumes truly duplicate
|
|
/// triangles are not given as input
|
|
///
|
|
/// @param[in] A triangle in 3D
|
|
/// @param[in] B triangle in 3D
|
|
/// @param[in] fa index of A in F (and key into offending)
|
|
/// @param[in] fb index of B in F (and key into offending)
|
|
/// @param[in] va index of shared vertex in A (and key into offending)
|
|
/// @param[in] vb index of shared vertex in B (and key into offending)
|
|
/// @return true if intersection (besides shared point)
|
|
///
|
|
inline bool single_shared_vertex(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb,
|
|
const Index va,
|
|
const Index vb);
|
|
//// Helper handling one direction
|
|
///
|
|
/// @param[in] A triangle in 3D
|
|
/// @param[in] B triangle in 3D
|
|
/// @param[in] fa index of A in F (and key into offending)
|
|
/// @param[in] fb index of B in F (and key into offending)
|
|
/// @param[in] va index of shared vertex in A (and key into offending)
|
|
/// @return true if intersection (besides shared point)
|
|
inline bool single_shared_vertex(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb,
|
|
const Index va);
|
|
/// Helper function for box_intersect. In the case where A and B have
|
|
/// already been identified to share two vertices, then we only want
|
|
/// to add a possible coplanar (Triangle) intersection. Assumes truly
|
|
/// degenerate facets are not givin as input.
|
|
///
|
|
/// @param[in] A triangle in 3D
|
|
/// @param[in] B triangle in 3D
|
|
/// @param[in] fa index of A in F (and key into offending)
|
|
/// @param[in] fb index of B in F (and key into offending)
|
|
/// @param[in] shared list of pairs of indices of shared vertices
|
|
/// @return true if intersection (besides shared point)
|
|
inline bool double_shared_vertex(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb,
|
|
const std::vector<std::pair<Index,Index> > shared);
|
|
|
|
public:
|
|
/// Callback function called during box self intersections test. Means
|
|
/// boxes a and b intersect. This method then checks if the triangles
|
|
/// in each box intersect and if so, then processes the intersections
|
|
///
|
|
/// @param[in] a box containing a triangle
|
|
/// @param[in] b box containing a triangle
|
|
inline void box_intersect(const Box& a, const Box& b);
|
|
/// Process all of the intersecting boxes
|
|
inline void process_intersecting_boxes();
|
|
public:
|
|
// Getters:
|
|
//const IndexList& get_lIF() const{ return lIF;}
|
|
/// Static function that captures a SelfIntersectMesh instance to pass
|
|
/// to cgal.
|
|
/// @param[in] SIM pointer to SelfIntersectMesh instance
|
|
/// @param[in] a box containing a triangle
|
|
/// @param[in] b box containing a triangle
|
|
static inline void box_intersect_static(
|
|
SelfIntersectMesh * SIM,
|
|
const Box &a,
|
|
const Box &b);
|
|
private:
|
|
std::mutex m_offending_lock;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
// Implementation
|
|
|
|
#include "mesh_to_cgal_triangle_list.h"
|
|
#include "remesh_intersections.h"
|
|
|
|
#include "../../get_seconds.h"
|
|
#include "../../C_STR.h"
|
|
|
|
|
|
#include <functional>
|
|
#include <algorithm>
|
|
#include <exception>
|
|
#include <cassert>
|
|
|
|
// References:
|
|
// http://minregret.googlecode.com/svn/trunk/skyline/src/extern/CGAL-3.3.1/examples/Polyhedron/polyhedron_self_intersection.cpp
|
|
// http://www.cgal.org/Manual/3.9/examples/Boolean_set_operations_2/do_intersect.cpp
|
|
|
|
// Q: Should we be using CGAL::Polyhedron_3?
|
|
// A: No! Input is just a list of unoriented triangles. Polyhedron_3 requires
|
|
// a 2-manifold.
|
|
// A: But! It seems we could use CGAL::Triangulation_3. Though it won't be easy
|
|
// to take advantage of functions like insert_in_facet because we want to
|
|
// constrain segments. Hmmm. Actually Triangulation_3 doesn't look right...
|
|
|
|
// CGAL's box_self_intersection_d uses C-style function callbacks without
|
|
// userdata. This is a leapfrog method for calling a member function. It should
|
|
// be bound as if the prototype was:
|
|
// static void box_intersect(const Box &a, const Box &b)
|
|
// using boost:
|
|
// boost::function<void(const Box &a,const Box &b)> cb
|
|
// = boost::bind(&::box_intersect, this, _1,_2);
|
|
//
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline void igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::box_intersect_static(
|
|
Self * SIM,
|
|
const typename Self::Box &a,
|
|
const typename Self::Box &b)
|
|
{
|
|
SIM->box_intersect(a,b);
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::SelfIntersectMesh(
|
|
const Eigen::MatrixBase<DerivedV> & V,
|
|
const Eigen::MatrixBase<DerivedF> & F,
|
|
const RemeshSelfIntersectionsParam & params,
|
|
Eigen::PlainObjectBase<DerivedVV> & VV,
|
|
Eigen::PlainObjectBase<DerivedFF> & FF,
|
|
Eigen::PlainObjectBase<DerivedIF> & IF,
|
|
Eigen::PlainObjectBase<DerivedJ> & J,
|
|
Eigen::PlainObjectBase<DerivedIM> & IM):
|
|
V(V),
|
|
F(F),
|
|
count(0),
|
|
T(),
|
|
lIF(),
|
|
offending(),
|
|
params(params)
|
|
{
|
|
using namespace std;
|
|
using namespace Eigen;
|
|
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
const auto & tictoc = []() -> double
|
|
{
|
|
static double t_start = igl::get_seconds();
|
|
double diff = igl::get_seconds()-t_start;
|
|
t_start += diff;
|
|
return diff;
|
|
};
|
|
const auto log_time = [&](const std::string& label) -> void{
|
|
std::printf("%50s: %0.5lf\n",
|
|
C_STR("SelfIntersectMesh." << label),tictoc());
|
|
};
|
|
tictoc();
|
|
#endif
|
|
|
|
// Compute and process self intersections
|
|
mesh_to_cgal_triangle_list(V,F,T);
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
log_time("convert_to_triangle_list");
|
|
#endif
|
|
// http://www.cgal.org/Manual/latest/doc_html/cgal_manual/Box_intersection_d/Chapter_main.html#Section_63.5
|
|
// Create the corresponding vector of bounding boxes
|
|
std::vector<Box> boxes;
|
|
boxes.reserve(T.size());
|
|
for (
|
|
TrianglesIterator tit = T.begin();
|
|
tit != T.end();
|
|
++tit)
|
|
{
|
|
if (!tit->is_degenerate())
|
|
{
|
|
boxes.push_back(Box(tit->bbox(), tit));
|
|
}
|
|
}
|
|
// Leapfrog callback
|
|
std::function<void(const Box &a,const Box &b)> cb =
|
|
std::bind(&box_intersect_static, this,
|
|
// Explicitly use std namespace to avoid confusion with boost (who puts
|
|
// _1 etc. in global namespace)
|
|
std::placeholders::_1,
|
|
std::placeholders::_2);
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
log_time("box_and_bind");
|
|
#endif
|
|
// Run the self intersection algorithm with given cutoff size
|
|
CGAL::box_self_intersection_d(boxes.begin(), boxes.end(),cb,std::ptrdiff_t(params.cutoff));
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
log_time("box_intersection_d");
|
|
#endif
|
|
try{
|
|
process_intersecting_boxes();
|
|
}catch(int e)
|
|
{
|
|
// Rethrow if not IGL_FIRST_HIT_EXCEPTION
|
|
if(e != IGL_FIRST_HIT_EXCEPTION)
|
|
{
|
|
throw e;
|
|
}
|
|
// Otherwise just fall through
|
|
}
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
log_time("resolve_intersection");
|
|
#endif
|
|
|
|
// Convert lIF to Eigen matrix
|
|
assert(lIF.size()%2 == 0);
|
|
IF.resize(lIF.size()/2,2);
|
|
{
|
|
Index i=0;
|
|
for(
|
|
typename IndexList::const_iterator ifit = lIF.begin();
|
|
ifit!=lIF.end();
|
|
)
|
|
{
|
|
IF(i,0) = (*ifit);
|
|
ifit++;
|
|
IF(i,1) = (*ifit);
|
|
ifit++;
|
|
i++;
|
|
}
|
|
}
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
log_time("store_intersecting_face_pairs");
|
|
#endif
|
|
|
|
if(params.detect_only)
|
|
{
|
|
return;
|
|
}
|
|
|
|
remesh_intersections(
|
|
V,F,T,offending,
|
|
params.stitch_all,params.slow_and_more_precise_rounding,VV,FF,J,IM);
|
|
|
|
#ifdef IGL_SELFINTERSECTMESH_TIMING
|
|
log_time("remesh_intersection");
|
|
#endif
|
|
}
|
|
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline void igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::mark_offensive(const Index f)
|
|
{
|
|
using namespace std;
|
|
lIF.push_back(f);
|
|
if(offending.count(f) == 0)
|
|
{
|
|
// first time marking, initialize with new id and empty list
|
|
offending[f] = {};
|
|
}
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline void igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::count_intersection(
|
|
const Index fa,
|
|
const Index fb)
|
|
{
|
|
std::lock_guard<std::mutex> guard(m_offending_lock);
|
|
mark_offensive(fa);
|
|
mark_offensive(fb);
|
|
this->count++;
|
|
// We found the first intersection
|
|
if(params.first_only && this->count >= 1)
|
|
{
|
|
throw IGL_FIRST_HIT_EXCEPTION;
|
|
}
|
|
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline bool igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::intersect(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb)
|
|
{
|
|
// Determine whether there is an intersection
|
|
if(!CGAL::do_intersect(A,B))
|
|
{
|
|
return false;
|
|
}
|
|
count_intersection(fa,fb);
|
|
if(!params.detect_only)
|
|
{
|
|
// Construct intersection
|
|
CGAL::Object result = CGAL::intersection(A,B);
|
|
// Could avoid this mutex if `offending` was per-thread and passed as input
|
|
// reference.
|
|
std::lock_guard<std::mutex> guard(m_offending_lock);
|
|
offending[fa].push_back({fb, result});
|
|
offending[fb].push_back({fa, result});
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline bool igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::single_shared_vertex(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb,
|
|
const Index va,
|
|
const Index vb)
|
|
{
|
|
if(single_shared_vertex(A,B,fa,fb,va))
|
|
{
|
|
return true;
|
|
}
|
|
return single_shared_vertex(B,A,fb,fa,vb);
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline bool igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::single_shared_vertex(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb,
|
|
const Index va)
|
|
{
|
|
// This was not a good idea. It will not handle coplanar triangles well.
|
|
using namespace std;
|
|
Segment_3 sa(
|
|
A.vertex((va+1)%3),
|
|
A.vertex((va+2)%3));
|
|
|
|
if(CGAL::do_intersect(sa,B))
|
|
{
|
|
// can't put count_intersection(fa,fb) here since we use intersect below
|
|
// and then it will be counted twice.
|
|
if(params.detect_only)
|
|
{
|
|
count_intersection(fa,fb);
|
|
return true;
|
|
}
|
|
CGAL::Object result = CGAL::intersection(sa,B);
|
|
if(const Point_3 * p = CGAL::object_cast<Point_3 >(&result))
|
|
{
|
|
// Single intersection --> segment from shared point to intersection
|
|
CGAL::Object seg = CGAL::make_object(Segment_3(
|
|
A.vertex(va),
|
|
*p));
|
|
count_intersection(fa,fb);
|
|
std::lock_guard<std::mutex> guard(m_offending_lock);
|
|
offending[fa].push_back({fb, seg});
|
|
offending[fb].push_back({fa, seg});
|
|
return true;
|
|
}else if(CGAL::object_cast<Segment_3 >(&result))
|
|
{
|
|
// Need to do full test. Intersection could be a general poly.
|
|
bool test = intersect(A,B,fa,fb);
|
|
((void)test);
|
|
assert(test && "intersect should agree with do_intersect");
|
|
return true;
|
|
}else
|
|
{
|
|
// Should never happen.
|
|
assert(false && "Segment ∩ triangle neither point nor segment?");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline bool igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::double_shared_vertex(
|
|
const Triangle_3 & A,
|
|
const Triangle_3 & B,
|
|
const Index fa,
|
|
const Index fb,
|
|
const std::vector<std::pair<Index,Index> > shared)
|
|
{
|
|
using namespace std;
|
|
|
|
auto opposite_vertex = [](const Index a0, const Index a1) {
|
|
// get opposite index of A
|
|
int a2=-1;
|
|
for(int c=0;c<3;++c)
|
|
if(c!=a0 && c!=a1) {
|
|
a2 = c;
|
|
break;
|
|
}
|
|
assert(a2 != -1);
|
|
return a2;
|
|
};
|
|
|
|
// must be co-planar
|
|
Index a2 = opposite_vertex(shared[0].first, shared[1].first);
|
|
if (! B.supporting_plane().has_on(A.vertex(a2)))
|
|
return false;
|
|
|
|
Index b2 = opposite_vertex(shared[0].second, shared[1].second);
|
|
|
|
if (int(CGAL::coplanar_orientation(A.vertex(shared[0].first), A.vertex(shared[1].first), A.vertex(a2))) *
|
|
int(CGAL::coplanar_orientation(B.vertex(shared[0].second), B.vertex(shared[1].second), B.vertex(b2))) < 0)
|
|
// There is certainly no self intersection as the non-shared triangle vertices lie on opposite sides of the shared edge.
|
|
return false;
|
|
|
|
// Since A and B are non-degenerate the intersection must be a polygon
|
|
// (triangle). Either
|
|
// - the vertex of A (B) opposite the shared edge of lies on B (A), or
|
|
// - an edge of A intersects and edge of B without sharing a vertex
|
|
//
|
|
// Determine if the vertex opposite edge (a0,a1) in triangle A lies in
|
|
// (intersects) triangle B
|
|
const auto & opposite_point_inside = [](
|
|
const Triangle_3 & A, const Index a2, const Triangle_3 & B)
|
|
-> bool
|
|
{
|
|
return CGAL::do_intersect(A.vertex(a2),B);
|
|
};
|
|
|
|
// Determine if edge opposite vertex va in triangle A intersects edge
|
|
// opposite vertex vb in triangle B.
|
|
const auto & opposite_edges_intersect = [](
|
|
const Triangle_3 & A, const Index va,
|
|
const Triangle_3 & B, const Index vb) -> bool
|
|
{
|
|
Segment_3 sa( A.vertex((va+1)%3), A.vertex((va+2)%3));
|
|
Segment_3 sb( B.vertex((vb+1)%3), B.vertex((vb+2)%3));
|
|
bool ret = CGAL::do_intersect(sa,sb);
|
|
return ret;
|
|
};
|
|
|
|
if(
|
|
!opposite_point_inside(A,a2,B) &&
|
|
!opposite_point_inside(B,b2,A) &&
|
|
!opposite_edges_intersect(A,shared[0].first,B,shared[1].second) &&
|
|
!opposite_edges_intersect(A,shared[1].first,B,shared[0].second))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// there is an intersection indeed
|
|
count_intersection(fa,fb);
|
|
if(params.detect_only)
|
|
{
|
|
return true;
|
|
}
|
|
// Construct intersection
|
|
try
|
|
{
|
|
// This can fail for Epick but not Epeck
|
|
CGAL::Object result = CGAL::intersection(A,B);
|
|
if(!result.empty())
|
|
{
|
|
if(CGAL::object_cast<Segment_3 >(&result))
|
|
{
|
|
// not coplanar
|
|
assert(false &&
|
|
"Co-planar non-degenerate triangles should intersect over triangle");
|
|
return false;
|
|
} else if(CGAL::object_cast<Point_3 >(&result))
|
|
{
|
|
// this "shouldn't" happen but does for inexact
|
|
assert(false &&
|
|
"Co-planar non-degenerate triangles should intersect over triangle");
|
|
return false;
|
|
} else
|
|
{
|
|
// Triangle object
|
|
std::lock_guard<std::mutex> guard(m_offending_lock);
|
|
offending[fa].push_back({fb, result});
|
|
offending[fb].push_back({fa, result});
|
|
return true;
|
|
}
|
|
}else
|
|
{
|
|
// CGAL::intersection is disagreeing with do_intersect
|
|
assert(false && "CGAL::intersection should agree with predicate tests");
|
|
return false;
|
|
}
|
|
}catch(...)
|
|
{
|
|
// This catches some cgal assertion:
|
|
// CGAL error: assertion violation!
|
|
// Expression : is_finite(d)
|
|
// File : /opt/local/include/CGAL/GMP/Gmpq_type.h
|
|
// Line : 132
|
|
// Explanation:
|
|
// But only if NDEBUG is not defined, otherwise there's an uncaught
|
|
// "Floating point exception: 8" SIGFPE
|
|
return false;
|
|
}
|
|
// No intersection.
|
|
return false;
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline void igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::box_intersect(
|
|
const Box& a,
|
|
const Box& b)
|
|
{
|
|
candidate_triangle_pairs.push_back({a.handle(), b.handle()});
|
|
}
|
|
|
|
template <
|
|
typename Kernel,
|
|
typename DerivedV,
|
|
typename DerivedF,
|
|
typename DerivedVV,
|
|
typename DerivedFF,
|
|
typename DerivedIF,
|
|
typename DerivedJ,
|
|
typename DerivedIM>
|
|
inline void igl::copyleft::cgal::SelfIntersectMesh<
|
|
Kernel,
|
|
DerivedV,
|
|
DerivedF,
|
|
DerivedVV,
|
|
DerivedFF,
|
|
DerivedIF,
|
|
DerivedJ,
|
|
DerivedIM>::process_intersecting_boxes()
|
|
{
|
|
std::mutex exception_mutex;
|
|
bool exception_fired = false;
|
|
int exception = -1;
|
|
// Eventually switching to igl::parallel_for would be good, but currently
|
|
// igl::parallel_for does not provide a way to catch exceptions fired on a
|
|
// spawned thread _outside_ of its loop-chunk which is the mechanism used here
|
|
// to bail out early when `first_only=true` to avoid
|
|
// O(#candidate_triangle_pairs) behavior.
|
|
auto process_chunk = [&]( const size_t first, const size_t last) -> void
|
|
{
|
|
try
|
|
{
|
|
assert(last >= first);
|
|
|
|
for (size_t i=first; i<last; i++)
|
|
{
|
|
if(exception_fired) return;
|
|
Index fa=T.size(), fb=T.size();
|
|
{
|
|
const auto& tri_pair = candidate_triangle_pairs[i];
|
|
fa = tri_pair.first - T.begin();
|
|
fb = tri_pair.second - T.begin();
|
|
}
|
|
assert(fa < T.size());
|
|
assert(fb < T.size());
|
|
|
|
if(exception_fired) return;
|
|
|
|
const Triangle_3& A = T[fa];
|
|
const Triangle_3& B = T[fb];
|
|
|
|
// Number of combinatorially shared vertices
|
|
Index comb_shared_vertices = 0;
|
|
// Number of geometrically shared vertices (*not* including
|
|
// combinatorially shared)
|
|
Index geo_shared_vertices = 0;
|
|
// Keep track of shared vertex indices
|
|
std::vector<std::pair<Index,Index> > shared;
|
|
Index ea,eb;
|
|
for(ea=0;ea<3;ea++)
|
|
{
|
|
for(eb=0;eb<3;eb++)
|
|
{
|
|
if(F(fa,ea) == F(fb,eb))
|
|
{
|
|
comb_shared_vertices++;
|
|
shared.emplace_back(ea,eb);
|
|
}else if(A.vertex(ea) == B.vertex(eb))
|
|
{
|
|
geo_shared_vertices++;
|
|
shared.emplace_back(ea,eb);
|
|
}
|
|
}
|
|
}
|
|
const Index total_shared_vertices =
|
|
comb_shared_vertices + geo_shared_vertices;
|
|
if(exception_fired) return;
|
|
|
|
if(comb_shared_vertices== 3)
|
|
{
|
|
assert(shared.size() == 3);
|
|
// Combinatorially duplicate face, these should be removed by
|
|
// preprocessing
|
|
continue;
|
|
}
|
|
if(total_shared_vertices== 3)
|
|
{
|
|
assert(shared.size() == 3);
|
|
// Geometrically duplicate face, these should be removed by
|
|
// preprocessing
|
|
continue;
|
|
}
|
|
if(total_shared_vertices == 2)
|
|
{
|
|
assert(shared.size() == 2);
|
|
// Q: What about coplanar?
|
|
//
|
|
// o o
|
|
// |\ /|
|
|
// | \/ |
|
|
// | /\ |
|
|
// |/ \|
|
|
// o----o
|
|
double_shared_vertex(A,B,fa,fb,shared);
|
|
continue;
|
|
}
|
|
assert(total_shared_vertices<=1);
|
|
if(total_shared_vertices==1)
|
|
{
|
|
single_shared_vertex(A,B,fa,fb,shared[0].first,shared[0].second);
|
|
}else
|
|
{
|
|
intersect(A,B,fa,fb);
|
|
}
|
|
}
|
|
}catch(int e)
|
|
{
|
|
std::lock_guard<std::mutex> exception_lock(exception_mutex);
|
|
exception_fired = true;
|
|
exception = e;
|
|
}
|
|
};
|
|
const size_t num_threads = default_num_threads();
|
|
assert(num_threads > 0);
|
|
const size_t num_pairs = candidate_triangle_pairs.size();
|
|
const size_t chunk_size = num_pairs / num_threads;
|
|
std::vector<std::thread> threads;
|
|
for (size_t i=0; i<num_threads-1; i++)
|
|
{
|
|
threads.emplace_back(process_chunk, i*chunk_size, (i+1)*chunk_size);
|
|
}
|
|
// Do some work in the master thread.
|
|
process_chunk((num_threads-1)*chunk_size, num_pairs);
|
|
for (auto& t : threads)
|
|
{
|
|
if (t.joinable()) t.join();
|
|
}
|
|
if(exception_fired) throw exception;
|
|
//process_chunk(0, candidate_triangle_pairs.size());
|
|
}
|
|
|
|
#endif
|