Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHoward Trickey <howard.trickey@gmail.com>2020-08-28 17:56:44 +0300
committerHoward Trickey <howard.trickey@gmail.com>2020-08-28 18:01:06 +0300
commit9e09b5c418c0a436e3c84ccf38c065527988b0a0 (patch)
treec0e81b8834aaf27c22a2734e364452fa2af5c6c1 /source/blender/blenlib/intern/mesh_intersect.cc
parent4a17508c6d2a24dfb7c018ae49c80f12b4d3e610 (diff)
Merge newboolean branch into master.
This is for design task T67744, Boolean Redesign. It adds a choice of solver to the Boolean modifier and the Intersect (Boolean) and Intersect (Knife) tools. The 'Fast' choice is the current Bmesh boolean. The new 'Exact' choice is a more advanced algorithm that supports overlapping geometry and uses more robust calculations, but is slower than the Fast choice. The default with this commit is set to 'Exact'. We can decide before the 2.91 release whether or not this is the right choice, but this choice now will get us more testing and feedback on the new code.
Diffstat (limited to 'source/blender/blenlib/intern/mesh_intersect.cc')
-rw-r--r--source/blender/blenlib/intern/mesh_intersect.cc3304
1 files changed, 3304 insertions, 0 deletions
diff --git a/source/blender/blenlib/intern/mesh_intersect.cc b/source/blender/blenlib/intern/mesh_intersect.cc
new file mode 100644
index 00000000000..d973775a797
--- /dev/null
+++ b/source/blender/blenlib/intern/mesh_intersect.cc
@@ -0,0 +1,3304 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/** \file
+ * \ingroup bli
+ */
+
+/* The #blender::meshintersect API needs GMP. */
+#ifdef WITH_GMP
+
+# include <algorithm>
+# include <fstream>
+# include <iostream>
+
+# include "BLI_allocator.hh"
+# include "BLI_array.hh"
+# include "BLI_assert.h"
+# include "BLI_delaunay_2d.h"
+# include "BLI_double3.hh"
+# include "BLI_float3.hh"
+# include "BLI_hash.hh"
+# include "BLI_kdopbvh.h"
+# include "BLI_map.hh"
+# include "BLI_math_boolean.hh"
+# include "BLI_math_mpq.hh"
+# include "BLI_mpq2.hh"
+# include "BLI_mpq3.hh"
+# include "BLI_span.hh"
+# include "BLI_task.h"
+# include "BLI_threads.h"
+# include "BLI_vector.hh"
+# include "BLI_vector_set.hh"
+
+# include "PIL_time.h"
+
+# include "BLI_mesh_intersect.hh"
+
+// # define PERFDEBUG
+
+namespace blender::meshintersect {
+
+# ifdef PERFDEBUG
+static void perfdata_init(void);
+static void incperfcount(int countnum);
+static void bumpperfcount(int countnum, int amt);
+static void doperfmax(int maxnum, int val);
+static void dump_perfdata(void);
+# endif
+
+/** For debugging, can disable threading in intersect code with this static constant. */
+static constexpr bool intersect_use_threading = true;
+
+Vert::Vert(const mpq3 &mco, const double3 &dco, int id, int orig)
+ : co_exact(mco), co(dco), id(id), orig(orig)
+{
+}
+
+bool Vert::operator==(const Vert &other) const
+{
+ return this->co_exact == other.co_exact;
+}
+
+uint64_t Vert::hash() const
+{
+ return co_exact.hash();
+}
+
+std::ostream &operator<<(std::ostream &os, const Vert *v)
+{
+ os << "v" << v->id;
+ if (v->orig != NO_INDEX) {
+ os << "o" << v->orig;
+ }
+ os << v->co;
+ return os;
+}
+
+bool Plane::operator==(const Plane &other) const
+{
+ return norm_exact == other.norm_exact && d_exact == other.d_exact;
+}
+
+void Plane::make_canonical()
+{
+ if (norm_exact[0] != 0) {
+ mpq_class den = norm_exact[0];
+ norm_exact = mpq3(1, norm_exact[1] / den, norm_exact[2] / den);
+ d_exact = d_exact / den;
+ }
+ else if (norm_exact[1] != 0) {
+ mpq_class den = norm_exact[1];
+ norm_exact = mpq3(0, 1, norm_exact[2] / den);
+ d_exact = d_exact / den;
+ }
+ else {
+ if (norm_exact[2] != 0) {
+ mpq_class den = norm_exact[2];
+ norm_exact = mpq3(0, 0, 1);
+ d_exact = d_exact / den;
+ }
+ else {
+ /* A degenerate plane. */
+ d_exact = 0;
+ }
+ }
+ norm = double3(norm_exact[0].get_d(), norm_exact[1].get_d(), norm_exact[2].get_d());
+ d = d_exact.get_d();
+}
+
+Plane::Plane(const mpq3 &norm_exact, const mpq_class &d_exact)
+ : norm_exact(norm_exact), d_exact(d_exact)
+{
+ norm = double3(norm_exact[0].get_d(), norm_exact[1].get_d(), norm_exact[2].get_d());
+ d = d_exact.get_d();
+}
+
+Plane::Plane(const double3 &norm, const double d) : norm(norm), d(d)
+{
+ norm_exact = mpq3(0, 0, 0); /* Marks as "exact not yet populated". */
+}
+
+/** This is wrong for degenerate planes, but we don't expect to call it on those. */
+bool Plane::exact_populated() const
+{
+ return norm_exact[0] != 0 || norm_exact[1] != 0 || norm_exact[2] != 0;
+}
+
+uint64_t Plane::hash() const
+{
+ constexpr uint64_t h1 = 33;
+ constexpr uint64_t h2 = 37;
+ constexpr uint64_t h3 = 39;
+ uint64_t hashx = hash_mpq_class(this->norm_exact.x);
+ uint64_t hashy = hash_mpq_class(this->norm_exact.y);
+ uint64_t hashz = hash_mpq_class(this->norm_exact.z);
+ uint64_t hashd = hash_mpq_class(this->d_exact);
+ uint64_t ans = hashx ^ (hashy * h1) ^ (hashz * h1 * h2) ^ (hashd * h1 * h2 * h3);
+ return ans;
+}
+
+std::ostream &operator<<(std::ostream &os, const Plane *plane)
+{
+ os << "[" << plane->norm << ";" << plane->d << "]";
+ return os;
+}
+
+Face::Face(
+ Span<const Vert *> verts, int id, int orig, Span<int> edge_origs, Span<bool> is_intersect)
+ : vert(verts), edge_orig(edge_origs), is_intersect(is_intersect), id(id), orig(orig)
+{
+}
+
+Face::Face(Span<const Vert *> verts, int id, int orig) : vert(verts), id(id), orig(orig)
+{
+}
+
+void Face::populate_plane(bool need_exact)
+{
+ if (plane != nullptr) {
+ if (!need_exact || plane->exact_populated()) {
+ return;
+ }
+ }
+ if (need_exact) {
+ mpq3 normal_exact;
+ if (vert.size() > 3) {
+ Array<mpq3> co(vert.size());
+ for (int i : index_range()) {
+ co[i] = vert[i]->co_exact;
+ }
+ normal_exact = mpq3::cross_poly(co);
+ }
+ else {
+ mpq3 tr02 = vert[0]->co_exact - vert[2]->co_exact;
+ mpq3 tr12 = vert[1]->co_exact - vert[2]->co_exact;
+ normal_exact = mpq3::cross(tr02, tr12);
+ }
+ mpq_class d_exact = -mpq3::dot(normal_exact, vert[0]->co_exact);
+ plane = new Plane(normal_exact, d_exact);
+ }
+ else {
+ double3 normal;
+ if (vert.size() > 3) {
+ Array<double3> co(vert.size());
+ for (int i : index_range()) {
+ co[i] = vert[i]->co;
+ }
+ normal = double3::cross_poly(co);
+ }
+ else {
+ double3 tr02 = vert[0]->co - vert[2]->co;
+ double3 tr12 = vert[1]->co - vert[2]->co;
+ normal = double3::cross_high_precision(tr02, tr12);
+ }
+ double d = -double3::dot(normal, vert[0]->co);
+ plane = new Plane(normal, d);
+ }
+}
+
+Face::~Face()
+{
+ if (plane != nullptr) {
+ delete plane;
+ }
+}
+
+bool Face::operator==(const Face &other) const
+{
+ if (this->size() != other.size()) {
+ return false;
+ }
+ for (FacePos i : index_range()) {
+ /* Can test pointer equality since we will have
+ * unique vert pointers for unique co_equal's. */
+ if (this->vert[i] != other.vert[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Face::cyclic_equal(const Face &other) const
+{
+ if (this->size() != other.size()) {
+ return false;
+ }
+ int flen = this->size();
+ for (FacePos start : index_range()) {
+ for (FacePos start_other : index_range()) {
+ bool ok = true;
+ for (int i = 0; ok && i < flen; ++i) {
+ FacePos p = (start + i) % flen;
+ FacePos p_other = (start_other + i) % flen;
+ if (this->vert[p] != other.vert[p_other]) {
+ ok = false;
+ }
+ }
+ if (ok) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+std::ostream &operator<<(std::ostream &os, const Face *f)
+{
+ os << "f" << f->id << "o" << f->orig << "[";
+ for (const Vert *v : *f) {
+ os << "v" << v->id;
+ if (v->orig != NO_INDEX) {
+ os << "o" << v->orig;
+ }
+ if (v != f->vert[f->size() - 1]) {
+ os << " ";
+ }
+ }
+ os << "]";
+ if (f->orig != NO_INDEX) {
+ os << "o" << f->orig;
+ }
+ os << " e_orig[";
+ for (int i : f->index_range()) {
+ os << f->edge_orig[i];
+ if (f->is_intersect[i]) {
+ os << "#";
+ }
+ if (i != f->size() - 1) {
+ os << " ";
+ }
+ }
+ os << "]";
+ return os;
+}
+
+/**
+ * Un-comment the following to try using a spin-lock instead of
+ * a mutex in the arena allocation routines.
+ * Initial tests showed that it doesn't seem to help very much,
+ * if at all, to use a spin-lock.
+ */
+// #define USE_SPINLOCK
+
+/**
+ * #IMeshArena is the owner of the Vert and Face resources used
+ * during a run of one of the mesh-intersect main functions.
+ * It also keeps has a hash table of all Verts created so that it can
+ * ensure that only one instance of a Vert with a given co_exact will
+ * exist. I.e., it de-duplicates the vertices.
+ */
+class IMeshArena::IMeshArenaImpl : NonCopyable, NonMovable {
+
+ /**
+ * Don't use Vert itself as key since resizing may move
+ * pointers to the Vert around, and we need to have those pointers
+ * stay the same throughout the lifetime of the #IMeshArena.
+ */
+ struct VSetKey {
+ Vert *vert;
+
+ VSetKey(Vert *p) : vert(p)
+ {
+ }
+
+ uint32_t hash() const
+ {
+ return vert->hash();
+ }
+
+ bool operator==(const VSetKey &other) const
+ {
+ return *this->vert == *other.vert;
+ }
+ };
+
+ VectorSet<VSetKey> vset_; /* TODO: replace with Set */
+
+ /**
+ * Ownership of the Vert memory is here, so destroying this reclaims that memory.
+ *
+ * TODO: replace these with pooled allocation, and just destroy the pools at the end.
+ */
+ Vector<std::unique_ptr<Vert>> allocated_verts_;
+ Vector<std::unique_ptr<Face>> allocated_faces_;
+
+ /* Use these to allocate ids when Verts and Faces are allocated. */
+ int next_vert_id_ = 0;
+ int next_face_id_ = 0;
+
+ /* Need a lock when multi-threading to protect allocation of new elements. */
+# ifdef USE_SPINLOCK
+ SpinLock lock_;
+# else
+ ThreadMutex *mutex_;
+# endif
+
+ public:
+ IMeshArenaImpl()
+ {
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_init(&lock_);
+# else
+ mutex_ = BLI_mutex_alloc();
+# endif
+ }
+ }
+ ~IMeshArenaImpl()
+ {
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_end(&lock_);
+# else
+ BLI_mutex_free(mutex_);
+# endif
+ }
+ }
+
+ void reserve(int vert_num_hint, int face_num_hint)
+ {
+ vset_.reserve(vert_num_hint);
+ allocated_verts_.reserve(vert_num_hint);
+ allocated_faces_.reserve(face_num_hint);
+ }
+
+ int tot_allocated_verts() const
+ {
+ return allocated_verts_.size();
+ }
+
+ int tot_allocated_faces() const
+ {
+ return allocated_faces_.size();
+ }
+
+ const Vert *add_or_find_vert(const mpq3 &co, int orig)
+ {
+ double3 dco(co[0].get_d(), co[1].get_d(), co[2].get_d());
+ return add_or_find_vert(co, dco, orig);
+ }
+
+ const Vert *add_or_find_vert(const double3 &co, int orig)
+ {
+ mpq3 mco(co[0], co[1], co[2]);
+ return add_or_find_vert(mco, co, orig);
+ }
+
+ Face *add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs, Span<bool> is_intersect)
+ {
+ Face *f = new Face(verts, next_face_id_++, orig, edge_origs, is_intersect);
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_lock(&lock_);
+# else
+ BLI_mutex_lock(mutex_);
+# endif
+ }
+ allocated_faces_.append(std::unique_ptr<Face>(f));
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_unlock(&lock_);
+# else
+ BLI_mutex_unlock(mutex_);
+# endif
+ }
+ return f;
+ }
+
+ Face *add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs)
+ {
+ Array<bool> is_intersect(verts.size(), false);
+ return add_face(verts, orig, edge_origs, is_intersect);
+ }
+
+ Face *add_face(Span<const Vert *> verts, int orig)
+ {
+ Array<int> edge_origs(verts.size(), NO_INDEX);
+ Array<bool> is_intersect(verts.size(), false);
+ return add_face(verts, orig, edge_origs, is_intersect);
+ }
+
+ const Vert *find_vert(const mpq3 &co)
+ {
+ const Vert *ans;
+ Vert vtry(co, double3(), NO_INDEX, NO_INDEX);
+ VSetKey vskey(&vtry);
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_lock(&lock_);
+# else
+ BLI_mutex_lock(mutex_);
+# endif
+ }
+ int i = vset_.index_of_try(vskey);
+ if (i == -1) {
+ ans = nullptr;
+ }
+ else {
+ ans = vset_[i].vert;
+ }
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_unlock(&lock_);
+# else
+ BLI_mutex_unlock(mutex_);
+# endif
+ }
+ return ans;
+ }
+
+ /**
+ * This is slow. Only used for unit tests right now.
+ * Since it is only used for that purpose, access is not lock-protected.
+ * The argument vs can be a cyclic shift of the actual stored Face.
+ */
+ const Face *find_face(Span<const Vert *> vs)
+ {
+ Array<int> eorig(vs.size(), NO_INDEX);
+ Array<bool> is_intersect(vs.size(), false);
+ Face ftry(vs, NO_INDEX, NO_INDEX, eorig, is_intersect);
+ for (const int i : allocated_faces_.index_range()) {
+ if (ftry.cyclic_equal(*allocated_faces_[i])) {
+ return allocated_faces_[i].get();
+ }
+ }
+ return nullptr;
+ }
+
+ private:
+ const Vert *add_or_find_vert(const mpq3 &mco, const double3 &dco, int orig)
+ {
+ /* Don't allocate Vert yet, in case it is already there. */
+ Vert vtry(mco, dco, NO_INDEX, NO_INDEX);
+ const Vert *ans;
+ VSetKey vskey(&vtry);
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_lock(&lock_);
+# else
+ BLI_mutex_lock(mutex_);
+# endif
+ }
+ int i = vset_.index_of_try(vskey);
+ if (i == -1) {
+ vskey.vert = new Vert(mco, dco, next_vert_id_++, orig);
+ vset_.add_new(vskey);
+ allocated_verts_.append(std::unique_ptr<Vert>(vskey.vert));
+ ans = vskey.vert;
+ }
+ else {
+ /* It was a duplicate, so return the existing one.
+ * Note that the returned Vert may have a different orig.
+ * This is the intended semantics: if the Vert already
+ * exists then we are merging verts and using the first-seen
+ * one as the canonical one. */
+ ans = vset_[i].vert;
+ }
+ if (intersect_use_threading) {
+# ifdef USE_SPINLOCK
+ BLI_spin_unlock(&lock_);
+# else
+ BLI_mutex_unlock(mutex_);
+# endif
+ }
+ return ans;
+ };
+};
+
+IMeshArena::IMeshArena()
+{
+ pimpl_ = std::unique_ptr<IMeshArenaImpl>(new IMeshArenaImpl());
+}
+
+IMeshArena::~IMeshArena()
+{
+}
+
+void IMeshArena::reserve(int vert_num_hint, int face_num_hint)
+{
+ pimpl_->reserve(vert_num_hint, face_num_hint);
+}
+
+int IMeshArena::tot_allocated_verts() const
+{
+ return pimpl_->tot_allocated_verts();
+}
+
+int IMeshArena::tot_allocated_faces() const
+{
+ return pimpl_->tot_allocated_faces();
+}
+
+const Vert *IMeshArena::add_or_find_vert(const mpq3 &co, int orig)
+{
+ return pimpl_->add_or_find_vert(co, orig);
+}
+
+Face *IMeshArena::add_face(Span<const Vert *> verts,
+ int orig,
+ Span<int> edge_origs,
+ Span<bool> is_intersect)
+{
+ return pimpl_->add_face(verts, orig, edge_origs, is_intersect);
+}
+
+Face *IMeshArena::add_face(Span<const Vert *> verts, int orig, Span<int> edge_origs)
+{
+ return pimpl_->add_face(verts, orig, edge_origs);
+}
+
+Face *IMeshArena::add_face(Span<const Vert *> verts, int orig)
+{
+ return pimpl_->add_face(verts, orig);
+}
+
+const Vert *IMeshArena::add_or_find_vert(const double3 &co, int orig)
+{
+ return pimpl_->add_or_find_vert(co, orig);
+}
+
+const Vert *IMeshArena::find_vert(const mpq3 &co) const
+{
+ return pimpl_->find_vert(co);
+}
+
+const Face *IMeshArena::find_face(Span<const Vert *> verts) const
+{
+ return pimpl_->find_face(verts);
+}
+
+void IMesh::set_faces(Span<Face *> faces)
+{
+ face_ = faces;
+}
+
+int IMesh::lookup_vert(const Vert *v) const
+{
+ BLI_assert(vert_populated_);
+ return vert_to_index_.lookup_default(v, NO_INDEX);
+}
+
+void IMesh::populate_vert()
+{
+ /* This is likely an overestimate, since verts are shared between
+ * faces. It is ok if estimate is over or even under. */
+ constexpr int ESTIMATE_VERTS_PER_FACE = 4;
+ int estimate_num_verts = ESTIMATE_VERTS_PER_FACE * face_.size();
+ populate_vert(estimate_num_verts);
+}
+
+void IMesh::populate_vert(int max_verts)
+{
+ if (vert_populated_) {
+ return;
+ }
+ vert_to_index_.reserve(max_verts);
+ int next_allocate_index = 0;
+ for (const Face *f : face_) {
+ for (const Vert *v : *f) {
+ if (v->id == 1) {
+ }
+ int index = vert_to_index_.lookup_default(v, NO_INDEX);
+ if (index == NO_INDEX) {
+ BLI_assert(next_allocate_index < UINT_MAX - 2);
+ vert_to_index_.add(v, next_allocate_index++);
+ }
+ }
+ }
+ int tot_v = next_allocate_index;
+ vert_ = Array<const Vert *>(tot_v);
+ for (auto item : vert_to_index_.items()) {
+ int index = item.value;
+ BLI_assert(index < tot_v);
+ vert_[index] = item.key;
+ }
+ /* Easier debugging (at least when there are no merged input verts)
+ * if output vert order is same as input, with new verts at the end.
+ * TODO: when all debugged, set fix_order = false. */
+ const bool fix_order = true;
+ if (fix_order) {
+ std::sort(vert_.begin(), vert_.end(), [](const Vert *a, const Vert *b) {
+ if (a->orig != NO_INDEX && b->orig != NO_INDEX) {
+ return a->orig < b->orig;
+ }
+ if (a->orig != NO_INDEX) {
+ return true;
+ }
+ if (b->orig != NO_INDEX) {
+ return false;
+ }
+ return a->id < b->id;
+ });
+ for (int i : vert_.index_range()) {
+ const Vert *v = vert_[i];
+ vert_to_index_.add_overwrite(v, i);
+ }
+ }
+ vert_populated_ = true;
+}
+
+void IMesh::erase_face_positions(int f_index, Span<bool> face_pos_erase, IMeshArena *arena)
+{
+ const Face *cur_f = this->face(f_index);
+ int cur_len = cur_f->size();
+ int num_to_erase = 0;
+ for (int i : cur_f->index_range()) {
+ if (face_pos_erase[i]) {
+ ++num_to_erase;
+ }
+ }
+ if (num_to_erase == 0) {
+ return;
+ }
+ int new_len = cur_len - num_to_erase;
+ if (new_len < 3) {
+ /* Invalid erase. Don't do anything. */
+ return;
+ }
+ Array<const Vert *> new_vert(new_len);
+ Array<int> new_edge_orig(new_len);
+ Array<bool> new_is_intersect(new_len);
+ int new_index = 0;
+ for (int i : cur_f->index_range()) {
+ if (!face_pos_erase[i]) {
+ new_vert[new_index] = (*cur_f)[i];
+ new_edge_orig[new_index] = cur_f->edge_orig[i];
+ new_is_intersect[new_index] = cur_f->is_intersect[i];
+ ++new_index;
+ }
+ }
+ BLI_assert(new_index == new_len);
+ this->face_[f_index] = arena->add_face(new_vert, cur_f->orig, new_edge_orig, new_is_intersect);
+}
+
+std::ostream &operator<<(std::ostream &os, const IMesh &mesh)
+{
+ if (mesh.has_verts()) {
+ os << "Verts:\n";
+ int i = 0;
+ for (const Vert *v : mesh.vertices()) {
+ os << i << ": " << v << "\n";
+ ++i;
+ }
+ }
+ os << "\nFaces:\n";
+ int i = 0;
+ for (const Face *f : mesh.faces()) {
+ os << i << ": " << f << "\n";
+ if (f->plane != nullptr) {
+ os << " plane=" << f->plane << " eorig=[";
+ for (Face::FacePos p = 0; p < f->size(); ++p) {
+ os << f->edge_orig[p] << " ";
+ }
+ os << "]\n";
+ }
+ ++i;
+ }
+ return os;
+}
+
+struct BoundingBox {
+ float3 min{FLT_MAX, FLT_MAX, FLT_MAX};
+ float3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX};
+
+ BoundingBox() = default;
+ BoundingBox(const float3 &min, const float3 &max) : min(min), max(max)
+ {
+ }
+ BoundingBox(const BoundingBox &other) : min(other.min), max(other.max)
+ {
+ }
+ BoundingBox(BoundingBox &&other) noexcept : min(std::move(other.min)), max(std::move(other.max))
+ {
+ }
+ ~BoundingBox() = default;
+ BoundingBox operator=(const BoundingBox &other)
+ {
+ if (this != &other) {
+ min = other.min;
+ max = other.max;
+ }
+ return *this;
+ }
+ BoundingBox operator=(BoundingBox &&other) noexcept
+ {
+ min = std::move(other.min);
+ max = std::move(other.max);
+ return *this;
+ }
+
+ void combine(const float3 &p)
+ {
+ min.x = min_ff(min.x, p.x);
+ min.y = min_ff(min.y, p.y);
+ min.z = min_ff(min.z, p.z);
+ max.x = max_ff(max.x, p.x);
+ max.y = max_ff(max.y, p.y);
+ max.z = max_ff(max.z, p.z);
+ }
+
+ void combine(const double3 &p)
+ {
+ min.x = min_ff(min.x, static_cast<float>(p.x));
+ min.y = min_ff(min.y, static_cast<float>(p.y));
+ min.z = min_ff(min.z, static_cast<float>(p.z));
+ max.x = max_ff(max.x, static_cast<float>(p.x));
+ max.y = max_ff(max.y, static_cast<float>(p.y));
+ max.z = max_ff(max.z, static_cast<float>(p.z));
+ }
+
+ void combine(const BoundingBox &bb)
+ {
+ min.x = min_ff(min.x, bb.min.x);
+ min.y = min_ff(min.y, bb.min.y);
+ min.z = min_ff(min.z, bb.min.z);
+ max.x = max_ff(max.x, bb.max.x);
+ max.y = max_ff(max.y, bb.max.y);
+ max.z = max_ff(max.z, bb.max.z);
+ }
+
+ void expand(float pad)
+ {
+ min.x -= pad;
+ min.y -= pad;
+ min.z -= pad;
+ max.x += pad;
+ max.y += pad;
+ max.z += pad;
+ }
+};
+
+/**
+ * Assume bounding boxes have been expanded by a sufficient epsilon on all sides
+ * so that the comparisons against the bb bounds are sufficient to guarantee that
+ * if an overlap or even touching could happen, this will return true.
+ */
+static bool bbs_might_intersect(const BoundingBox &bb_a, const BoundingBox &bb_b)
+{
+ return isect_aabb_aabb_v3(bb_a.min, bb_a.max, bb_b.min, bb_b.max);
+}
+
+/**
+ * Data and functions to calculate bounding boxes and pad them, in parallel.
+ * The bounding box calculation has the additional task of calculating the maximum
+ * absolute value of any coordinate in the mesh, which will be used to calculate
+ * the pad value.
+ */
+struct BBChunkData {
+ double max_abs_val = 0.0;
+};
+
+struct BBCalcData {
+ const IMesh &im;
+ Array<BoundingBox> *face_bounding_box;
+
+ BBCalcData(const IMesh &im, Array<BoundingBox> *fbb) : im(im), face_bounding_box(fbb){};
+};
+
+static void calc_face_bb_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict tls)
+{
+ BBCalcData *bbdata = static_cast<BBCalcData *>(userdata);
+ double max_abs = 0.0;
+ const Face &face = *bbdata->im.face(iter);
+ BoundingBox &bb = (*bbdata->face_bounding_box)[iter];
+ for (const Vert *v : face) {
+ bb.combine(v->co);
+ for (int i = 0; i < 3; ++i) {
+ max_abs = max_dd(max_abs, fabs(v->co[i]));
+ }
+ }
+ BBChunkData *chunk = static_cast<BBChunkData *>(tls->userdata_chunk);
+ chunk->max_abs_val = max_dd(max_abs, chunk->max_abs_val);
+}
+
+struct BBPadData {
+ Array<BoundingBox> *face_bounding_box;
+ double pad;
+
+ BBPadData(Array<BoundingBox> *fbb, double pad) : face_bounding_box(fbb), pad(pad){};
+};
+
+static void pad_face_bb_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ BBPadData *pad_data = static_cast<BBPadData *>(userdata);
+ (*pad_data->face_bounding_box)[iter].expand(pad_data->pad);
+}
+
+static void calc_face_bb_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ BBChunkData *bbchunk_join = static_cast<BBChunkData *>(chunk_join);
+ BBChunkData *bbchunk = static_cast<BBChunkData *>(chunk);
+ bbchunk_join->max_abs_val = max_dd(bbchunk_join->max_abs_val, bbchunk->max_abs_val);
+}
+
+/**
+ * We will expand the bounding boxes by an epsilon on all sides so that
+ * the "less than" tests in isect_aabb_aabb_v3 are sufficient to detect
+ * touching or overlap.
+ */
+static Array<BoundingBox> calc_face_bounding_boxes(const IMesh &m)
+{
+ int n = m.face_size();
+ Array<BoundingBox> ans(n);
+ TaskParallelSettings settings;
+ BBCalcData data(m, &ans);
+ BBChunkData chunk_data;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.userdata_chunk = &chunk_data;
+ settings.userdata_chunk_size = sizeof(chunk_data);
+ settings.func_reduce = calc_face_bb_reduce;
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(0, n, &data, calc_face_bb_range_func, &settings);
+ double max_abs_val = chunk_data.max_abs_val;
+ constexpr float pad_factor = 10.0f;
+ float pad = max_abs_val == 0.0f ? FLT_EPSILON : 2 * FLT_EPSILON * max_abs_val;
+ pad *= pad_factor; /* For extra safety. */
+ TaskParallelSettings pad_settings;
+ BLI_parallel_range_settings_defaults(&pad_settings);
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BBPadData pad_data(&ans, pad);
+ BLI_task_parallel_range(0, n, &pad_data, pad_face_bb_range_func, &pad_settings);
+ return ans;
+}
+
+/**
+ * A cluster of co-planar triangles, by index.
+ * A pair of triangles T0 and T1 is said to "non-trivially co-planar-intersect"
+ * if they are co-planar, intersect, and their intersection is not just existing
+ * elements (verts, edges) of both triangles.
+ * A co-planar cluster is said to be "nontrivial" if it has more than one triangle
+ * and every triangle in it non-trivially co-planar-intersects with at least one other
+ * triangle in the cluster.
+ */
+class CoplanarCluster {
+ Vector<int> tris_;
+ BoundingBox bb_;
+
+ public:
+ CoplanarCluster() = default;
+ CoplanarCluster(int t, const BoundingBox &bb)
+ {
+ this->add_tri(t, bb);
+ }
+ CoplanarCluster(const CoplanarCluster &other) : tris_(other.tris_), bb_(other.bb_)
+ {
+ }
+ CoplanarCluster(CoplanarCluster &&other) noexcept
+ : tris_(std::move(other.tris_)), bb_(std::move(other.bb_))
+ {
+ }
+ ~CoplanarCluster() = default;
+ CoplanarCluster &operator=(const CoplanarCluster &other)
+ {
+ if (this != &other) {
+ tris_ = other.tris_;
+ bb_ = other.bb_;
+ }
+ return *this;
+ }
+ CoplanarCluster &operator=(CoplanarCluster &&other) noexcept
+ {
+ tris_ = std::move(other.tris_);
+ bb_ = std::move(other.bb_);
+ return *this;
+ }
+
+ /* Assume that caller knows this will not be a duplicate. */
+ void add_tri(int t, const BoundingBox &bb)
+ {
+ tris_.append(t);
+ bb_ = bb;
+ }
+ int tot_tri() const
+ {
+ return tris_.size();
+ }
+ int tri(int index) const
+ {
+ return tris_[index];
+ }
+ const int *begin() const
+ {
+ return tris_.begin();
+ }
+ const int *end() const
+ {
+ return tris_.end();
+ }
+
+ const BoundingBox &bounding_box() const
+ {
+ return bb_;
+ }
+};
+
+/**
+ * Maintains indexed set of #CoplanarCluster, with the added ability
+ * to efficiently find the cluster index of any given triangle
+ * (the max triangle index needs to be given in the initializer).
+ * The #tri_cluster(t) function returns -1 if t is not part of any cluster.
+ */
+class CoplanarClusterInfo {
+ Vector<CoplanarCluster> clusters_;
+ Array<int> tri_cluster_;
+
+ public:
+ CoplanarClusterInfo() = default;
+ explicit CoplanarClusterInfo(int numtri) : tri_cluster_(Array<int>(numtri))
+ {
+ tri_cluster_.fill(-1);
+ }
+
+ int tri_cluster(int t) const
+ {
+ BLI_assert(t < tri_cluster_.size());
+ return tri_cluster_[t];
+ }
+
+ int add_cluster(CoplanarCluster cl)
+ {
+ int c_index = clusters_.append_and_get_index(cl);
+ for (int t : cl) {
+ BLI_assert(t < tri_cluster_.size());
+ tri_cluster_[t] = c_index;
+ }
+ return c_index;
+ }
+
+ int tot_cluster() const
+ {
+ return clusters_.size();
+ }
+
+ const CoplanarCluster *begin()
+ {
+ return clusters_.begin();
+ }
+
+ const CoplanarCluster *end()
+ {
+ return clusters_.end();
+ }
+
+ IndexRange index_range() const
+ {
+ return clusters_.index_range();
+ }
+
+ const CoplanarCluster &cluster(int index) const
+ {
+ BLI_assert(index < clusters_.size());
+ return clusters_[index];
+ }
+};
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarCluster &cl);
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarClusterInfo &clinfo);
+
+enum ITT_value_kind { INONE, IPOINT, ISEGMENT, ICOPLANAR };
+
+struct ITT_value {
+ enum ITT_value_kind kind;
+ mpq3 p1; /* Only relevant for IPOINT and ISEGMENT kind. */
+ mpq3 p2; /* Only relevant for ISEGMENT kind. */
+ int t_source; /* Index of the source triangle that intersected the target one. */
+
+ ITT_value() : kind(INONE), t_source(-1)
+ {
+ }
+ ITT_value(ITT_value_kind k) : kind(k), t_source(-1)
+ {
+ }
+ ITT_value(ITT_value_kind k, int tsrc) : kind(k), t_source(tsrc)
+ {
+ }
+ ITT_value(ITT_value_kind k, const mpq3 &p1) : kind(k), p1(p1), t_source(-1)
+ {
+ }
+ ITT_value(ITT_value_kind k, const mpq3 &p1, const mpq3 &p2)
+ : kind(k), p1(p1), p2(p2), t_source(-1)
+ {
+ }
+ ITT_value(const ITT_value &other)
+ : kind(other.kind), p1(other.p1), p2(other.p2), t_source(other.t_source)
+ {
+ }
+ ITT_value(ITT_value &&other) noexcept
+ : kind(other.kind),
+ p1(std::move(other.p1)),
+ p2(std::move(other.p2)),
+ t_source(other.t_source)
+ {
+ }
+ ~ITT_value()
+ {
+ }
+ ITT_value &operator=(const ITT_value &other)
+ {
+ if (this != &other) {
+ kind = other.kind;
+ p1 = other.p1;
+ p2 = other.p2;
+ t_source = other.t_source;
+ }
+ return *this;
+ }
+ ITT_value &operator=(ITT_value &&other) noexcept
+ {
+ kind = other.kind;
+ p1 = std::move(other.p1);
+ p2 = std::move(other.p2);
+ t_source = other.t_source;
+ return *this;
+ }
+};
+
+static std::ostream &operator<<(std::ostream &os, const ITT_value &itt);
+
+/**
+ * Project a 3d vert to a 2d one by eliding proj_axis. This does not create
+ * degeneracies as long as the projection axis is one where the corresponding
+ * component of the originating plane normal is non-zero.
+ */
+static mpq2 project_3d_to_2d(const mpq3 &p3d, int proj_axis)
+{
+ mpq2 p2d;
+ switch (proj_axis) {
+ case (0): {
+ p2d[0] = p3d[1];
+ p2d[1] = p3d[2];
+ break;
+ }
+ case (1): {
+ p2d[0] = p3d[0];
+ p2d[1] = p3d[2];
+ break;
+ }
+ case (2): {
+ p2d[0] = p3d[0];
+ p2d[1] = p3d[1];
+ break;
+ }
+ default:
+ BLI_assert(false);
+ }
+ return p2d;
+}
+
+/**
+ Is a point in the interior of a 2d triangle or on one of its
+ * edges but not either endpoint of the edge?
+ * orient[pi][i] is the orientation test of the point pi against
+ * the side of the triangle starting at index i.
+ * Assume the triangle is non-degenerate and CCW-oriented.
+ * Then answer is true if p is left of or on all three of triangle a's edges,
+ * and strictly left of at least on of them.
+ */
+static bool non_trivially_2d_point_in_tri(const int orients[3][3], int pi)
+{
+ int p_left_01 = orients[pi][0];
+ int p_left_12 = orients[pi][1];
+ int p_left_20 = orients[pi][2];
+ return (p_left_01 >= 0 && p_left_12 >= 0 && p_left_20 >= 0 &&
+ (p_left_01 + p_left_12 + p_left_20) >= 2);
+}
+
+/**
+ * Given orients as defined in non_trivially_2d_intersect, do the triangles
+ * overlap in a "hex" pattern? That is, the overlap region is a hexagon, which
+ * one gets by having, each point of one triangle being strictly right-of one
+ * edge of the other and strictly left of the other two edges; and vice versa.
+ */
+static bool non_trivially_2d_hex_overlap(int orients[2][3][3])
+{
+ for (int ab = 0; ab < 2; ++ab) {
+ for (int i = 0; i < 3; ++i) {
+ bool ok = orients[ab][i][0] + orients[ab][i][1] + orients[ab][i][2] == 1 &&
+ orients[ab][i][0] != 0 && orients[ab][i][1] != 0 && orients[i][2] != 0;
+ if (!ok) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+/**
+ * Given orients as defined in non_trivially_2d_intersect, do the triangles
+ * have one shared edge in a "folded-over" configuration?
+ * As well as a shared edge, the third vertex of one triangle needs to be
+ * right-of one and left-of the other two edges of the other triangle.
+ */
+static bool non_trivially_2d_shared_edge_overlap(int orients[2][3][3],
+ const mpq2 *a[3],
+ const mpq2 *b[3])
+{
+ for (int i = 0; i < 3; ++i) {
+ int in = (i + 1) % 3;
+ int inn = (i + 2) % 3;
+ for (int j = 0; j < 3; ++j) {
+ int jn = (j + 1) % 3;
+ int jnn = (j + 2) % 3;
+ if (*a[i] == *b[j] && *a[in] == *b[jn]) {
+ /* Edge from a[i] is shared with edge from b[j]. */
+ /* See if a[inn] is right-of or on one of the other edges of b.
+ * If it is on, then it has to be right-of or left-of the shared edge,
+ * depending on which edge it is. */
+ if (orients[0][inn][jn] < 0 || orients[0][inn][jnn] < 0) {
+ return true;
+ }
+ if (orients[0][inn][jn] == 0 && orients[0][inn][j] == 1) {
+ return true;
+ }
+ if (orients[0][inn][jnn] == 0 && orients[0][inn][j] == -1) {
+ return true;
+ }
+ /* Similarly for `b[jnn]`. */
+ if (orients[1][jnn][in] < 0 || orients[1][jnn][inn] < 0) {
+ return true;
+ }
+ if (orients[1][jnn][in] == 0 && orients[1][jnn][i] == 1) {
+ return true;
+ }
+ if (orients[1][jnn][inn] == 0 && orients[1][jnn][i] == -1) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * Are the triangles the same, perhaps with some permutation of vertices?
+ */
+static bool same_triangles(const mpq2 *a[3], const mpq2 *b[3])
+{
+ for (int i = 0; i < 3; ++i) {
+ if (a[0] == b[i] && a[1] == b[(i + 1) % 3] && a[2] == b[(i + 2) % 3]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Do 2d triangles (a[0], a[1], a[2]) and (b[0], b[1], b2[2]) intersect at more than just shared
+ * vertices or a shared edge? This is true if any point of one triangle is non-trivially inside the
+ * other. NO: that isn't quite sufficient: there is also the case where the verts are all mutually
+ * outside the other's triangle, but there is a hexagonal overlap region where they overlap.
+ */
+static bool non_trivially_2d_intersect(const mpq2 *a[3], const mpq2 *b[3])
+{
+ /* TODO: Could experiment with trying bounding box tests before these.
+ * TODO: Find a less expensive way than 18 orient tests to do this. */
+
+ /* `orients[0][ai][bi]` is orient of point `a[ai]` compared to segment starting at `b[bi]`.
+ * `orients[1][bi][ai]` is orient of point `b[bi]` compared to segment starting at `a[ai]`. */
+ int orients[2][3][3];
+ for (int ab = 0; ab < 2; ++ab) {
+ for (int ai = 0; ai < 3; ++ai) {
+ for (int bi = 0; bi < 3; ++bi) {
+ if (ab == 0) {
+ orients[0][ai][bi] = orient2d(*b[bi], *b[(bi + 1) % 3], *a[ai]);
+ }
+ else {
+ orients[1][bi][ai] = orient2d(*a[ai], *a[(ai + 1) % 3], *b[bi]);
+ }
+ }
+ }
+ }
+ return non_trivially_2d_point_in_tri(orients[0], 0) ||
+ non_trivially_2d_point_in_tri(orients[0], 1) ||
+ non_trivially_2d_point_in_tri(orients[0], 2) ||
+ non_trivially_2d_point_in_tri(orients[1], 0) ||
+ non_trivially_2d_point_in_tri(orients[1], 1) ||
+ non_trivially_2d_point_in_tri(orients[1], 2) || non_trivially_2d_hex_overlap(orients) ||
+ non_trivially_2d_shared_edge_overlap(orients, a, b) || same_triangles(a, b);
+ return true;
+}
+
+/**
+ * Does triangle t in tm non-trivially non-co-planar intersect any triangle
+ * in `CoplanarCluster cl`? Assume t is known to be in the same plane as all
+ * the triangles in cl, and that proj_axis is a good axis to project down
+ * to solve this problem in 2d.
+ */
+static bool non_trivially_coplanar_intersects(const IMesh &tm,
+ int t,
+ const CoplanarCluster &cl,
+ int proj_axis)
+{
+ const Face &tri = *tm.face(t);
+ mpq2 v0 = project_3d_to_2d(tri[0]->co_exact, proj_axis);
+ mpq2 v1 = project_3d_to_2d(tri[1]->co_exact, proj_axis);
+ mpq2 v2 = project_3d_to_2d(tri[2]->co_exact, proj_axis);
+ if (orient2d(v0, v1, v2) != 1) {
+ mpq2 tmp = v1;
+ v1 = v2;
+ v2 = tmp;
+ }
+ for (const int cl_t : cl) {
+ const Face &cl_tri = *tm.face(cl_t);
+ mpq2 ctv0 = project_3d_to_2d(cl_tri[0]->co_exact, proj_axis);
+ mpq2 ctv1 = project_3d_to_2d(cl_tri[1]->co_exact, proj_axis);
+ mpq2 ctv2 = project_3d_to_2d(cl_tri[2]->co_exact, proj_axis);
+ if (orient2d(ctv0, ctv1, ctv2) != 1) {
+ mpq2 tmp = ctv1;
+ ctv1 = ctv2;
+ ctv2 = tmp;
+ }
+ const mpq2 *v[] = {&v0, &v1, &v2};
+ const mpq2 *ctv[] = {&ctv0, &ctv1, &ctv2};
+ if (non_trivially_2d_intersect(v, ctv)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Keeping this code for a while, but for now, almost all
+ * trivial intersects are found before calling intersect_tri_tri now.
+ */
+# if 0
+/**
+ * Do tri1 and tri2 intersect at all, and if so, is the intersection
+ * something other than a common vertex or a common edge?
+ * The \a itt value is the result of calling intersect_tri_tri on tri1, tri2.
+ */
+static bool non_trivial_intersect(const ITT_value &itt, const Face * tri1, const Face * tri2)
+{
+ if (itt.kind == INONE) {
+ return false;
+ }
+ const Face * tris[2] = {tri1, tri2};
+ if (itt.kind == IPOINT) {
+ bool has_p_as_vert[2] {false, false};
+ for (int i = 0; i < 2; ++i) {
+ for (const Vert * v : *tris[i]) {
+ if (itt.p1 == v->co_exact) {
+ has_p_as_vert[i] = true;
+ break;
+ }
+ }
+ }
+ return !(has_p_as_vert[0] && has_p_as_vert[1]);
+ }
+ if (itt.kind == ISEGMENT) {
+ bool has_seg_as_edge[2] = {false, false};
+ for (int i = 0; i < 2; ++i) {
+ const Face &t = *tris[i];
+ for (int pos : t.index_range()) {
+ int nextpos = t.next_pos(pos);
+ if ((itt.p1 == t[pos]->co_exact && itt.p2 == t[nextpos]->co_exact) ||
+ (itt.p2 == t[pos]->co_exact && itt.p1 == t[nextpos]->co_exact)) {
+ has_seg_as_edge[i] = true;
+ break;
+ }
+ }
+ }
+ return !(has_seg_as_edge[0] && has_seg_as_edge[1]);
+ }
+ BLI_assert(itt.kind == ICOPLANAR);
+ /* TODO: refactor this common code with code above. */
+ int proj_axis = mpq3::dominant_axis(tri1->plane.norm_exact);
+ mpq2 tri_2d[2][3];
+ for (int i = 0; i < 2; ++i) {
+ mpq2 v0 = project_3d_to_2d((*tris[i])[0]->co_exact, proj_axis);
+ mpq2 v1 = project_3d_to_2d((*tris[i])[1]->co_exact, proj_axis);
+ mpq2 v2 = project_3d_to_2d((*tris[i])[2]->co_exact, proj_axis);
+ if (mpq2::orient2d(v0, v1, v2) != 1) {
+ mpq2 tmp = v1;
+ v1 = v2;
+ v2 = tmp;
+ }
+ tri_2d[i][0] = v0;
+ tri_2d[i][1] = v1;
+ tri_2d[i][2] = v2;
+ }
+ const mpq2 *va[] = {&tri_2d[0][0], &tri_2d[0][1], &tri_2d[0][2]};
+ const mpq2 *vb[] = {&tri_2d[1][0], &tri_2d[1][1], &tri_2d[1][2]};
+ return non_trivially_2d_intersect(va, vb);
+}
+# endif
+
+/**
+ * The sup and index functions are defined in the paper:
+ * EXACT GEOMETRIC COMPUTATION USING CASCADING, by
+ * Burnikel, Funke, and Seel. They are used to find absolute
+ * bounds on the error due to doing a calculation in double
+ * instead of exactly. For calculations involving only +, -, and *,
+ * the supremum is the same function except using absolute values
+ * on inputs and using + instead of -.
+ * The index function follows these rules:
+ * index(x op y) = 1 + max(index(x), index(y)) for op + or -
+ * index(x * y) = 1 + index(x) + index(y)
+ * index(x) = 0 if input x can be represented exactly as a double
+ * index(x) = 1 otherwise.
+ *
+ * With these rules in place, we know an absolute error bound:
+ *
+ * |E_exact - E| <= supremum(E) * index(E) * DBL_EPSILON
+ *
+ * where E_exact is what would have been the exact value of the
+ * expression and E is the one calculated with doubles.
+ *
+ * So the sign of E is the same as the sign of E_exact if
+ * |E| > supremum(E) * index(E) * DBL_EPSILON
+ *
+ * Note: a possible speedup would be to have a simple function
+ * that calculates the error bound if one knows that all values
+ * are less than some global maximum - most of the function would
+ * be calculated ahead of time. The global max could be passed
+ * from above.
+ */
+static double supremum_dot_cross(const double3 &a, const double3 &b)
+{
+ double3 abs_a = double3::abs(a);
+ double3 abs_b = double3::abs(b);
+ double3 c;
+ /* This is dot(cross(a, b), cross(a,b)) but using absolute values for a and b
+ * and always using + when operation is + or -. */
+ c[0] = abs_a[1] * abs_b[2] + abs_a[2] * abs_b[1];
+ c[1] = abs_a[2] * abs_b[0] + abs_a[0] * abs_b[2];
+ c[2] = abs_a[0] * abs_b[1] + abs_a[1] * abs_b[0];
+ return double3::dot(c, c);
+}
+
+/**
+ * Used with supremum to get error bound. See Burnikel et al paper.
+ * index_plane_coord is the index of a plane coordinate calculated
+ * for a triangle using the usual formula, assuming the input
+ * coordinates have index 1.
+ * index_cross is the index of each coordinate of the cross product.
+ * It is actually 2 + 2 * (max index of input coords).
+ * index_dot_cross is the index of the dot product of two cross products.
+ * It is actually 7 + 4 * (max index of input coords)
+ */
+constexpr int index_dot_cross = 11;
+
+static double supremum_dot(const double3 &a, const double3 &b)
+{
+ double3 abs_a = double3::abs(a);
+ double3 abs_b = double3::abs(b);
+ return double3::dot(abs_a, abs_b);
+}
+
+/* Actually index_dot = 3 + 2 * (max index of input coordinates). */
+/* The index of dot when inputs are plane_coords with index 1 is much higher.
+ * Plane coords have index 6.
+ */
+constexpr int index_dot_plane_coords = 15;
+
+static double supremum_orient3d(const double3 &a,
+ const double3 &b,
+ const double3 &c,
+ const double3 &d)
+{
+ double3 abs_a = double3::abs(a);
+ double3 abs_b = double3::abs(b);
+ double3 abs_c = double3::abs(c);
+ double3 abs_d = double3::abs(d);
+ double adx = abs_a[0] + abs_d[0];
+ double bdx = abs_b[0] + abs_d[0];
+ double cdx = abs_c[0] + abs_d[0];
+ double ady = abs_a[1] + abs_d[1];
+ double bdy = abs_b[1] + abs_d[1];
+ double cdy = abs_c[1] + abs_d[1];
+ double adz = abs_a[2] + abs_d[2];
+ double bdz = abs_b[2] + abs_d[2];
+ double cdz = abs_c[2] + abs_d[2];
+
+ double bdxcdy = bdx * cdy;
+ double cdxbdy = cdx * bdy;
+
+ double cdxady = cdx * ady;
+ double adxcdy = adx * cdy;
+
+ double adxbdy = adx * bdy;
+ double bdxady = bdx * ady;
+
+ double det = adz * (bdxcdy + cdxbdy) + bdz * (cdxady + adxcdy) + cdz * (adxbdy + bdxady);
+ return det;
+}
+
+/** Actually index_orient3d = 10 + 4 * (max degree of input coordinates) */
+constexpr int index_orient3d = 14;
+
+/**
+ * Return the approximate orient3d of the four double3's, with
+ * the guarantee that if the value is -1 or 1 then the underlying
+ * mpq3 test would also have returned that value.
+ * When the return value is 0, we are not sure of the sign.
+ */
+static int filter_orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d)
+{
+ double o3dfast = orient3d_fast(a, b, c, d);
+ if (o3dfast == 0.0) {
+ return 0;
+ }
+ double err_bound = supremum_orient3d(a, b, c, d) * index_orient3d * DBL_EPSILON;
+ if (fabs(o3dfast) > err_bound) {
+ return o3dfast > 0.0 ? 1 : -1;
+ }
+ return 0;
+}
+
+/**
+ * Return the approximate orient3d of the triangle plane points and v, with
+ * the guarantee that if the value is -1 or 1 then the underlying
+ * mpq3 test would also have returned that value.
+ * When the return value is 0, we are not sure of the sign.
+ */
+static int filter_tri_plane_vert_orient3d(const Face &tri, const Vert *v)
+{
+ return filter_orient3d(tri[0]->co, tri[1]->co, tri[2]->co, v->co);
+}
+
+/**
+ * Are vectors a and b parallel or nearly parallel?
+ * This routine should only return false if we are certain
+ * that they are not parallel, taking into account the
+ * possible numeric errors and input value approximation.
+ */
+static bool near_parallel_vecs(const double3 &a, const double3 &b)
+{
+ double3 cr = double3::cross_high_precision(a, b);
+ double cr_len_sq = cr.length_squared();
+ if (cr_len_sq == 0.0) {
+ return true;
+ }
+ double err_bound = supremum_dot_cross(a, b) * index_dot_cross * DBL_EPSILON;
+ if (cr_len_sq > err_bound) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Return true if we are sure that dot(a,b) > 0, taking into
+ * account the error bounds due to numeric errors and input value
+ * approximation.
+ */
+static bool dot_must_be_positive(const double3 &a, const double3 &b)
+{
+ double d = double3::dot(a, b);
+ if (d <= 0.0) {
+ return false;
+ }
+ double err_bound = supremum_dot(a, b) * index_dot_plane_coords * DBL_EPSILON;
+ if (d > err_bound) {
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Return the approximate side of point p on a plane with normal plane_no and point plane_p.
+ * The answer will be 1 if p is definitely above the plane, -1 if it is definitely below.
+ * If the answer is 0, we are unsure about which side of the plane (or if it is on the plane).
+ * In exact arithmetic, the answer is just `sgn(dot(p - plane_p, plane_no))`.
+ *
+ * The plane_no input is constructed, so has a higher index.
+ */
+constexpr int index_plane_side = 3 + 2 * index_dot_plane_coords;
+
+static int filter_plane_side(const double3 &p,
+ const double3 &plane_p,
+ const double3 &plane_no,
+ const double3 &abs_p,
+ const double3 &abs_plane_p,
+ const double3 &abs_plane_no)
+{
+ double d = double3::dot(p - plane_p, plane_no);
+ if (d == 0.0) {
+ return 0;
+ }
+ double supremum = double3::dot(abs_p + abs_plane_p, abs_plane_no);
+ double err_bound = supremum * index_plane_side * DBL_EPSILON;
+ if (d > err_bound) {
+ return d > 0 ? 1 : -1;
+ }
+ return 0;
+}
+
+/**
+ * A fast, non-exhaustive test for non_trivial intersection.
+ * If this returns false then we are sure that tri1 and tri2
+ * do not intersect. If it returns true, they may or may not
+ * non-trivially intersect.
+ * We assume that bounding box overlap tests have already been
+ * done, so don't repeat those here. This routine is checking
+ * for the very common cases (when doing mesh self-intersect)
+ * where triangles share an edge or a vertex, but don't
+ * otherwise intersect.
+ */
+static bool may_non_trivially_intersect(Face *t1, Face *t2)
+{
+ Face &tri1 = *t1;
+ Face &tri2 = *t2;
+ BLI_assert(t1->plane_populated() && t2->plane_populated());
+ Face::FacePos share1_pos[3];
+ Face::FacePos share2_pos[3];
+ int n_shared = 0;
+ for (Face::FacePos p1 = 0; p1 < 3; ++p1) {
+ const Vert *v1 = tri1[p1];
+ for (Face::FacePos p2 = 0; p2 < 3; ++p2) {
+ const Vert *v2 = tri2[p2];
+ if (v1 == v2) {
+ share1_pos[n_shared] = p1;
+ share2_pos[n_shared] = p2;
+ ++n_shared;
+ }
+ }
+ }
+ if (n_shared == 2) {
+ /* t1 and t2 share an entire edge.
+ * If their normals are not parallel, they cannot non-trivially intersect. */
+ if (!near_parallel_vecs(tri1.plane->norm, tri2.plane->norm)) {
+ return false;
+ }
+ /* The normals are parallel or nearly parallel.
+ * If the normals are in the same direction and the edges have opposite
+ * directions in the two triangles, they cannot non-trivially intersect. */
+ bool erev1 = tri1.prev_pos(share1_pos[0]) == share1_pos[1];
+ bool erev2 = tri2.prev_pos(share2_pos[0]) == share2_pos[1];
+ if (erev1 != erev2) {
+ if (dot_must_be_positive(tri1.plane->norm, tri2.plane->norm)) {
+ return false;
+ }
+ }
+ }
+ else if (n_shared == 1) {
+ /* t1 and t2 share a vertex, but not an entire edge.
+ * If the two non-shared verts of t2 are both on the same
+ * side of tri1's plane, then they cannot non-trivially intersect.
+ * (There are some other cases that could be caught here but
+ * they are more expensive to check). */
+ Face::FacePos p = share2_pos[0];
+ const Vert *v2a = p == 0 ? tri2[1] : tri2[0];
+ const Vert *v2b = (p == 0 || p == 1) ? tri2[2] : tri2[1];
+ int o1 = filter_tri_plane_vert_orient3d(tri1, v2a);
+ int o2 = filter_tri_plane_vert_orient3d(tri1, v2b);
+ if (o1 == o2 && o1 != 0) {
+ return false;
+ }
+ p = share1_pos[0];
+ const Vert *v1a = p == 0 ? tri1[1] : tri1[0];
+ const Vert *v1b = (p == 0 || p == 1) ? tri1[2] : tri1[1];
+ o1 = filter_tri_plane_vert_orient3d(tri2, v1a);
+ o2 = filter_tri_plane_vert_orient3d(tri2, v1b);
+ if (o1 == o2 && o1 != 0) {
+ return false;
+ }
+ }
+ /* We weren't able to prove that any intersection is trivial. */
+ return true;
+}
+
+/*
+ * interesect_tri_tri and helper functions.
+ * This code uses the algorithm of Guigue and Devillers, as described
+ * in "Faster Triangle-Triangle Intersection Tests".
+ * Adapted from github code by Eric Haines:
+ * github.com/erich666/jgt-code/tree/master/Volume_08/Number_1/Guigue2003
+ */
+
+/**
+ * Return the point on ab where the plane with normal n containing point c intersects it.
+ * Assumes ab is not perpendicular to n.
+ * This works because the ratio of the projections of ab and ac onto n is the same as
+ * the ratio along the line ab of the intersection point to the whole of ab.
+ */
+static inline mpq3 tti_interp(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &n)
+{
+ mpq3 ab = a - b;
+ mpq_class den = mpq3::dot(ab, n);
+ BLI_assert(den != 0);
+ mpq_class alpha = mpq3::dot(a - c, n) / den;
+ return a - alpha * ab;
+}
+
+/**
+ * Return +1, 0, -1 as a + ad is above, on, or below the oriented plane containing a, b, c in CCW
+ * order. This is the same as -oriented(a, b, c, a + ad), but uses fewer arithmetic operations.
+ * TODO: change arguments to `const Vert *` and use floating filters.
+ */
+static inline int tti_above(const mpq3 &a, const mpq3 &b, const mpq3 &c, const mpq3 &ad)
+{
+ mpq3 n = mpq3::cross(b - a, c - a);
+ return sgn(mpq3::dot(ad, n));
+}
+
+/**
+ * Given that triangles (p1, q1, r1) and (p2, q2, r2) are in canonical order,
+ * use the classification chart in the Guigue and Devillers paper to find out
+ * how the intervals [i,j] and [k,l] overlap, where [i,j] is where p1r1 and p1q1
+ * intersect the plane-plane intersection line, L, and [k,l] is where p2q2 and p2r2
+ * intersect L. By the canonicalization, those segments intersect L exactly once.
+ * Canonicalization has made it so that for p1, q1, r1, either:
+ * (a)) p1 is off the second triangle's plane and both q1 and r1 are either
+ * on the plane or on the other side of it from p1; or
+ * (b) p1 is on the plane both q1 and r1 are on the same side
+ * of the plane and at least one of q1 and r1 are off the plane.
+ * Similarly for p2, q2, r2 with respect to the first triangle's plane.
+ */
+static ITT_value itt_canon2(const mpq3 &p1,
+ const mpq3 &q1,
+ const mpq3 &r1,
+ const mpq3 &p2,
+ const mpq3 &q2,
+ const mpq3 &r2,
+ const mpq3 &n1,
+ const mpq3 &n2)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\ntri_tri_intersect_canon:\n";
+ std::cout << "p1=" << p1 << " q1=" << q1 << " r1=" << r1 << "\n";
+ std::cout << "p2=" << p2 << " q2=" << q2 << " r2=" << r2 << "\n";
+ std::cout << "n1=" << n1 << " n2=" << n2 << "\n";
+ std::cout << "approximate values:\n";
+ std::cout << "p1=(" << p1[0].get_d() << "," << p1[1].get_d() << "," << p1[2].get_d() << ")\n";
+ std::cout << "q1=(" << q1[0].get_d() << "," << q1[1].get_d() << "," << q1[2].get_d() << ")\n";
+ std::cout << "r1=(" << r1[0].get_d() << "," << r1[1].get_d() << "," << r1[2].get_d() << ")\n";
+ std::cout << "p2=(" << p2[0].get_d() << "," << p2[1].get_d() << "," << p2[2].get_d() << ")\n";
+ std::cout << "q2=(" << q2[0].get_d() << "," << q2[1].get_d() << "," << q2[2].get_d() << ")\n";
+ std::cout << "r2=(" << r2[0].get_d() << "," << r2[1].get_d() << "," << r2[2].get_d() << ")\n";
+ std::cout << "n1=(" << n1[0].get_d() << "," << n1[1].get_d() << "," << n1[2].get_d() << ")\n";
+ std::cout << "n2=(" << n2[0].get_d() << "," << n2[1].get_d() << "," << n2[2].get_d() << ")\n";
+ }
+ mpq3 p1p2 = p2 - p1;
+ mpq3 intersect_1;
+ mpq3 intersect_2;
+ bool no_overlap = false;
+ /* Top test in classification tree. */
+ if (tti_above(p1, q1, r2, p1p2) > 0) {
+ /* Middle right test in classification tree. */
+ if (tti_above(p1, r1, r2, p1p2) <= 0) {
+ /* Bottom right test in classification tree. */
+ if (tti_above(p1, r1, q2, p1p2) > 0) {
+ /* Overlap is [k [i l] j]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [k [i l] j]\n";
+ }
+ /* i is intersect with p1r1. l is intersect with p2r2. */
+ intersect_1 = tti_interp(p1, r1, p2, n2);
+ intersect_2 = tti_interp(p2, r2, p1, n1);
+ }
+ else {
+ /* Overlap is [i [k l] j]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [i [k l] j]\n";
+ }
+ /* k is intersect with p2q2. l is intersect is p2r2. */
+ intersect_1 = tti_interp(p2, q2, p1, n1);
+ intersect_2 = tti_interp(p2, r2, p1, n1);
+ }
+ }
+ else {
+ /* No overlap: [k l] [i j]. */
+ if (dbg_level > 0) {
+ std::cout << "no overlap: [k l] [i j]\n";
+ }
+ no_overlap = true;
+ }
+ }
+ else {
+ /* Middle left test in classification tree. */
+ if (tti_above(p1, q1, q2, p1p2) < 0) {
+ /* No overlap: [i j] [k l]. */
+ if (dbg_level > 0) {
+ std::cout << "no overlap: [i j] [k l]\n";
+ }
+ no_overlap = true;
+ }
+ else {
+ /* Bottom left test in classification tree. */
+ if (tti_above(p1, r1, q2, p1p2) >= 0) {
+ /* Overlap is [k [i j] l]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [k [i j] l]\n";
+ }
+ /* i is intersect with p1r1. j is intersect with p1q1. */
+ intersect_1 = tti_interp(p1, r1, p2, n2);
+ intersect_2 = tti_interp(p1, q1, p2, n2);
+ }
+ else {
+ /* Overlap is [i [k j] l]. */
+ if (dbg_level > 0) {
+ std::cout << "overlap [i [k j] l]\n";
+ }
+ /* k is intersect with p2q2. j is intersect with p1q1. */
+ intersect_1 = tti_interp(p2, q2, p1, n1);
+ intersect_2 = tti_interp(p1, q1, p2, n2);
+ }
+ }
+ }
+ if (no_overlap) {
+ return ITT_value(INONE);
+ }
+ if (intersect_1 == intersect_2) {
+ if (dbg_level > 0) {
+ std::cout << "single intersect: " << intersect_1 << "\n";
+ }
+ return ITT_value(IPOINT, intersect_1);
+ }
+ if (dbg_level > 0) {
+ std::cout << "intersect segment: " << intersect_1 << ", " << intersect_2 << "\n";
+ }
+ return ITT_value(ISEGMENT, intersect_1, intersect_2);
+}
+
+/* Helper function for intersect_tri_tri. Args have been canonicalized for triangle 1. */
+
+static ITT_value itt_canon1(const mpq3 &p1,
+ const mpq3 &q1,
+ const mpq3 &r1,
+ const mpq3 &p2,
+ const mpq3 &q2,
+ const mpq3 &r2,
+ const mpq3 &n1,
+ const mpq3 &n2,
+ int sp2,
+ int sq2,
+ int sr2)
+{
+ constexpr int dbg_level = 0;
+ if (sp2 > 0) {
+ if (sq2 > 0) {
+ return itt_canon2(p1, r1, q1, r2, p2, q2, n1, n2);
+ }
+ if (sr2 > 0) {
+ return itt_canon2(p1, r1, q1, q2, r2, p2, n1, n2);
+ }
+ return itt_canon2(p1, q1, r1, p2, q2, r2, n1, n2);
+ }
+ if (sp2 < 0) {
+ if (sq2 < 0) {
+ return itt_canon2(p1, q1, r1, r2, p2, q2, n1, n2);
+ }
+ if (sr2 < 0) {
+ return itt_canon2(p1, q1, r1, q2, r2, p2, n1, n2);
+ }
+ return itt_canon2(p1, r1, q1, p2, q2, r2, n1, n2);
+ }
+ if (sq2 < 0) {
+ if (sr2 >= 0) {
+ return itt_canon2(p1, r1, q1, q2, r2, p2, n1, n2);
+ }
+ return itt_canon2(p1, q1, r1, p2, q2, r2, n1, n2);
+ }
+ if (sq2 > 0) {
+ if (sr2 > 0) {
+ return itt_canon2(p1, r1, q1, p2, q2, r2, n1, n2);
+ }
+ return itt_canon2(p1, q1, r1, q2, r2, p2, n1, n2);
+ }
+ if (sr2 > 0) {
+ return itt_canon2(p1, q1, r1, r2, p2, q2, n1, n2);
+ }
+ if (sr2 < 0) {
+ return itt_canon2(p1, r1, q1, r2, p2, q2, n1, n2);
+ }
+ if (dbg_level > 0) {
+ std::cout << "triangles are co-planar\n";
+ }
+ return ITT_value(ICOPLANAR);
+}
+
+static ITT_value intersect_tri_tri(const IMesh &tm, int t1, int t2)
+{
+ constexpr int dbg_level = 0;
+# ifdef PERFDEBUG
+ incperfcount(1); /* Intersect_tri_tri calls. */
+# endif
+ const Face &tri1 = *tm.face(t1);
+ const Face &tri2 = *tm.face(t2);
+ BLI_assert(tri1.plane_populated() && tri2.plane_populated());
+ const Vert *vp1 = tri1[0];
+ const Vert *vq1 = tri1[1];
+ const Vert *vr1 = tri1[2];
+ const Vert *vp2 = tri2[0];
+ const Vert *vq2 = tri2[1];
+ const Vert *vr2 = tri2[2];
+ if (dbg_level > 0) {
+ std::cout << "\nINTERSECT_TRI_TRI t1=" << t1 << ", t2=" << t2 << "\n";
+ std::cout << " p1 = " << vp1 << "\n";
+ std::cout << " q1 = " << vq1 << "\n";
+ std::cout << " r1 = " << vr1 << "\n";
+ std::cout << " p2 = " << vp2 << "\n";
+ std::cout << " q2 = " << vq2 << "\n";
+ std::cout << " r2 = " << vr2 << "\n";
+ }
+
+ /* Get signs of t1's vertices' distances to plane of t2 and vice versa. */
+
+ /* Try first getting signs with double arithmetic, with error bounds.
+ * If the signs calculated in this section are not 0, they are the same
+ * as what they would be using exact arithmetic. */
+ const double3 &d_p1 = vp1->co;
+ const double3 &d_q1 = vq1->co;
+ const double3 &d_r1 = vr1->co;
+ const double3 &d_p2 = vp2->co;
+ const double3 &d_q2 = vq2->co;
+ const double3 &d_r2 = vr2->co;
+ const double3 &d_n2 = tri2.plane->norm;
+
+ const double3 &abs_d_p1 = double3::abs(d_p1);
+ const double3 &abs_d_q1 = double3::abs(d_q1);
+ const double3 &abs_d_r1 = double3::abs(d_r1);
+ const double3 &abs_d_r2 = double3::abs(d_r2);
+ const double3 &abs_d_n2 = double3::abs(d_n2);
+
+ int sp1 = filter_plane_side(d_p1, d_r2, d_n2, abs_d_p1, abs_d_r2, abs_d_n2);
+ int sq1 = filter_plane_side(d_q1, d_r2, d_n2, abs_d_q1, abs_d_r2, abs_d_n2);
+ int sr1 = filter_plane_side(d_r1, d_r2, d_n2, abs_d_r1, abs_d_r2, abs_d_n2);
+ if ((sp1 > 0 && sq1 > 0 && sr1 > 0) || (sp1 < 0 && sq1 < 0 && sr1 < 0)) {
+# ifdef PERFDEBUG
+ incperfcount(2); /* Tri tri intersects decided by filter plane tests. */
+# endif
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t1's verts above or below t2\n";
+ }
+ return ITT_value(INONE);
+ }
+
+ const double3 &d_n1 = tri1.plane->norm;
+ const double3 &abs_d_p2 = double3::abs(d_p2);
+ const double3 &abs_d_q2 = double3::abs(d_q2);
+ const double3 &abs_d_n1 = double3::abs(d_n1);
+
+ int sp2 = filter_plane_side(d_p2, d_r1, d_n1, abs_d_p2, abs_d_r1, abs_d_n1);
+ int sq2 = filter_plane_side(d_q2, d_r1, d_n1, abs_d_q2, abs_d_r1, abs_d_n1);
+ int sr2 = filter_plane_side(d_r2, d_r1, d_n1, abs_d_r2, abs_d_r1, abs_d_n1);
+ if ((sp2 > 0 && sq2 > 0 && sr2 > 0) || (sp2 < 0 && sq2 < 0 && sr2 < 0)) {
+# ifdef PERFDEBUG
+ incperfcount(2); /* Tri tri intersects decided by filter plane tests. */
+# endif
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t2's verts above or below t1\n";
+ }
+ return ITT_value(INONE);
+ }
+
+ const mpq3 &p1 = vp1->co_exact;
+ const mpq3 &q1 = vq1->co_exact;
+ const mpq3 &r1 = vr1->co_exact;
+ const mpq3 &p2 = vp2->co_exact;
+ const mpq3 &q2 = vq2->co_exact;
+ const mpq3 &r2 = vr2->co_exact;
+
+ const mpq3 &n2 = tri2.plane->norm_exact;
+ if (sp1 == 0) {
+ sp1 = sgn(mpq3::dot(p1 - r2, n2));
+ }
+ if (sq1 == 0) {
+ sq1 = sgn(mpq3::dot(q1 - r2, n2));
+ }
+ if (sr1 == 0) {
+ sr1 = sgn(mpq3::dot(r1 - r2, n2));
+ }
+
+ if (dbg_level > 1) {
+ std::cout << " sp1=" << sp1 << " sq1=" << sq1 << " sr1=" << sr1 << "\n";
+ }
+
+ if ((sp1 * sq1 > 0) && (sp1 * sr1 > 0)) {
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t1's verts above or below t2 (exact)\n";
+ }
+# ifdef PERFDEBUG
+ incperfcount(3); /* Tri tri intersects decided by exact plane tests. */
+# endif
+ return ITT_value(INONE);
+ }
+
+ /* Repeat for signs of t2's vertices with respect to plane of t1. */
+ const mpq3 &n1 = tri1.plane->norm_exact;
+ if (sp2 == 0) {
+ sp2 = sgn(mpq3::dot(p2 - r1, n1));
+ }
+ if (sq2 == 0) {
+ sq2 = sgn(mpq3::dot(q2 - r1, n1));
+ }
+ if (sr2 == 0) {
+ sr2 = sgn(mpq3::dot(r2 - r1, n1));
+ }
+
+ if (dbg_level > 1) {
+ std::cout << " sp2=" << sp2 << " sq2=" << sq2 << " sr2=" << sr2 << "\n";
+ }
+
+ if ((sp2 * sq2 > 0) && (sp2 * sr2 > 0)) {
+ if (dbg_level > 0) {
+ std::cout << "no intersection, all t2's verts above or below t1 (exact)\n";
+ }
+# ifdef PERFDEBUG
+ incperfcount(3); /* Tri tri intersects decided by exact plane tests. */
+# endif
+ return ITT_value(INONE);
+ }
+
+ /* Do rest of the work with vertices in a canonical order, where p1 is on
+ * positive side of plane and q1, r1 are not, or p1 is on the plane and
+ * q1 and r1 are off the plane on the same side. */
+ ITT_value ans;
+ if (sp1 > 0) {
+ if (sq1 > 0) {
+ ans = itt_canon1(r1, p1, q1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else if (sr1 > 0) {
+ ans = itt_canon1(q1, r1, p1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ ans = itt_canon1(p1, q1, r1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ }
+ else if (sp1 < 0) {
+ if (sq1 < 0) {
+ ans = itt_canon1(r1, p1, q1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ else if (sr1 < 0) {
+ ans = itt_canon1(q1, r1, p1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ else {
+ ans = itt_canon1(p1, q1, r1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ }
+ else {
+ if (sq1 < 0) {
+ if (sr1 >= 0) {
+ ans = itt_canon1(q1, r1, p1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ ans = itt_canon1(p1, q1, r1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ }
+ else if (sq1 > 0) {
+ if (sr1 > 0) {
+ ans = itt_canon1(p1, q1, r1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ ans = itt_canon1(q1, r1, p1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ }
+ else {
+ if (sr1 > 0) {
+ ans = itt_canon1(r1, p1, q1, p2, q2, r2, n1, n2, sp2, sq2, sr2);
+ }
+ else if (sr1 < 0) {
+ ans = itt_canon1(r1, p1, q1, p2, r2, q2, n1, n2, sp2, sr2, sq2);
+ }
+ else {
+ if (dbg_level > 0) {
+ std::cout << "triangles are co-planar\n";
+ }
+ ans = ITT_value(ICOPLANAR);
+ }
+ }
+ }
+ if (ans.kind == ICOPLANAR) {
+ ans.t_source = t2;
+ }
+
+# ifdef PERFDEBUG
+ if (ans.kind != INONE) {
+ incperfcount(4);
+ }
+# endif
+ return ans;
+}
+
+struct CDT_data {
+ const Plane *t_plane;
+ Vector<mpq2> vert;
+ Vector<std::pair<int, int>> edge;
+ Vector<Vector<int>> face;
+ /** Parallels face, gives id from input #IMesh of input face. */
+ Vector<int> input_face;
+ /** Parallels face, says if input face orientation is opposite. */
+ Vector<bool> is_reversed;
+ /** Result of running CDT on input with (vert, edge, face). */
+ CDT_result<mpq_class> cdt_out;
+ int proj_axis;
+};
+
+/**
+ * We could de-duplicate verts here, but CDT routine will do that anyway.
+ */
+static int prepare_need_vert(CDT_data &cd, const mpq3 &p3d)
+{
+ mpq2 p2d = project_3d_to_2d(p3d, cd.proj_axis);
+ int v = cd.vert.append_and_get_index(p2d);
+ return v;
+}
+
+/**
+ * To un-project a 2d vert that was projected along cd.proj_axis, we copy the coordinates
+ * from the two axes not involved in the projection, and use the plane equation of the
+ * originating 3d plane, cd.t_plane, to derive the coordinate of the projected axis.
+ * The plane equation says a point p is on the plane if dot(p, plane.n()) + plane.d() == 0.
+ * Assume that the projection axis is such that plane.n()[proj_axis] != 0.
+ */
+static mpq3 unproject_cdt_vert(const CDT_data &cd, const mpq2 &p2d)
+{
+ mpq3 p3d;
+ BLI_assert(cd.t_plane->exact_populated());
+ BLI_assert(cd.t_plane->norm_exact[cd.proj_axis] != 0);
+ const mpq3 &n = cd.t_plane->norm_exact;
+ const mpq_class &d = cd.t_plane->d_exact;
+ switch (cd.proj_axis) {
+ case (0): {
+ mpq_class num = n[1] * p2d[0] + n[2] * p2d[1] + d;
+ num = -num;
+ p3d[0] = num / n[0];
+ p3d[1] = p2d[0];
+ p3d[2] = p2d[1];
+ break;
+ }
+ case (1): {
+ p3d[0] = p2d[0];
+ mpq_class num = n[0] * p2d[0] + n[2] * p2d[1] + d;
+ num = -num;
+ p3d[1] = num / n[1];
+ p3d[2] = p2d[1];
+ break;
+ }
+ case (2): {
+ p3d[0] = p2d[0];
+ p3d[1] = p2d[1];
+ mpq_class num = n[0] * p2d[0] + n[1] * p2d[1] + d;
+ num = -num;
+ p3d[2] = num / n[2];
+ break;
+ }
+ default:
+ BLI_assert(false);
+ }
+ return p3d;
+}
+
+static void prepare_need_edge(CDT_data &cd, const mpq3 &p1, const mpq3 &p2)
+{
+ int v1 = prepare_need_vert(cd, p1);
+ int v2 = prepare_need_vert(cd, p2);
+ cd.edge.append(std::pair<int, int>(v1, v2));
+}
+
+static void prepare_need_tri(CDT_data &cd, const IMesh &tm, int t)
+{
+ const Face &tri = *tm.face(t);
+ int v0 = prepare_need_vert(cd, tri[0]->co_exact);
+ int v1 = prepare_need_vert(cd, tri[1]->co_exact);
+ int v2 = prepare_need_vert(cd, tri[2]->co_exact);
+ bool rev;
+ /* How to get CCW orientation of projected triangle? Note that when look down y axis
+ * as opposed to x or z, the orientation of the other two axes is not right-and-up. */
+ BLI_assert(cd.t_plane->exact_populated());
+ if (tri.plane->norm_exact[cd.proj_axis] >= 0) {
+ rev = cd.proj_axis == 1;
+ }
+ else {
+ rev = cd.proj_axis != 1;
+ }
+ int cd_t = cd.face.append_and_get_index(Vector<int>());
+ cd.face[cd_t].append(v0);
+ if (rev) {
+ cd.face[cd_t].append(v2);
+ cd.face[cd_t].append(v1);
+ }
+ else {
+ cd.face[cd_t].append(v1);
+ cd.face[cd_t].append(v2);
+ }
+ cd.input_face.append(t);
+ cd.is_reversed.append(rev);
+}
+
+static CDT_data prepare_cdt_input(const IMesh &tm, int t, const Vector<ITT_value> itts)
+{
+ CDT_data ans;
+ BLI_assert(tm.face(t)->plane_populated());
+ ans.t_plane = tm.face(t)->plane;
+ BLI_assert(ans.t_plane->exact_populated());
+ ans.proj_axis = mpq3::dominant_axis(ans.t_plane->norm_exact);
+ prepare_need_tri(ans, tm, t);
+ for (const ITT_value &itt : itts) {
+ switch (itt.kind) {
+ case INONE:
+ break;
+ case IPOINT: {
+ prepare_need_vert(ans, itt.p1);
+ break;
+ }
+ case ISEGMENT: {
+ prepare_need_edge(ans, itt.p1, itt.p2);
+ break;
+ }
+ case ICOPLANAR: {
+ prepare_need_tri(ans, tm, itt.t_source);
+ break;
+ }
+ }
+ }
+ return ans;
+}
+
+static CDT_data prepare_cdt_input_for_cluster(const IMesh &tm,
+ const CoplanarClusterInfo &clinfo,
+ int c,
+ const Vector<ITT_value> itts)
+{
+ CDT_data ans;
+ BLI_assert(c < clinfo.tot_cluster());
+ const CoplanarCluster &cl = clinfo.cluster(c);
+ BLI_assert(cl.tot_tri() > 0);
+ int t0 = cl.tri(0);
+ BLI_assert(tm.face(t0)->plane_populated());
+ ans.t_plane = tm.face(t0)->plane;
+ BLI_assert(ans.t_plane->exact_populated());
+ ans.proj_axis = mpq3::dominant_axis(ans.t_plane->norm_exact);
+ for (const int t : cl) {
+ prepare_need_tri(ans, tm, t);
+ }
+ for (const ITT_value &itt : itts) {
+ switch (itt.kind) {
+ case IPOINT: {
+ prepare_need_vert(ans, itt.p1);
+ break;
+ }
+ case ISEGMENT: {
+ prepare_need_edge(ans, itt.p1, itt.p2);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return ans;
+}
+
+/**
+ * Fills in cd.cdt_out with result of doing the cdt calculation on (vert, edge, face).
+ */
+static void do_cdt(CDT_data &cd)
+{
+ constexpr int dbg_level = 0;
+ CDT_input<mpq_class> cdt_in;
+ cdt_in.vert = Span<mpq2>(cd.vert);
+ cdt_in.edge = Span<std::pair<int, int>>(cd.edge);
+ cdt_in.face = Span<Vector<int>>(cd.face);
+ if (dbg_level > 0) {
+ std::cout << "CDT input\nVerts:\n";
+ for (int i : cdt_in.vert.index_range()) {
+ std::cout << "v" << i << ": " << cdt_in.vert[i] << "=(" << cdt_in.vert[i][0].get_d() << ","
+ << cdt_in.vert[i][1].get_d() << ")\n";
+ }
+ std::cout << "Edges:\n";
+ for (int i : cdt_in.edge.index_range()) {
+ std::cout << "e" << i << ": (" << cdt_in.edge[i].first << ", " << cdt_in.edge[i].second
+ << ")\n";
+ }
+ std::cout << "Tris\n";
+ for (int f : cdt_in.face.index_range()) {
+ std::cout << "f" << f << ": ";
+ for (int j : cdt_in.face[f].index_range()) {
+ std::cout << cdt_in.face[f][j] << " ";
+ }
+ std::cout << "\n";
+ }
+ }
+ cdt_in.epsilon = 0; /* TODO: needs attention for non-exact T. */
+ cd.cdt_out = blender::meshintersect::delaunay_2d_calc(cdt_in, CDT_INSIDE);
+ if (dbg_level > 0) {
+ std::cout << "\nCDT result\nVerts:\n";
+ for (int i : cd.cdt_out.vert.index_range()) {
+ std::cout << "v" << i << ": " << cd.cdt_out.vert[i] << "=(" << cd.cdt_out.vert[i][0].get_d()
+ << "," << cd.cdt_out.vert[i][1].get_d() << "\n";
+ }
+ std::cout << "Tris\n";
+ for (int f : cd.cdt_out.face.index_range()) {
+ std::cout << "f" << f << ": ";
+ for (int j : cd.cdt_out.face[f].index_range()) {
+ std::cout << cd.cdt_out.face[f][j] << " ";
+ }
+ std::cout << "orig: ";
+ for (int j : cd.cdt_out.face_orig[f].index_range()) {
+ std::cout << cd.cdt_out.face_orig[f][j] << " ";
+ }
+ std::cout << "\n";
+ }
+ std::cout << "Edges\n";
+ for (int e : cd.cdt_out.edge.index_range()) {
+ std::cout << "e" << e << ": (" << cd.cdt_out.edge[e].first << ", "
+ << cd.cdt_out.edge[e].second << ") ";
+ std::cout << "orig: ";
+ for (int j : cd.cdt_out.edge_orig[e].index_range()) {
+ std::cout << cd.cdt_out.edge_orig[e][j] << " ";
+ }
+ std::cout << "\n";
+ }
+ }
+}
+
+static int get_cdt_edge_orig(
+ int i0, int i1, const CDT_data &cd, const IMesh &in_tm, bool *r_is_intersect)
+{
+ int foff = cd.cdt_out.face_edge_offset;
+ *r_is_intersect = false;
+ for (int e : cd.cdt_out.edge.index_range()) {
+ std::pair<int, int> edge = cd.cdt_out.edge[e];
+ if ((edge.first == i0 && edge.second == i1) || (edge.first == i1 && edge.second == i0)) {
+ /* Pick an arbitrary orig, but not one equal to NO_INDEX, if we can help it. */
+ /* TODO: if edge has origs from more than on part of the nary input,
+ * then want to set *r_is_intersect to true. */
+ for (int orig_index : cd.cdt_out.edge_orig[e]) {
+ /* orig_index encodes the triangle and pos within the triangle of the input edge. */
+ if (orig_index >= foff) {
+ int in_face_index = (orig_index / foff) - 1;
+ int pos = orig_index % foff;
+ /* We need to retrieve the edge orig field from the Face used to populate the
+ * in_face_index'th face of the CDT, at the pos'th position of the face. */
+ int in_tm_face_index = cd.input_face[in_face_index];
+ BLI_assert(in_tm_face_index < in_tm.face_size());
+ const Face *facep = in_tm.face(in_tm_face_index);
+ BLI_assert(pos < facep->size());
+ bool is_rev = cd.is_reversed[in_face_index];
+ int eorig = is_rev ? facep->edge_orig[2 - pos] : facep->edge_orig[pos];
+ if (eorig != NO_INDEX) {
+ return eorig;
+ }
+ }
+ else {
+ /* This edge came from an edge input to the CDT problem,
+ * so it is an intersect edge. */
+ *r_is_intersect = true;
+ /* TODO: maybe there is an orig index:
+ * This happens if an input edge was formed by an input face having
+ * an edge that is co-planar with the cluster, while the face as a whole is not. */
+ return NO_INDEX;
+ }
+ }
+ return NO_INDEX;
+ }
+ }
+ return NO_INDEX;
+}
+
+/**
+ * Using the result of CDT in cd.cdt_out, extract an #IMesh representing the subdivision
+ * of input triangle t, which should be an element of cd.input_face.
+ */
+static IMesh extract_subdivided_tri(const CDT_data &cd,
+ const IMesh &in_tm,
+ int t,
+ IMeshArena *arena)
+{
+ const CDT_result<mpq_class> &cdt_out = cd.cdt_out;
+ int t_in_cdt = -1;
+ for (int i = 0; i < cd.input_face.size(); ++i) {
+ if (cd.input_face[i] == t) {
+ t_in_cdt = i;
+ }
+ }
+ if (t_in_cdt == -1) {
+ std::cout << "Could not find " << t << " in cdt input tris\n";
+ BLI_assert(false);
+ return IMesh();
+ }
+ int t_orig = in_tm.face(t)->orig;
+ constexpr int inline_buf_size = 20;
+ Vector<Face *, inline_buf_size> faces;
+ for (int f : cdt_out.face.index_range()) {
+ if (cdt_out.face_orig[f].contains(t_in_cdt)) {
+ BLI_assert(cdt_out.face[f].size() == 3);
+ int i0 = cdt_out.face[f][0];
+ int i1 = cdt_out.face[f][1];
+ int i2 = cdt_out.face[f][2];
+ mpq3 v0co = unproject_cdt_vert(cd, cdt_out.vert[i0]);
+ mpq3 v1co = unproject_cdt_vert(cd, cdt_out.vert[i1]);
+ mpq3 v2co = unproject_cdt_vert(cd, cdt_out.vert[i2]);
+ /* No need to provide an original index: if coord matches
+ * an original one, then it will already be in the arena
+ * with the correct orig field. */
+ const Vert *v0 = arena->add_or_find_vert(v0co, NO_INDEX);
+ const Vert *v1 = arena->add_or_find_vert(v1co, NO_INDEX);
+ const Vert *v2 = arena->add_or_find_vert(v2co, NO_INDEX);
+ Face *facep;
+ bool is_isect0;
+ bool is_isect1;
+ bool is_isect2;
+ if (cd.is_reversed[t_in_cdt]) {
+ int oe0 = get_cdt_edge_orig(i0, i2, cd, in_tm, &is_isect0);
+ int oe1 = get_cdt_edge_orig(i2, i1, cd, in_tm, &is_isect1);
+ int oe2 = get_cdt_edge_orig(i1, i0, cd, in_tm, &is_isect2);
+ facep = arena->add_face(
+ {v0, v2, v1}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2});
+ }
+ else {
+ int oe0 = get_cdt_edge_orig(i0, i1, cd, in_tm, &is_isect0);
+ int oe1 = get_cdt_edge_orig(i1, i2, cd, in_tm, &is_isect1);
+ int oe2 = get_cdt_edge_orig(i2, i0, cd, in_tm, &is_isect2);
+ facep = arena->add_face(
+ {v0, v1, v2}, t_orig, {oe0, oe1, oe2}, {is_isect0, is_isect1, is_isect2});
+ }
+ facep->populate_plane(false);
+ faces.append(facep);
+ }
+ }
+ return IMesh(faces);
+}
+
+static IMesh extract_single_tri(const IMesh &tm, int t)
+{
+ Face *f = tm.face(t);
+ return IMesh({f});
+}
+
+static bool bvhtreeverlap_cmp(const BVHTreeOverlap &a, const BVHTreeOverlap &b)
+{
+ if (a.indexA < b.indexA) {
+ return true;
+ }
+ if ((a.indexA == b.indexA) & (a.indexB < b.indexB)) {
+ return true;
+ }
+ return false;
+}
+class TriOverlaps {
+ BVHTree *tree_{nullptr};
+ BVHTree *tree_b_{nullptr};
+ BVHTreeOverlap *overlap_{nullptr};
+ uint overlap_tot_{0};
+
+ struct CBData {
+ const IMesh &tm;
+ std::function<int(int)> shape_fn;
+ int nshapes;
+ bool use_self;
+ };
+
+ public:
+ TriOverlaps(const IMesh &tm,
+ const Array<BoundingBox> &tri_bb,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self)
+ {
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "TriOverlaps construction\n";
+ }
+ /* Tree type is 8 => octtree; axis = 6 => using XYZ axes only. */
+ tree_ = BLI_bvhtree_new(tm.face_size(), FLT_EPSILON, 8, 6);
+ /* In the common case of a binary boolean and no self intersection in
+ * each shape, we will use two trees and simple bounding box overlap. */
+ bool two_trees_no_self = nshapes == 2 && !use_self;
+ if (two_trees_no_self) {
+ tree_b_ = BLI_bvhtree_new(tm.face_size(), FLT_EPSILON, 8, 6);
+ }
+ float bbpts[6];
+ for (int t : tm.face_index_range()) {
+ const BoundingBox &bb = tri_bb[t];
+ copy_v3_v3(bbpts, bb.min);
+ copy_v3_v3(bbpts + 3, bb.max);
+ int shape = shape_fn(tm.face(t)->orig);
+ if (two_trees_no_self) {
+ if (shape == 0) {
+ BLI_bvhtree_insert(tree_, t, bbpts, 2);
+ }
+ else if (shape == 1) {
+ BLI_bvhtree_insert(tree_b_, t, bbpts, 2);
+ }
+ }
+ else {
+ if (shape != -1) {
+ BLI_bvhtree_insert(tree_, t, bbpts, 2);
+ }
+ }
+ }
+ BLI_bvhtree_balance(tree_);
+ if (two_trees_no_self) {
+ BLI_bvhtree_balance(tree_b_);
+ /* Don't expect a lot of trivial intersects in this case. */
+ overlap_ = BLI_bvhtree_overlap(tree_, tree_b_, &overlap_tot_, NULL, NULL);
+ }
+ else {
+ CBData cbdata{tm, shape_fn, nshapes, use_self};
+ if (nshapes == 1 && use_self) {
+ /* Expect a lot of trivial intersects from quads that are triangulated
+ * and faces that share vertices.
+ * Filter them out with a callback. */
+ overlap_ = BLI_bvhtree_overlap(
+ tree_, tree_, &overlap_tot_, only_nontrivial_intersects, &cbdata);
+ }
+ else {
+ overlap_ = BLI_bvhtree_overlap(
+ tree_, tree_, &overlap_tot_, only_different_shapes, &cbdata);
+ }
+ }
+ /* The rest of the code is simpler and easier to parallelize if, in the two-trees case,
+ * we repeat the overlaps with indexA and indexB reversed. It is important that
+ * in the repeated part, sorting will then bring things with indexB together. */
+ if (two_trees_no_self) {
+ overlap_ = static_cast<BVHTreeOverlap *>(
+ MEM_reallocN(overlap_, 2 * overlap_tot_ * sizeof(overlap_[0])));
+ for (uint i = 0; i < overlap_tot_; ++i) {
+ overlap_[overlap_tot_ + i].indexA = overlap_[i].indexB;
+ overlap_[overlap_tot_ + i].indexB = overlap_[i].indexA;
+ }
+ overlap_tot_ += overlap_tot_;
+ }
+ /* Sort the overlaps to bring all the intersects with a given indexA together. */
+ std::sort(overlap_, overlap_ + overlap_tot_, bvhtreeverlap_cmp);
+ if (dbg_level > 0) {
+ std::cout << overlap_tot_ << " overlaps found:\n";
+ for (BVHTreeOverlap ov : overlap()) {
+ std::cout << "A: " << ov.indexA << ", B: " << ov.indexB << "\n";
+ }
+ }
+ }
+
+ ~TriOverlaps()
+ {
+ if (tree_) {
+ BLI_bvhtree_free(tree_);
+ }
+ if (tree_b_) {
+ BLI_bvhtree_free(tree_b_);
+ }
+ if (overlap_) {
+ MEM_freeN(overlap_);
+ }
+ }
+
+ Span<BVHTreeOverlap> overlap() const
+ {
+ return Span<BVHTreeOverlap>(overlap_, overlap_tot_);
+ }
+
+ private:
+ static bool only_nontrivial_intersects(void *userdata,
+ int index_a,
+ int index_b,
+ int UNUSED(thread))
+ {
+ CBData *cbdata = static_cast<CBData *>(userdata);
+ return may_non_trivially_intersect(cbdata->tm.face(index_a), cbdata->tm.face(index_b));
+ }
+
+ static bool only_different_shapes(void *userdata, int index_a, int index_b, int UNUSED(thread))
+ {
+ CBData *cbdata = static_cast<CBData *>(userdata);
+ return cbdata->tm.face(index_a)->orig != cbdata->tm.face(index_b)->orig;
+ }
+};
+
+/**
+ * Data needed for parallelization of #calc_overlap_itts.
+ */
+struct OverlapIttsData {
+ Vector<std::pair<int, int>> intersect_pairs;
+ Map<std::pair<int, int>, ITT_value> &itt_map;
+ const IMesh &tm;
+ IMeshArena *arena;
+
+ OverlapIttsData(Map<std::pair<int, int>, ITT_value> &itt_map, const IMesh &tm, IMeshArena *arena)
+ : itt_map(itt_map), tm(tm), arena(arena)
+ {
+ }
+};
+
+/**
+ * Return a std::pair containing a and b in canonical order:
+ * With a <= b.
+ */
+static std::pair<int, int> canon_int_pair(int a, int b)
+{
+ if (a > b) {
+ std::swap(a, b);
+ }
+ return std::pair<int, int>(a, b);
+}
+
+static void calc_overlap_itts_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ constexpr int dbg_level = 0;
+ OverlapIttsData *data = static_cast<OverlapIttsData *>(userdata);
+ std::pair<int, int> tri_pair = data->intersect_pairs[iter];
+ int a = tri_pair.first;
+ int b = tri_pair.second;
+ if (dbg_level > 0) {
+ std::cout << "calc_overlap_itts_range_func a=" << a << ", b=" << b << "\n";
+ }
+ ITT_value itt = intersect_tri_tri(data->tm, a, b);
+ if (dbg_level > 0) {
+ std::cout << "result of intersecting " << a << " and " << b << " = " << itt << "\n";
+ }
+ BLI_assert(data->itt_map.contains(tri_pair));
+ data->itt_map.add_overwrite(tri_pair, itt);
+}
+
+/**
+ * Fill in itt_map with the vector of ITT_values that result from intersecting the triangles in ov.
+ * Use a canonical order for triangles: (a,b) where a < b.
+ * Don't bother doing this if both a and b are part of the same co-planar cluster, as the
+ * intersection for those will be handled by CDT, later.
+ */
+static void calc_overlap_itts(Map<std::pair<int, int>, ITT_value> &itt_map,
+ const IMesh &tm,
+ const CoplanarClusterInfo &clinfo,
+ const TriOverlaps &ov,
+ IMeshArena *arena)
+{
+ OverlapIttsData data(itt_map, tm, arena);
+ /* Put dummy values in `itt_map` initially,
+ * so map entries will exist when doing the range function.
+ * This means we won't have to protect the `itt_map.add_overwrite` function with a lock. */
+ for (const BVHTreeOverlap &olap : ov.overlap()) {
+ std::pair<int, int> key = canon_int_pair(olap.indexA, olap.indexB);
+ if (!itt_map.contains(key)) {
+ int ca = clinfo.tri_cluster(key.first);
+ int cb = clinfo.tri_cluster(key.second);
+ if (ca == NO_INDEX || ca != cb) {
+ itt_map.add_new(key, ITT_value());
+ data.intersect_pairs.append(key);
+ }
+ }
+ }
+ int tot_intersect_pairs = data.intersect_pairs.size();
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(0, tot_intersect_pairs, &data, calc_overlap_itts_range_func, &settings);
+}
+
+/**
+ * Data needed for parallelization of calc_subdivided_tris.
+ */
+struct OverlapTriRange {
+ int tri_index;
+ int overlap_start;
+ int len;
+};
+struct SubdivideTrisData {
+ Array<IMesh> &r_tri_subdivided;
+ const IMesh &tm;
+ const Map<std::pair<int, int>, ITT_value> &itt_map;
+ Span<BVHTreeOverlap> overlap;
+ IMeshArena *arena;
+
+ /* This vector gives, for each triangle in tm that has an intersection
+ * we want to calculate: what the index of that triangle in tm is,
+ * where it starts in the ov structure as indexA, and how many
+ * overlap pairs have that same indexA (they will be continuous). */
+ Vector<OverlapTriRange> overlap_tri_range;
+
+ SubdivideTrisData(Array<IMesh> &r_tri_subdivided,
+ const IMesh &tm,
+ const Map<std::pair<int, int>, ITT_value> &itt_map,
+ Span<BVHTreeOverlap> overlap,
+ IMeshArena *arena)
+ : r_tri_subdivided(r_tri_subdivided),
+ tm(tm),
+ itt_map(itt_map),
+ overlap(overlap),
+ arena(arena),
+ overlap_tri_range{}
+ {
+ }
+};
+
+static void calc_subdivided_tri_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ constexpr int dbg_level = 0;
+ SubdivideTrisData *data = static_cast<SubdivideTrisData *>(userdata);
+ OverlapTriRange &otr = data->overlap_tri_range[iter];
+ int t = otr.tri_index;
+ if (dbg_level > 0) {
+ std::cout << "calc_subdivided_tri_range_func\nt=" << t << " start=" << otr.overlap_start
+ << " len=" << otr.len << "\n";
+ }
+ constexpr int inline_capacity = 100;
+ Vector<ITT_value, inline_capacity> itts(otr.len);
+ for (int j = otr.overlap_start; j < otr.overlap_start + otr.len; ++j) {
+ int t_other = data->overlap[j].indexB;
+ std::pair<int, int> key = canon_int_pair(t, t_other);
+ ITT_value itt;
+ if (data->itt_map.contains(key)) {
+ itt = data->itt_map.lookup(key);
+ }
+ if (itt.kind != INONE) {
+ itts.append(itt);
+ }
+ if (dbg_level > 0) {
+ std::cout << " tri t" << t_other << "; result = " << itt << "\n";
+ }
+ }
+ if (itts.size() > 0) {
+ CDT_data cd_data = prepare_cdt_input(data->tm, t, itts);
+ do_cdt(cd_data);
+ data->r_tri_subdivided[t] = extract_subdivided_tri(cd_data, data->tm, t, data->arena);
+ if (dbg_level > 0) {
+ std::cout << "subdivide output\n" << data->r_tri_subdivided[t];
+ }
+ }
+}
+
+/**
+ * For each triangle in tm, fill in the corresponding slot in
+ * r_tri_subdivided with the result of intersecting it with
+ * all the other triangles in the mesh, if it intersects any others.
+ * But don't do this for triangles that are part of a cluster.
+ * Also, do nothing here if the answer is just the triangle itself.
+ */
+static void calc_subdivided_tris(Array<IMesh> &r_tri_subdivided,
+ const IMesh &tm,
+ const Map<std::pair<int, int>, ITT_value> &itt_map,
+ const CoplanarClusterInfo &clinfo,
+ const TriOverlaps &ov,
+ IMeshArena *arena)
+{
+ const int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nCALC_SUBDIVIDED_TRIS\n\n";
+ }
+ Span<BVHTreeOverlap> overlap = ov.overlap();
+ SubdivideTrisData data(r_tri_subdivided, tm, itt_map, overlap, arena);
+ int overlap_tot = overlap.size();
+ data.overlap_tri_range = Vector<OverlapTriRange>();
+ data.overlap_tri_range.reserve(overlap_tot);
+ int overlap_index = 0;
+ while (overlap_index < overlap_tot) {
+ int t = overlap[overlap_index].indexA;
+ int i = overlap_index;
+ while (i + 1 < overlap_tot && overlap[i + 1].indexA == t) {
+ ++i;
+ }
+ /* Now overlap[overlap_index] to overlap[i] have indexA == t.
+ * We only record ranges for triangles that are not in clusters,
+ * because the ones in clusters are handled separately. */
+ if (clinfo.tri_cluster(t) == NO_INDEX) {
+ int len = i - overlap_index + 1;
+ if (!(len == 1 && overlap[overlap_index].indexB == t)) {
+ OverlapTriRange range = {t, overlap_index, len};
+ data.overlap_tri_range.append(range);
+# ifdef PERFDEBUG
+ bumpperfcount(0, len); /* Non-cluster overlaps. */
+# endif
+ }
+ }
+ overlap_index = i + 1;
+ }
+ int overlap_tri_range_tot = data.overlap_tri_range.size();
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.min_iter_per_thread = 50;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(
+ 0, overlap_tri_range_tot, &data, calc_subdivided_tri_range_func, &settings);
+}
+
+/* Get first index in ov where indexA == t. Assuming sorted on indexA. */
+static int find_first_overlap_index(const TriOverlaps &ov, int t)
+{
+ Span<BVHTreeOverlap> span = ov.overlap();
+ if (span.size() == 0) {
+ return -1;
+ }
+ BVHTreeOverlap bo{t, -1};
+ const BVHTreeOverlap *p = std::lower_bound(
+ span.begin(), span.end(), bo, [](const BVHTreeOverlap &o1, const BVHTreeOverlap &o2) {
+ return o1.indexA < o2.indexA;
+ });
+ if (p != span.end()) {
+ return p - span.begin();
+ }
+ return -1;
+}
+
+static CDT_data calc_cluster_subdivided(const CoplanarClusterInfo &clinfo,
+ int c,
+ const IMesh &tm,
+ const TriOverlaps &ov,
+ const Map<std::pair<int, int>, ITT_value> &itt_map,
+ IMeshArena *UNUSED(arena))
+{
+ constexpr int dbg_level = 0;
+ BLI_assert(c < clinfo.tot_cluster());
+ const CoplanarCluster &cl = clinfo.cluster(c);
+ /* Make a CDT input with triangles from C and intersects from other triangles in tm. */
+ if (dbg_level > 0) {
+ std::cout << "CALC_CLUSTER_SUBDIVIDED for cluster " << c << " = " << cl << "\n";
+ }
+ /* Get vector itts of all intersections of a triangle of cl with any triangle of tm not
+ * in cl and not co-planar with it (for that latter, if there were an intersection,
+ * it should already be in cluster cl). */
+ Vector<ITT_value> itts;
+ Span<BVHTreeOverlap> ovspan = ov.overlap();
+ for (int t : cl) {
+ if (dbg_level > 0) {
+ std::cout << "find intersects with triangle " << t << " of cluster\n";
+ }
+ int first_i = find_first_overlap_index(ov, t);
+ if (first_i == -1) {
+ continue;
+ }
+ for (int i = first_i; i < ovspan.size() && ovspan[i].indexA == t; ++i) {
+ int t_other = ovspan[i].indexB;
+ if (clinfo.tri_cluster(t_other) != c) {
+ if (dbg_level > 0) {
+ std::cout << "use intersect(" << t << "," << t_other << "\n";
+ }
+ std::pair<int, int> key = canon_int_pair(t, t_other);
+ if (itt_map.contains(key)) {
+ ITT_value itt = itt_map.lookup(key);
+ if (itt.kind != INONE && itt.kind != ICOPLANAR) {
+ itts.append(itt);
+ if (dbg_level > 0) {
+ std::cout << " itt = " << itt << "\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ /* Use CDT to subdivide the cluster triangles and the points and segs in itts. */
+ CDT_data cd_data = prepare_cdt_input_for_cluster(tm, clinfo, c, itts);
+ do_cdt(cd_data);
+ return cd_data;
+}
+
+static IMesh union_tri_subdivides(const blender::Array<IMesh> &tri_subdivided)
+{
+ int tot_tri = 0;
+ for (const IMesh &m : tri_subdivided) {
+ tot_tri += m.face_size();
+ }
+ Array<Face *> faces(tot_tri);
+ int face_index = 0;
+ for (const IMesh &m : tri_subdivided) {
+ for (Face *f : m.faces()) {
+ faces[face_index++] = f;
+ }
+ }
+ return IMesh(faces);
+}
+
+static CoplanarClusterInfo find_clusters(const IMesh &tm, const Array<BoundingBox> &tri_bb)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "FIND_CLUSTERS\n";
+ }
+ CoplanarClusterInfo ans(tm.face_size());
+ /* There can be more than one #CoplanarCluster per plane. Accumulate them in
+ * a Vector. We will have to merge some elements of the Vector as we discover
+ * triangles that form intersection bridges between two or more clusters. */
+ Map<Plane, Vector<CoplanarCluster>> plane_cls;
+ plane_cls.reserve(tm.face_size());
+ for (int t : tm.face_index_range()) {
+ /* Use a canonical version of the plane for map index.
+ * We can't just store the canonical version in the face
+ * since canonicalizing loses the orientation of the normal. */
+ Plane tplane = *tm.face(t)->plane;
+ BLI_assert(tplane.exact_populated());
+ tplane.make_canonical();
+ if (dbg_level > 0) {
+ std::cout << "plane for tri " << t << " = " << &tplane << "\n";
+ }
+ /* Assume all planes are in canonical from (see canon_plane()). */
+ if (plane_cls.contains(tplane)) {
+ Vector<CoplanarCluster> &curcls = plane_cls.lookup(tplane);
+ if (dbg_level > 0) {
+ std::cout << "already has " << curcls.size() << " clusters\n";
+ }
+ int proj_axis = mpq3::dominant_axis(tplane.norm_exact);
+ /* Partition `curcls` into those that intersect t non-trivially, and those that don't. */
+ Vector<CoplanarCluster *> int_cls;
+ Vector<CoplanarCluster *> no_int_cls;
+ for (CoplanarCluster &cl : curcls) {
+ if (bbs_might_intersect(tri_bb[t], cl.bounding_box()) &&
+ non_trivially_coplanar_intersects(tm, t, cl, proj_axis)) {
+ if (dbg_level > 1) {
+ std::cout << "append to int_cls\n";
+ }
+ int_cls.append(&cl);
+ }
+ else {
+ if (dbg_level > 1) {
+ std::cout << "append to no_int_cls\n";
+ }
+ no_int_cls.append(&cl);
+ }
+ }
+ if (int_cls.size() == 0) {
+ /* t doesn't intersect any existing cluster in its plane, so make one just for it. */
+ if (dbg_level > 1) {
+ std::cout << "no intersecting clusters for t, make a new one\n";
+ }
+ curcls.append(CoplanarCluster(t, tri_bb[t]));
+ }
+ else if (int_cls.size() == 1) {
+ /* t intersects exactly one existing cluster, so can add t to that cluster. */
+ if (dbg_level > 1) {
+ std::cout << "exactly one existing cluster, " << int_cls[0] << ", adding to it\n";
+ }
+ int_cls[0]->add_tri(t, tri_bb[t]);
+ }
+ else {
+ /* t intersections 2 or more existing clusters: need to merge them and replace all the
+ * originals with the merged one in `curcls`. */
+ if (dbg_level > 1) {
+ std::cout << "merging\n";
+ }
+ CoplanarCluster mergecl;
+ mergecl.add_tri(t, tri_bb[t]);
+ for (CoplanarCluster *cl : int_cls) {
+ for (int t : *cl) {
+ mergecl.add_tri(t, tri_bb[t]);
+ }
+ }
+ Vector<CoplanarCluster> newvec;
+ newvec.append(mergecl);
+ for (CoplanarCluster *cl_no_int : no_int_cls) {
+ newvec.append(*cl_no_int);
+ }
+ plane_cls.add_overwrite(tplane, newvec);
+ }
+ }
+ else {
+ if (dbg_level > 0) {
+ std::cout << "first cluster for its plane\n";
+ }
+ plane_cls.add_new(tplane, Vector<CoplanarCluster>{CoplanarCluster(t, tri_bb[t])});
+ }
+ }
+ /* Does this give deterministic order for cluster ids? I think so, since
+ * hash for planes is on their values, not their addresses. */
+ for (auto item : plane_cls.items()) {
+ for (const CoplanarCluster &cl : item.value) {
+ if (cl.tot_tri() > 1) {
+ ans.add_cluster(cl);
+ }
+ }
+ }
+
+ return ans;
+}
+
+static bool face_is_degenerate(const Face *f)
+{
+ const Face &face = *f;
+ const Vert *v0 = face[0];
+ const Vert *v1 = face[1];
+ const Vert *v2 = face[2];
+ if (v0 == v1 || v0 == v2 || v1 == v2) {
+ return true;
+ }
+ double3 da = v2->co - v0->co;
+ double3 db = v2->co - v1->co;
+ double3 dab = double3::cross_high_precision(da, db);
+ double dab_length_squared = dab.length_squared();
+ double err_bound = supremum_dot_cross(dab, dab) * index_dot_cross * DBL_EPSILON;
+ if (dab_length_squared > err_bound) {
+ return false;
+ }
+ mpq3 a = v2->co_exact - v0->co_exact;
+ mpq3 b = v2->co_exact - v1->co_exact;
+ mpq3 ab = mpq3::cross(a, b);
+ if (ab.x == 0 && ab.y == 0 && ab.z == 0) {
+ return true;
+ }
+
+ return false;
+}
+
+/* Data and functions to test triangle degeneracy in parallel. */
+struct DegenData {
+ const IMesh &tm;
+};
+
+struct DegenChunkData {
+ bool has_degenerate_tri = false;
+};
+
+static void degenerate_range_func(void *__restrict userdata,
+ const int iter,
+ const TaskParallelTLS *__restrict tls)
+{
+ DegenData *data = static_cast<DegenData *>(userdata);
+ DegenChunkData *chunk_data = static_cast<DegenChunkData *>(tls->userdata_chunk);
+ const Face *f = data->tm.face(iter);
+ bool is_degenerate = face_is_degenerate(f);
+ chunk_data->has_degenerate_tri |= is_degenerate;
+}
+
+static void degenerate_reduce(const void *__restrict UNUSED(userdata),
+ void *__restrict chunk_join,
+ void *__restrict chunk)
+{
+ DegenChunkData *degen_chunk_join = static_cast<DegenChunkData *>(chunk_join);
+ DegenChunkData *degen_chunk = static_cast<DegenChunkData *>(chunk);
+ degen_chunk_join->has_degenerate_tri |= degen_chunk->has_degenerate_tri;
+}
+
+/* Does triangle #IMesh tm have any triangles with zero area? */
+static bool has_degenerate_tris(const IMesh &tm)
+{
+ DegenData degen_data = {tm};
+ DegenChunkData degen_chunk_data;
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.userdata_chunk = &degen_chunk_data;
+ settings.userdata_chunk_size = sizeof(degen_chunk_data);
+ settings.func_reduce = degenerate_reduce;
+ settings.min_iter_per_thread = 1000;
+ settings.use_threading = intersect_use_threading;
+ BLI_task_parallel_range(0, tm.face_size(), &degen_data, degenerate_range_func, &settings);
+ return degen_chunk_data.has_degenerate_tri;
+}
+
+static IMesh remove_degenerate_tris(const IMesh &tm_in)
+{
+ IMesh ans;
+ Vector<Face *> new_faces;
+ new_faces.reserve(tm_in.face_size());
+ for (Face *f : tm_in.faces()) {
+ if (!face_is_degenerate(f)) {
+ new_faces.append(f);
+ }
+ }
+ ans.set_faces(new_faces);
+ return ans;
+}
+
+/* This is the main routine for calculating the self_intersection of a triangle mesh. */
+IMesh trimesh_self_intersect(const IMesh &tm_in, IMeshArena *arena)
+{
+ return trimesh_nary_intersect(
+ tm_in, 1, [](int UNUSED(t)) { return 0; }, true, arena);
+}
+
+IMesh trimesh_nary_intersect(const IMesh &tm_in,
+ int nshapes,
+ std::function<int(int)> shape_fn,
+ bool use_self,
+ IMeshArena *arena)
+{
+ constexpr int dbg_level = 0;
+ if (dbg_level > 0) {
+ std::cout << "\nTRIMESH_NARY_INTERSECT nshapes=" << nshapes << " use_self=" << use_self
+ << "\n";
+ for (const Face *f : tm_in.faces()) {
+ BLI_assert(f->is_tri());
+ UNUSED_VARS_NDEBUG(f);
+ }
+ if (dbg_level > 1) {
+ std::cout << "input mesh:\n" << tm_in;
+ for (int t : tm_in.face_index_range()) {
+ std::cout << "shape(" << t << ") = " << shape_fn(tm_in.face(t)->orig) << "\n";
+ }
+ }
+ }
+# ifdef PERFDEBUG
+ perfdata_init();
+ double start_time = PIL_check_seconds_timer();
+ std::cout << "trimesh_nary_intersect start\n";
+# endif
+ /* Usually can use tm_in but if it has degenerate or illegal triangles,
+ * then need to work on a copy of it without those triangles. */
+ const IMesh *tm_clean = &tm_in;
+ IMesh tm_cleaned;
+ if (has_degenerate_tris(tm_in)) {
+ if (dbg_level > 0) {
+ std::cout << "cleaning degenerate triangles\n";
+ }
+ tm_cleaned = remove_degenerate_tris(tm_in);
+ tm_clean = &tm_cleaned;
+ if (dbg_level > 1) {
+ std::cout << "cleaned input mesh:\n" << tm_cleaned;
+ }
+ }
+ /* Temporary, while developing: populate all plane normals exactly. */
+ for (Face *f : tm_clean->faces()) {
+ f->populate_plane(true);
+ }
+# ifdef PERFDEBUG
+ double clean_time = PIL_check_seconds_timer();
+ std::cout << "cleaned, time = " << clean_time - start_time << "\n";
+# endif
+ Array<BoundingBox> tri_bb = calc_face_bounding_boxes(*tm_clean);
+# ifdef PERFDEBUG
+ double bb_calc_time = PIL_check_seconds_timer();
+ std::cout << "bbs calculated, time = " << bb_calc_time - clean_time << "\n";
+# endif
+ TriOverlaps tri_ov(*tm_clean, tri_bb, nshapes, shape_fn, use_self);
+# ifdef PERFDEBUG
+ double overlap_time = PIL_check_seconds_timer();
+ std::cout << "intersect overlaps calculated, time = " << overlap_time - bb_calc_time << "\n";
+
+# endif
+ CoplanarClusterInfo clinfo = find_clusters(*tm_clean, tri_bb);
+ if (dbg_level > 1) {
+ std::cout << clinfo;
+ }
+# ifdef PERFDEBUG
+ double find_cluster_time = PIL_check_seconds_timer();
+ std::cout << "clusters found, time = " << find_cluster_time - overlap_time << "\n";
+ doperfmax(0, tm_in.face_size());
+ doperfmax(1, clinfo.tot_cluster());
+ doperfmax(2, tri_ov.overlap().size());
+# endif
+ /* itt_map((a,b)) will hold the intersection value resulting from intersecting
+ * triangles with indices a and b, where a < b. */
+ Map<std::pair<int, int>, ITT_value> itt_map;
+ itt_map.reserve(tri_ov.overlap().size());
+ calc_overlap_itts(itt_map, *tm_clean, clinfo, tri_ov, arena);
+# ifdef PERFDEBUG
+ double itt_time = PIL_check_seconds_timer();
+ std::cout << "itts found, time = " << itt_time - find_cluster_time << "\n";
+# endif
+ Array<IMesh> tri_subdivided(tm_clean->face_size());
+ calc_subdivided_tris(tri_subdivided, *tm_clean, itt_map, clinfo, tri_ov, arena);
+# ifdef PERFDEBUG
+ double subdivided_tris_time = PIL_check_seconds_timer();
+ std::cout << "subdivided tris found, time = " << subdivided_tris_time - itt_time << "\n";
+# endif
+ Array<CDT_data> cluster_subdivided(clinfo.tot_cluster());
+ for (int c : clinfo.index_range()) {
+ cluster_subdivided[c] = calc_cluster_subdivided(clinfo, c, *tm_clean, tri_ov, itt_map, arena);
+ }
+# ifdef PERFDEBUG
+ double cluster_subdivide_time = PIL_check_seconds_timer();
+ std::cout << "subdivided clusters found, time = "
+ << cluster_subdivide_time - subdivided_tris_time << "\n";
+# endif
+ for (int t : tm_clean->face_index_range()) {
+ int c = clinfo.tri_cluster(t);
+ if (c != NO_INDEX) {
+ BLI_assert(tri_subdivided[t].face_size() == 0);
+ tri_subdivided[t] = extract_subdivided_tri(cluster_subdivided[c], *tm_clean, t, arena);
+ }
+ else if (tri_subdivided[t].face_size() == 0) {
+ tri_subdivided[t] = extract_single_tri(*tm_clean, t);
+ }
+ }
+# ifdef PERFDEBUG
+ double extract_time = PIL_check_seconds_timer();
+ std::cout << "triangles extracted, time = " << extract_time - cluster_subdivide_time << "\n";
+# endif
+ IMesh combined = union_tri_subdivides(tri_subdivided);
+ if (dbg_level > 1) {
+ std::cout << "TRIMESH_NARY_INTERSECT answer:\n";
+ std::cout << combined;
+ }
+# ifdef PERFDEBUG
+ double end_time = PIL_check_seconds_timer();
+ std::cout << "triangles combined, time = " << end_time - extract_time << "\n";
+ std::cout << "trimesh_nary_intersect done, total time = " << end_time - start_time << "\n";
+ dump_perfdata();
+# endif
+ return combined;
+}
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarCluster &cl)
+{
+ os << "cl(";
+ bool first = true;
+ for (const int t : cl) {
+ if (first) {
+ first = false;
+ }
+ else {
+ os << ",";
+ }
+ os << t;
+ }
+ os << ")";
+ return os;
+}
+
+static std::ostream &operator<<(std::ostream &os, const CoplanarClusterInfo &clinfo)
+{
+ os << "Coplanar Cluster Info:\n";
+ for (int c : clinfo.index_range()) {
+ os << c << ": " << clinfo.cluster(c) << "\n";
+ }
+ return os;
+}
+
+static std::ostream &operator<<(std::ostream &os, const ITT_value &itt)
+{
+ switch (itt.kind) {
+ case INONE:
+ os << "none";
+ break;
+ case IPOINT:
+ os << "point " << itt.p1;
+ break;
+ case ISEGMENT:
+ os << "segment " << itt.p1 << " " << itt.p2;
+ break;
+ case ICOPLANAR:
+ os << "co-planar t" << itt.t_source;
+ break;
+ }
+ return os;
+}
+
+/**
+ * Writing the obj_mesh has the side effect of populating verts.
+ */
+void write_obj_mesh(IMesh &m, const std::string &objname)
+{
+ /* Would like to use #BKE_tempdir_base() here, but that brings in dependence on kernel library.
+ * This is just for developer debugging anyway,
+ * and should never be called in production Blender. */
+# ifdef _WIN_32
+ const char *objdir = BLI_getenv("HOME");
+# else
+ const char *objdir = "/tmp/";
+# endif
+ if (m.face_size() == 0) {
+ return;
+ }
+
+ std::string fname = std::string(objdir) + objname + std::string(".obj");
+ std::ofstream f;
+ f.open(fname);
+ if (!f) {
+ std::cout << "Could not open file " << fname << "\n";
+ return;
+ }
+
+ if (!m.has_verts()) {
+ m.populate_vert();
+ }
+ for (const Vert *v : m.vertices()) {
+ const double3 dv = v->co;
+ f << "v " << dv[0] << " " << dv[1] << " " << dv[2] << "\n";
+ }
+ int i = 0;
+ for (const Face *face : m.faces()) {
+ /* OBJ files use 1-indexing for vertices. */
+ f << "f ";
+ for (const Vert *v : *face) {
+ int i = m.lookup_vert(v);
+ BLI_assert(i != NO_INDEX);
+ /* OBJ files use 1-indexing for vertices. */
+ f << i + 1 << " ";
+ }
+ f << "\n";
+ ++i;
+ }
+ f.close();
+}
+
+# ifdef PERFDEBUG
+struct PerfCounts {
+ Vector<int> count;
+ Vector<const char *> count_name;
+ Vector<int> max;
+ Vector<const char *> max_name;
+};
+
+static PerfCounts *perfdata = nullptr;
+
+static void perfdata_init(void)
+{
+ perfdata = new PerfCounts;
+
+ /* count 0. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("Non-cluster overlaps");
+
+ /* count 1. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("intersect_tri_tri calls");
+
+ /* count 2. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("tri tri intersects decided by filter plane tests");
+
+ /* count 3. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("tri tri intersects decided by exact plane tests");
+
+ /* count 4. */
+ perfdata->count.append(0);
+ perfdata->count_name.append("final non-NONE intersects");
+
+ /* max 0. */
+ perfdata->max.append(0);
+ perfdata->max_name.append("total faces");
+
+ /* max 1. */
+ perfdata->max.append(0);
+ perfdata->max_name.append("total clusters");
+
+ /* max 2. */
+ perfdata->max.append(0);
+ perfdata->max_name.append("total overlaps");
+}
+
+static void incperfcount(int countnum)
+{
+ perfdata->count[countnum]++;
+}
+
+static void bumpperfcount(int countnum, int amt)
+{
+ perfdata->count[countnum] += amt;
+}
+
+static void doperfmax(int maxnum, int val)
+{
+ perfdata->max[maxnum] = max_ii(perfdata->max[maxnum], val);
+}
+
+static void dump_perfdata(void)
+{
+ std::cout << "\nPERFDATA\n";
+ for (int i : perfdata->count.index_range()) {
+ std::cout << perfdata->count_name[i] << " = " << perfdata->count[i] << "\n";
+ }
+ for (int i : perfdata->max.index_range()) {
+ std::cout << perfdata->max_name[i] << " = " << perfdata->max[i] << "\n";
+ }
+ delete perfdata;
+}
+# endif
+
+}; // namespace blender::meshintersect
+
+#endif // WITH_GMP