36# include <tbb/parallel_reduce.h>
37# include <tbb/spin_mutex.h>
54 const Vert *v_[2] = {
nullptr,
nullptr};
58 Edge(
const Vert *v0,
const Vert *v1)
60 if (v0->id <= v1->id) {
70 const Vert *v0()
const
75 const Vert *v1()
const
87 return v_[0]->id == other.v_[0]->id && v_[1]->id == other.v_[1]->id;
98 if (
e.v0() ==
nullptr) {
103 os <<
"(" <<
e.v0() <<
"," <<
e.v1() <<
")";
108static std::ostream &
operator<<(std::ostream &os,
const Span<int> a)
112 if (
i != a.size() - 1) {
119static std::ostream &
operator<<(std::ostream &os,
const Array<int> &iarr)
121 os << Span<int>(iarr);
126class TriMeshTopology : NonCopyable {
128 Map<Edge, Vector<int> *> edge_tri_;
130 Map<const Vert *, Vector<Edge>> vert_edges_;
133 TriMeshTopology(
const IMesh &tm);
138 int other_tri_if_manifold(
Edge e,
int t)
const
141 if (p !=
nullptr && (*p)->size() == 2) {
142 return ((**p)[0] == t) ? (**p)[1] : (**p)[0];
148 const Vector<int> *edge_tris(
Edge e)
const
155 Span<Edge> vert_edges(
const Vert *
v)
const
162 return edge_tri_.
items();
166TriMeshTopology::TriMeshTopology(
const IMesh &tm)
168 const int dbg_level = 0;
170 std::cout <<
"TRIMESHTOPOLOGY CONSTRUCTION\n";
174 const int estimate_num_edges = 2 * tm.face_size();
175 const int estimate_verts_num = tm.face_size();
176 edge_tri_.
reserve(estimate_num_edges);
177 vert_edges_.
reserve(estimate_verts_num);
178 for (
int t : tm.face_index_range()) {
179 const Face &tri = *tm.face(t);
181 for (
int i = 0;
i < 3; ++
i) {
182 const Vert *
v = tri[
i];
183 const Vert *vnext = tri[(
i + 1) % 3];
186 if (edges ==
nullptr) {
187 vert_edges_.
add_new(
v, Vector<Edge>());
195 edge_tri_.
add_new(
e,
new Vector<int>{t});
198 (*p)->append_non_duplicates(t);
204 std::cout <<
"After TriMeshTopology construction\n";
205 for (
auto item : edge_tri_.
items()) {
206 std::cout <<
"tris for edge " << item.key <<
": " << *item.value <<
"\n";
207 constexpr bool print_stats =
false;
212 for (
auto item : vert_edges_.
items()) {
213 std::cout <<
"edges for vert " << item.key <<
":\n";
214 for (
const Edge &
e : item.value) {
215 std::cout <<
" " <<
e <<
"\n";
222TriMeshTopology::~TriMeshTopology()
224 Vector<Vector<int> *> values;
227 for (
auto *item : edge_tri_.
values()) {
232 for (int i : range) {
260 IndexRange tri_range()
const
262 return IndexRange(tri_.
size());
265 Span<int> tris()
const
267 return Span<int>(tri_);
270 int cell_above{NO_INDEX};
271 int cell_below{NO_INDEX};
272 int component{NO_INDEX};
277 os <<
"Patch " << patch.tris();
278 if (patch.cell_above != NO_INDEX) {
279 os <<
" cell_above=" << patch.cell_above;
282 os <<
" cell_above not set";
284 if (patch.cell_below != NO_INDEX) {
285 os <<
" cell_below=" << patch.cell_below;
288 os <<
" cell_below not set";
295 Vector<Patch> patch_;
297 Array<int> tri_patch_;
299 Map<std::pair<int, int>,
Edge> pp_edge_;
302 explicit PatchesInfo(
int ntri)
304 constexpr int max_expected_patch_patch_incidences = 100;
305 tri_patch_ = Array<int>(ntri, NO_INDEX);
306 pp_edge_.
reserve(max_expected_patch_patch_incidences);
309 int tri_patch(
int t)
const
311 return tri_patch_[t];
320 void grow_patch(
int patch_index,
int t)
322 tri_patch_[t] = patch_index;
323 patch_[patch_index].add_tri(t);
326 bool tri_is_assigned(
int t)
const
328 return tri_patch_[t] != NO_INDEX;
331 const Patch &patch(
int patch_index)
const
333 return patch_[patch_index];
336 Patch &patch(
int patch_index)
338 return patch_[patch_index];
341 int tot_patch()
const
343 return patch_.
size();
346 IndexRange index_range()
const
348 return IndexRange(patch_.
size());
351 const Patch *
begin()
const
353 return patch_.
begin();
356 const Patch *end()
const
363 return patch_.
begin();
371 void add_new_patch_patch_edge(
int p1,
int p2,
Edge e)
373 pp_edge_.
add_new(std::pair<int, int>(p1, p2),
e);
374 pp_edge_.
add_new(std::pair<int, int>(p2, p1),
e);
377 Edge patch_patch_edge(
int p1,
int p2)
382 const Map<std::pair<int, int>,
Edge> &patch_patch_edge_map()
388static bool apply_bool_op(BoolOpType bool_optype,
const Array<int> &winding);
398 int merged_to_{NO_INDEX};
399 bool winding_assigned_{
false};
401 bool in_output_volume_{
false};
404 bool zero_volume_{
false};
409 void add_patch(
int p)
412 zero_volume_ =
false;
415 const Set<int> &patches()
const
421 int patch_other(
int p)
const
423 if (patches_.
size() != 2) {
426 for (
int pother : patches_) {
434 Span<int> winding()
const
436 return Span<int>(winding_);
439 void init_winding(
int winding_len)
441 winding_ = Array<int>(winding_len);
444 void seed_ambient_winding()
447 winding_assigned_ =
true;
450 void set_winding_and_in_output_volume(
const Cell &from_cell,
453 BoolOpType bool_optype)
455 std::copy(from_cell.winding().begin(), from_cell.winding().end(), winding_.
begin());
457 winding_[shape] += delta;
459 winding_assigned_ =
true;
460 in_output_volume_ = apply_bool_op(bool_optype, winding_);
463 bool in_output_volume()
const
465 return in_output_volume_;
468 bool winding_assigned()
const
470 return winding_assigned_;
473 bool zero_volume()
const
478 int merged_to()
const
483 void set_merged_to(
int c)
492 void check_for_zero_volume(
const PatchesInfo &pinfo,
const IMesh &mesh);
495static std::ostream &
operator<<(std::ostream &os,
const Cell &cell)
497 os <<
"Cell patches";
498 for (
int p : cell.patches()) {
499 std::cout <<
" " << p;
501 if (cell.winding().size() > 0) {
502 os <<
" winding=" << cell.winding();
503 os <<
" in_output_volume=" << cell.in_output_volume();
505 os <<
" zv=" << cell.zero_volume();
510static bool tris_have_same_verts(
const IMesh &mesh,
int t1,
int t2)
512 const Face &tri1 = *mesh.face(t1);
513 const Face &tri2 = *mesh.face(t2);
514 BLI_assert(tri1.size() == 3 && tri2.size() == 3);
515 if (tri1.vert[0] == tri2.vert[0]) {
516 return ((tri1.vert[1] == tri2.vert[1] && tri1.vert[2] == tri2.vert[2]) ||
517 (tri1.vert[1] == tri2.vert[2] && tri1.vert[2] == tri2.vert[1]));
519 if (tri1.vert[0] == tri2.vert[1]) {
520 return ((tri1.vert[1] == tri2.vert[0] && tri1.vert[2] == tri2.vert[2]) ||
521 (tri1.vert[1] == tri2.vert[2] && tri1.vert[2] == tri2.vert[0]));
523 if (tri1.vert[0] == tri2.vert[2]) {
524 return ((tri1.vert[1] == tri2.vert[0] && tri1.vert[2] == tri2.vert[1]) ||
525 (tri1.vert[1] == tri2.vert[1] && tri1.vert[2] == tri2.vert[0]));
535void Cell::check_for_zero_volume(
const PatchesInfo &pinfo,
const IMesh &mesh)
537 if (patches_.size() == 2) {
538 int p1_index = NO_INDEX;
539 int p2_index = NO_INDEX;
540 for (
int p : patches_) {
541 if (p1_index == NO_INDEX) {
548 BLI_assert(p1_index != NO_INDEX && p2_index != NO_INDEX);
549 const Patch &p1 = pinfo.patch(p1_index);
550 const Patch &p2 = pinfo.patch(p2_index);
551 if (p1.tot_tri() == 1 && p2.tot_tri() == 1) {
552 if (tris_have_same_verts(mesh, p1.tri(0), p2.tri(0))) {
564 CellsInfo() =
default;
577 const Cell &cell(
int c)
const
587 IndexRange index_range()
const
592 const Cell *
begin()
const
594 return cell_.
begin();
597 const Cell *end()
const
604 return cell_.
begin();
612 void init_windings(
int winding_len)
614 for (Cell &cell : cell_) {
615 cell.init_winding(winding_len);
623static void write_obj_cell_patch(
const IMesh &m,
624 const CellsInfo &cinfo,
625 const PatchesInfo &pinfo,
627 const std::string &
name)
634 if (objdir ==
nullptr) {
635 std::cout <<
"Could not access home directory\n";
639 const char *objdir =
"/tmp/";
642 std::string fname = std::string(objdir) +
name + std::string(
"_cellpatch.obj");
646 std::cout <<
"Could not open file " << fname <<
"\n";
653 f <<
"o cellpatch\n";
654 for (
const Vert *
v : mm.vertices()) {
656 f <<
"v " << dv[0] <<
" " << dv[1] <<
" " << dv[2] <<
"\n";
659 for (
int p : pinfo.index_range()) {
660 f <<
"g patch" << p <<
"\n";
661 const Patch &patch = pinfo.patch(p);
662 for (
int t : patch.tris()) {
663 const Face &tri = *mm.face(t);
665 for (
const Vert *
v : tri) {
666 f << mm.lookup_vert(
v) + 1 <<
" ";
672 for (
int c : cinfo.index_range()) {
673 f <<
"g cell" << c <<
"\n";
674 const Cell &cell = cinfo.cell(c);
675 for (
int p : cell.patches()) {
676 const Patch &patch = pinfo.patch(p);
677 for (
int t : patch.tris()) {
678 const Face &tri = *mm.face(t);
680 for (
const Vert *
v : tri) {
681 f << mm.lookup_vert(
v) + 1 <<
" ";
690static void merge_cells(
int merge_to,
int merge_from, CellsInfo &cinfo, PatchesInfo &pinfo)
692 if (merge_to == merge_from) {
695 Cell &merge_from_cell = cinfo.cell(merge_from);
696 Cell &merge_to_cell = cinfo.cell(merge_to);
697 int final_merge_to = merge_to;
698 while (merge_to_cell.merged_to() != NO_INDEX) {
699 final_merge_to = merge_to_cell.merged_to();
700 merge_to_cell = cinfo.cell(final_merge_to);
702 for (
int cell_p : merge_from_cell.patches()) {
703 merge_to_cell.add_patch(cell_p);
704 Patch &patch = pinfo.patch(cell_p);
705 if (patch.cell_above == merge_from) {
706 patch.cell_above = merge_to;
708 if (patch.cell_below == merge_from) {
709 patch.cell_below = merge_to;
712 merge_from_cell.set_merged_to(final_merge_to);
718static PatchesInfo find_patches(
const IMesh &tm,
const TriMeshTopology &tmtopo)
720 const int dbg_level = 0;
722 std::cout <<
"\nFIND_PATCHES\n";
724 int ntri = tm.face_size();
725 PatchesInfo pinfo(ntri);
731 threading::parallel_for(tm.face_index_range(), 2048, [&](
IndexRange range) {
732 for (int t : range) {
733 const Face &tri = *tm.face(t);
734 for (int i = 0; i < 3; ++i) {
735 Edge e(tri[i], tri[(i + 1) % 3]);
736 t_others[t][i] = tmtopo.other_tri_if_manifold(e, t);
740 for (
int t : tm.face_index_range()) {
741 if (pinfo.tri_patch(t) == -1) {
742 cur_patch_grow.
push(t);
743 int cur_patch_index = pinfo.add_patch();
744 while (!cur_patch_grow.
is_empty()) {
745 int tcand = cur_patch_grow.
pop();
747 std::cout <<
"pop tcand = " << tcand <<
"; assigned = " << pinfo.tri_is_assigned(tcand)
750 if (pinfo.tri_is_assigned(tcand)) {
754 std::cout <<
"grow patch from seed tcand=" << tcand <<
"\n";
756 pinfo.grow_patch(cur_patch_index, tcand);
757 const Face &tri = *tm.face(tcand);
758 for (
int i = 0;
i < 3; ++
i) {
759 Edge e(tri[
i], tri[(
i + 1) % 3]);
760 int t_other = t_others[tcand][
i];
762 std::cout <<
" edge " <<
e <<
" generates t_other=" << t_other <<
"\n";
764 if (t_other != NO_INDEX) {
765 if (!pinfo.tri_is_assigned(t_other)) {
767 std::cout <<
" push t_other = " << t_other <<
"\n";
769 cur_patch_grow.
push(t_other);
775 std::cout <<
" e non-manifold case\n";
778 if (etris !=
nullptr) {
780 int t_other = (*etris)[
i];
781 if (t_other != tcand && pinfo.tri_is_assigned(t_other)) {
782 int p_other = pinfo.tri_patch(t_other);
783 if (p_other == cur_patch_index) {
786 if (pinfo.patch_patch_edge(cur_patch_index, p_other).v0() ==
nullptr) {
787 pinfo.add_new_patch_patch_edge(cur_patch_index, p_other,
e);
789 std::cout <<
"added patch_patch_edge (" << cur_patch_index <<
"," << p_other
790 <<
") = " <<
e <<
"\n";
802 std::cout <<
"\nafter FIND_PATCHES: found " << pinfo.tot_patch() <<
" patches\n";
803 for (
int p : pinfo.index_range()) {
804 std::cout << p <<
": " << pinfo.patch(p) <<
"\n";
807 std::cout <<
"\ntriangle map\n";
808 for (
int t : tm.face_index_range()) {
809 std::cout << t <<
": " << tm.face(t) <<
" patch " << pinfo.tri_patch(t) <<
"\n";
812 std::cout <<
"\npatch-patch incidences\n";
813 for (
int p1 : pinfo.index_range()) {
814 for (
int p2 : pinfo.index_range()) {
815 Edge e = pinfo.patch_patch_edge(p1, p2);
816 if (
e.v0() !=
nullptr) {
817 std::cout <<
"p" << p1 <<
" and p" << p2 <<
" share edge " <<
e <<
"\n";
831static const Vert *find_flap_vert(
const Face &tri,
const Edge e,
bool *r_rev)
835 if (tri[0] ==
e.v0()) {
836 if (tri[1] ==
e.v1()) {
841 if (tri[2] !=
e.v1()) {
848 else if (tri[1] ==
e.v0()) {
849 if (tri[2] ==
e.v1()) {
854 if (tri[0] !=
e.v1()) {
862 if (tri[2] !=
e.v0()) {
865 if (tri[0] ==
e.v1()) {
870 if (tri[1] !=
e.v1()) {
894static int sort_tris_class(
const Face &tri,
const Face &tri0,
const Edge e)
896 const int dbg_level = 0;
898 std::cout <<
"classify e = " <<
e <<
"\n";
900 mpq3 a0 = tri0[0]->co_exact;
901 mpq3 a1 = tri0[1]->co_exact;
902 mpq3 a2 = tri0[2]->co_exact;
905 const Vert *flapv0 = find_flap_vert(tri0,
e, &rev0);
906 const Vert *flapv = find_flap_vert(tri,
e, &rev);
908 std::cout <<
" t0 = " << tri0[0] <<
" " << tri0[1] <<
" " << tri0[2];
909 std::cout <<
" rev0 = " << rev0 <<
" flapv0 = " << flapv0 <<
"\n";
910 std::cout <<
" t = " << tri[0] <<
" " << tri[1] <<
" " << tri[2];
911 std::cout <<
" rev = " << rev <<
" flapv = " << flapv <<
"\n";
913 BLI_assert(flapv !=
nullptr && flapv0 !=
nullptr);
914 const mpq3 flap = flapv->co_exact;
916 int orient =
orient3d(a0, a1, a2, flap);
921 else if (orient < 0) {
925 ans = flapv == flapv0 ? 1 : 2;
928 std::cout <<
" orient = " << orient <<
" ans = " << ans <<
"\n";
933constexpr int EXTRA_TRI_INDEX = INT_MAX;
941static void sort_by_signed_triangle_index(
Vector<int> &g,
944 const Face *extra_tri)
948 const Face &tri = g[
i] == EXTRA_TRI_INDEX ? *extra_tri : *tm.face(g[
i]);
950 find_flap_vert(tri,
e, &rev);
951 signed_g[
i] = rev ? -g[
i] : g[
i];
953 std::sort(signed_g.begin(), signed_g.end());
956 g[
i] =
abs(signed_g[
i]);
975 const IMesh &tm,
const Edge e,
const Span<int> tris,
const int t0,
const Face *extra_tri)
987 const int dbg_level = 0;
995 std::cout <<
"sort_tris_around_edge " <<
e <<
"\n";
996 std::cout <<
"tris = " << tris <<
"\n";
997 std::cout <<
"t0 = " << t0 <<
"\n";
1003 std::array<Vector<int> *, 4> groups = {&g1, &g2, &g3, &g4};
1004 const Face &triref = *tm.face(tris[0]);
1010 BLI_assert(t < tm.face_size() || (t == EXTRA_TRI_INDEX && extra_tri !=
nullptr));
1011 const Face &tri = (t == EXTRA_TRI_INDEX) ? *extra_tri : *tm.face(t);
1012 if (dbg_level > 2) {
1013 std::cout <<
"classifying tri " << t <<
" with respect to " << tris[0] <<
"\n";
1015 int group_num = sort_tris_class(tri, triref,
e);
1016 if (dbg_level > 2) {
1017 std::cout <<
" classify result : " << group_num <<
"\n";
1019 groups[group_num - 1]->append(t);
1021 if (dbg_level > 1) {
1022 std::cout <<
"g1 = " << g1 <<
"\n";
1023 std::cout <<
"g2 = " << g2 <<
"\n";
1024 std::cout <<
"g3 = " << g3 <<
"\n";
1025 std::cout <<
"g4 = " << g4 <<
"\n";
1027 if (g1.
size() > 1) {
1028 sort_by_signed_triangle_index(g1,
e, tm, extra_tri);
1029 if (dbg_level > 1) {
1030 std::cout <<
"g1 sorted: " << g1 <<
"\n";
1033 if (g2.
size() > 1) {
1034 sort_by_signed_triangle_index(g2,
e, tm, extra_tri);
1035 if (dbg_level > 1) {
1036 std::cout <<
"g2 sorted: " << g2 <<
"\n";
1039 if (g3.
size() > 1) {
1040 Array<int> g3sorted = sort_tris_around_edge(tm,
e, g3, t0, extra_tri);
1042 if (dbg_level > 1) {
1043 std::cout <<
"g3 sorted: " << g3 <<
"\n";
1046 if (g4.
size() > 1) {
1047 Array<int> g4sorted = sort_tris_around_edge(tm,
e, g4, t0, extra_tri);
1049 if (dbg_level > 1) {
1050 std::cout <<
"g4 sorted: " << g4 <<
"\n";
1055 int *p = ans.begin();
1056 if (tris[0] == t0) {
1057 p = std::copy(g1.
begin(), g1.
end(), p);
1058 p = std::copy(g4.
begin(), g4.
end(), p);
1059 p = std::copy(g2.
begin(), g2.
end(), p);
1060 std::copy(g3.
begin(), g3.
end(), p);
1063 p = std::copy(g3.
begin(), g3.
end(), p);
1064 p = std::copy(g1.
begin(), g1.
end(), p);
1065 p = std::copy(g4.
begin(), g4.
end(), p);
1066 std::copy(g2.
begin(), g2.
end(), p);
1068 if (dbg_level > 0) {
1069 std::cout <<
"sorted tris = " << ans <<
"\n";
1080static void find_cells_from_edge(
const IMesh &tm,
1081 const TriMeshTopology &tmtopo,
1086 const int dbg_level = 0;
1087 if (dbg_level > 0) {
1088 std::cout <<
"FIND_CELLS_FROM_EDGE " <<
e <<
"\n";
1092 Array<int> sorted_tris = sort_tris_around_edge(
1093 tm,
e,
Span<int>(*edge_tris), (*edge_tris)[0],
nullptr);
1095 int n_edge_tris = edge_tris->
size();
1097 for (
int i = 0;
i < n_edge_tris; ++
i) {
1098 edge_patches[
i] = pinfo.tri_patch(sorted_tris[
i]);
1099 if (dbg_level > 1) {
1100 std::cout <<
"edge_patches[" <<
i <<
"] = " << edge_patches[
i] <<
"\n";
1103 for (
int i = 0;
i < n_edge_tris; ++
i) {
1104 int inext = (
i + 1) % n_edge_tris;
1105 int r_index = edge_patches[
i];
1106 int rnext_index = edge_patches[inext];
1107 Patch &r = pinfo.patch(r_index);
1108 Patch &rnext = pinfo.patch(rnext_index);
1111 find_flap_vert(*tm.face(sorted_tris[
i]),
e, &r_flipped);
1112 find_flap_vert(*tm.face(sorted_tris[inext]),
e, &rnext_flipped);
1113 int *r_follow_cell = r_flipped ? &r.cell_below : &r.cell_above;
1114 int *rnext_prev_cell = rnext_flipped ? &rnext.cell_above : &rnext.cell_below;
1115 if (dbg_level > 0) {
1116 std::cout <<
"process patch pair " << r_index <<
" " << rnext_index <<
"\n";
1117 std::cout <<
" r_flipped = " << r_flipped <<
" rnext_flipped = " << rnext_flipped <<
"\n";
1118 std::cout <<
" r_follow_cell (" << (r_flipped ?
"below" :
"above")
1119 <<
") = " << *r_follow_cell <<
"\n";
1120 std::cout <<
" rnext_prev_cell (" << (rnext_flipped ?
"above" :
"below")
1121 <<
") = " << *rnext_prev_cell <<
"\n";
1123 if (*r_follow_cell == NO_INDEX && *rnext_prev_cell == NO_INDEX) {
1125 int c = cinfo.add_cell();
1127 *rnext_prev_cell = c;
1128 Cell &cell = cinfo.cell(c);
1129 cell.add_patch(r_index);
1130 cell.add_patch(rnext_index);
1131 cell.check_for_zero_volume(pinfo, tm);
1132 if (dbg_level > 0) {
1133 std::cout <<
" made new cell " << c <<
"\n";
1134 std::cout <<
" p" << r_index <<
"." << (r_flipped ?
"cell_below" :
"cell_above") <<
" = c"
1136 std::cout <<
" p" << rnext_index <<
"." << (rnext_flipped ?
"cell_above" :
"cell_below")
1137 <<
" = c" << c <<
"\n";
1140 else if (*r_follow_cell != NO_INDEX && *rnext_prev_cell == NO_INDEX) {
1141 int c = *r_follow_cell;
1142 *rnext_prev_cell = c;
1143 Cell &cell = cinfo.cell(c);
1144 cell.add_patch(rnext_index);
1145 cell.check_for_zero_volume(pinfo, tm);
1146 if (dbg_level > 0) {
1147 std::cout <<
" reuse r_follow: p" << rnext_index <<
"."
1148 << (rnext_flipped ?
"cell_above" :
"cell_below") <<
" = c" << c <<
"\n";
1151 else if (*r_follow_cell == NO_INDEX && *rnext_prev_cell != NO_INDEX) {
1152 int c = *rnext_prev_cell;
1154 Cell &cell = cinfo.cell(c);
1155 cell.add_patch(r_index);
1156 cell.check_for_zero_volume(pinfo, tm);
1157 if (dbg_level > 0) {
1158 std::cout <<
" reuse rnext prev: rprev_p" << r_index <<
"."
1159 << (r_flipped ?
"cell_below" :
"cell_above") <<
" = c" << c <<
"\n";
1163 if (*r_follow_cell != *rnext_prev_cell) {
1164 int follow_cell_num_patches = cinfo.cell(*r_follow_cell).patches().size();
1165 int prev_cell_num_patches = cinfo.cell(*rnext_prev_cell).patches().size();
1166 if (follow_cell_num_patches >= prev_cell_num_patches) {
1167 if (dbg_level > 0) {
1168 std::cout <<
" merge cell " << *rnext_prev_cell <<
" into cell " << *r_follow_cell
1171 merge_cells(*r_follow_cell, *rnext_prev_cell, cinfo, pinfo);
1175 if (dbg_level > 0) {
1176 std::cout <<
" merge cell " << *r_follow_cell <<
" into cell " << *rnext_prev_cell
1179 merge_cells(*rnext_prev_cell, *r_follow_cell, cinfo, pinfo);
1189static CellsInfo find_cells(
const IMesh &tm,
const TriMeshTopology &tmtopo, PatchesInfo &pinfo)
1191 const int dbg_level = 0;
1192 if (dbg_level > 0) {
1193 std::cout <<
"\nFIND_CELLS\n";
1198 for (
const auto item : pinfo.patch_patch_edge_map().items()) {
1199 int p = item.key.first;
1200 int q = item.key.second;
1202 const Edge &
e = item.value;
1205 find_cells_from_edge(tm, tmtopo, pinfo, cinfo,
e);
1214 for (
int p : pinfo.index_range()) {
1215 Patch &patch = pinfo.patch(p);
1216 if (patch.cell_above == NO_INDEX) {
1217 int c = cinfo.add_cell();
1218 patch.cell_above = c;
1219 Cell &cell = cinfo.cell(c);
1222 if (patch.cell_below == NO_INDEX) {
1223 int c = cinfo.add_cell();
1224 patch.cell_below = c;
1225 Cell &cell = cinfo.cell(c);
1229 if (dbg_level > 0) {
1230 std::cout <<
"\nFIND_CELLS found " << cinfo.tot_cell() <<
" cells\nCells\n";
1231 for (
int i : cinfo.index_range()) {
1232 std::cout <<
i <<
": " << cinfo.cell(
i) <<
"\n";
1234 std::cout <<
"Patches\n";
1235 for (
int i : pinfo.index_range()) {
1236 std::cout <<
i <<
": " << pinfo.patch(
i) <<
"\n";
1238 if (dbg_level > 1) {
1239 write_obj_cell_patch(tm, cinfo, pinfo,
false,
"postfindcells");
1250static Vector<Vector<int>> find_patch_components(
const CellsInfo &cinfo, PatchesInfo &pinfo)
1252 constexpr int dbg_level = 0;
1253 if (dbg_level > 0) {
1254 std::cout <<
"FIND_PATCH_COMPONENTS\n";
1256 if (pinfo.tot_patch() == 0) {
1259 int current_component = 0;
1260 Array<bool> cell_processed(cinfo.tot_cell(),
false);
1263 for (
int pstart : pinfo.index_range()) {
1264 Patch &patch_pstart = pinfo.patch(pstart);
1265 if (patch_pstart.component != NO_INDEX) {
1269 ans[current_component].
append(pstart);
1271 patch_pstart.component = current_component;
1273 int p = stack.
pop();
1274 Patch &patch = pinfo.patch(p);
1275 BLI_assert(patch.component == current_component);
1276 for (
int c : {patch.cell_above, patch.cell_below}) {
1277 if (cell_processed[c]) {
1280 cell_processed[c] =
true;
1281 for (
int pn : cinfo.cell(c).patches()) {
1282 Patch &patch_neighbor = pinfo.patch(pn);
1283 if (patch_neighbor.component == NO_INDEX) {
1284 patch_neighbor.component = current_component;
1286 ans[current_component].
append(pn);
1291 ++current_component;
1293 if (dbg_level > 0) {
1294 std::cout <<
"found " << ans.
size() <<
" components\n";
1296 std::cout << comp <<
": " << ans[comp] <<
"\n";
1306static bool patch_cell_graph_ok(
const CellsInfo &cinfo,
const PatchesInfo &pinfo)
1308 for (
int c : cinfo.index_range()) {
1309 const Cell &cell = cinfo.cell(c);
1310 if (cell.merged_to() != NO_INDEX) {
1313 if (cell.patches().is_empty()) {
1314 std::cout <<
"Patch/Cell graph disconnected at Cell " << c <<
" with no patches\n";
1317 for (
int p : cell.patches()) {
1318 if (p >= pinfo.tot_patch()) {
1319 std::cout <<
"Patch/Cell graph has bad patch index at Cell " << c <<
"\n";
1324 for (
int p : pinfo.index_range()) {
1325 const Patch &patch = pinfo.patch(p);
1326 if (patch.cell_above == NO_INDEX || patch.cell_below == NO_INDEX) {
1327 std::cout <<
"Patch/Cell graph disconnected at Patch " << p
1328 <<
" with one or two missing cells\n";
1331 if (patch.cell_above >= cinfo.tot_cell() || patch.cell_below >= cinfo.tot_cell()) {
1332 std::cout <<
"Patch/Cell graph has bad cell index at Patch " << p <<
"\n";
1352static bool is_pwn(
const IMesh &tm,
const TriMeshTopology &tmtopo)
1354 constexpr int dbg_level = 0;
1355 std::atomic<bool> is_pwn =
true;
1358 for (
auto item : tmtopo.edge_tri_map_items()) {
1363 if (!is_pwn.load()) {
1368 for (
int j : range) {
1373 for (
int t : *tris[j].second) {
1374 const Face &face = *tm.face(t);
1376 for (
int i : face.index_range()) {
1377 if (face[
i] == edge.v0()) {
1378 if (face[(
i + 1) % 3] == edge.v1()) {
1388 if (tot_orient != 0) {
1389 if (dbg_level > 0) {
1390 std::cout <<
"edge causing non-pwn: " << edge <<
"\n";
1397 return is_pwn.load();
1407static int find_cell_for_point_near_edge(
const mpq3 &p,
1410 const TriMeshTopology &tmtopo,
1411 const PatchesInfo &pinfo,
1414 constexpr int dbg_level = 0;
1415 if (dbg_level > 0) {
1416 std::cout <<
"FIND_CELL_FOR_POINT_NEAR_EDGE, p=" << p <<
" e=" <<
e <<
"\n";
1419 const Vert *dummy_vert = arena->add_or_find_vert(p, NO_INDEX);
1420 const Face *dummy_tri = arena->add_face({
e.v0(),
e.v1(), dummy_vert},
1422 {NO_INDEX, NO_INDEX, NO_INDEX},
1423 {
false,
false,
false});
1427 edge_tris[edge_tris.
size() - 1] = EXTRA_TRI_INDEX;
1428 Array<int> sorted_tris = sort_tris_around_edge(tm,
e, edge_tris, edge_tris[0], dummy_tri);
1429 if (dbg_level > 0) {
1430 std::cout <<
"sorted tris = " << sorted_tris <<
"\n";
1432 int *p_sorted_dummy = std::find(sorted_tris.
begin(), sorted_tris.
end(), EXTRA_TRI_INDEX);
1434 int dummy_index = p_sorted_dummy - sorted_tris.
begin();
1435 int prev_tri = (dummy_index == 0) ? sorted_tris[sorted_tris.
size() - 1] :
1436 sorted_tris[dummy_index - 1];
1437 if (dbg_level > 0) {
1438 int next_tri = (dummy_index == sorted_tris.
size() - 1) ? sorted_tris[0] :
1439 sorted_tris[dummy_index + 1];
1440 std::cout <<
"prev tri to dummy = " << prev_tri <<
"; next tri to dummy = " << next_tri
1443 const Patch &prev_patch = pinfo.patch(pinfo.tri_patch(prev_tri));
1444 if (dbg_level > 0) {
1445 std::cout <<
"prev_patch = " << prev_patch <<
"\n";
1448 find_flap_vert(*tm.face(prev_tri),
e, &prev_flipped);
1449 int c = prev_flipped ? prev_patch.cell_below : prev_patch.cell_above;
1450 if (dbg_level > 0) {
1451 std::cout <<
"find_cell_for_point_near_edge returns " << c <<
"\n";
1470static int find_ambient_cell(
const IMesh &tm,
1472 const TriMeshTopology &tmtopo,
1473 const PatchesInfo &pinfo,
1477 if (dbg_level > 0) {
1478 std::cout <<
"FIND_AMBIENT_CELL\n";
1482 const Vert *v_extreme;
1483 auto max_x_vert = [](
const Vert *a,
const Vert *
b) {
1484 return (a->co_exact.x >
b->co_exact.x) ? a :
b;
1486 if (component_patches ==
nullptr) {
1487 v_extreme = threading::parallel_reduce(
1488 tm.face_index_range(),
1492 const Vert *ans = init;
1493 for (int i : range) {
1494 const Face *f = tm.face(i);
1495 for (const Vert *v : *f) {
1496 if (v->co_exact.x > ans->co_exact.x) {
1506 if (dbg_level > 0) {
1507 std::cout <<
"restrict to patches " << *component_patches <<
"\n";
1509 int p0 = (*component_patches)[0];
1510 v_extreme = threading::parallel_reduce(
1513 (*tm.face(pinfo.patch(p0).tri(0)))[0],
1515 const Vert *ans = init;
1516 for (int pi : range) {
1517 int p = (*component_patches)[pi];
1518 const Vert *tris_ans = threading::parallel_reduce(
1519 IndexRange(pinfo.patch(p).tot_tri()),
1522 [&](IndexRange tris_range, const Vert *t_init) {
1523 const Vert *v_ans = t_init;
1524 for (int i : tris_range) {
1525 int t = pinfo.patch(p).tri(i);
1526 const Face *f = tm.face(t);
1527 for (const Vert *v : *f) {
1528 if (v->co_exact.x > v_ans->co_exact.x) {
1536 if (tris_ans->co_exact.x > ans->co_exact.x) {
1544 if (dbg_level > 0) {
1545 std::cout <<
"v_extreme = " << v_extreme <<
"\n";
1550 const Span<Edge> edges = tmtopo.vert_edges(v_extreme);
1551 const mpq_class &extreme_x = v_extreme->co_exact.x;
1552 const mpq_class &extreme_y = v_extreme->co_exact.y;
1554 mpq_class max_abs_slope = -1;
1555 for (
Edge e : edges) {
1556 const Vert *v_other = (
e.v0() == v_extreme) ?
e.v1() :
e.v0();
1557 const mpq3 &co_other = v_other->co_exact;
1558 mpq_class delta_x = co_other.x - extreme_x;
1564 mpq_class abs_slope =
abs((co_other.y - extreme_y) / delta_x);
1565 if (abs_slope > max_abs_slope) {
1567 max_abs_slope = abs_slope;
1570 if (dbg_level > 0) {
1571 std::cout <<
"ehull = " << ehull <<
" slope = " << max_abs_slope <<
"\n";
1575 mpq3 p_in_ambient = v_extreme->co_exact;
1576 p_in_ambient.x += 1;
1577 int c_ambient = find_cell_for_point_near_edge(p_in_ambient, ehull, tm, tmtopo, pinfo, arena);
1578 if (dbg_level > 0) {
1579 std::cout <<
"FIND_AMBIENT_CELL returns " << c_ambient <<
"\n";
1593static Edge find_good_sorting_edge(
const Vert *testp,
1594 const Vert *closestp,
1595 const TriMeshTopology &tmtopo)
1597 constexpr int dbg_level = 0;
1598 if (dbg_level > 0) {
1599 std::cout <<
"FIND_GOOD_SORTING_EDGE testp = " << testp <<
", closestp = " << closestp <<
"\n";
1607 const mpq3 &co_closest = closestp->co_exact;
1608 const mpq3 &co_test = testp->co_exact;
1610 mpq3 abscissa = co_test - co_closest;
1613 for (axis = 0; axis < 3; ++axis) {
1614 if (abscissa[axis] != 0) {
1619 int axis_next = (axis + 1) % 3;
1620 int axis_next_next = (axis_next + 1) % 3;
1622 ordinate[axis] = abscissa[axis_next];
1623 ordinate[axis_next] = -abscissa[axis];
1624 ordinate[axis_next_next] = 0;
1627 if (dbg_level > 0) {
1628 std::cout <<
"abscissa = " << abscissa <<
"\n";
1629 std::cout <<
"ordinate = " << ordinate <<
"\n";
1630 std::cout <<
"normal = " << normal <<
"\n";
1633 mpq_class max_abs_slope = -1;
1635 const Span<Edge> edges = tmtopo.vert_edges(closestp);
1636 for (
Edge e : edges) {
1637 const Vert *v_other = (
e.v0() == closestp) ?
e.v1() :
e.v0();
1638 const mpq3 &co_other = v_other->co_exact;
1639 mpq3 evec = co_other - co_closest;
1641 mpq3 proj_evec = evec - (
math::dot(evec, normal) / nlen2) * normal;
1645 mpq_class evec_a =
math::dot(proj_evec, abscissa);
1646 mpq_class evec_o =
math::dot(proj_evec, ordinate);
1647 if (dbg_level > 0) {
1648 std::cout <<
"e = " <<
e <<
"\n";
1649 std::cout <<
"v_other = " << v_other <<
"\n";
1650 std::cout <<
"evec = " << evec <<
", proj_evec = " << proj_evec <<
"\n";
1651 std::cout <<
"evec_a = " << evec_a <<
", evec_o = " << evec_o <<
"\n";
1656 if (dbg_level > 0) {
1657 std::cout <<
"perpendicular esort is " << esort <<
"\n";
1661 mpq_class abs_slope =
abs(evec_o / evec_a);
1662 if (abs_slope > max_abs_slope) {
1664 max_abs_slope = abs_slope;
1665 if (dbg_level > 0) {
1666 std::cout <<
"with abs_slope " << abs_slope <<
" new esort is " << esort <<
"\n";
1685static int find_containing_cell(
const Vert *
v,
1689 const PatchesInfo &pinfo,
1691 const TriMeshTopology &tmtopo,
1694 constexpr int dbg_level = 0;
1695 if (dbg_level > 0) {
1696 std::cout <<
"FIND_CONTAINING_CELL v=" <<
v <<
", t=" << t <<
"\n";
1698 const Face &tri = *tm.face(t);
1700 if (close_edge == -1 && close_vert == -1) {
1704 if (close_edge != -1) {
1705 const Vert *v0 = tri[close_edge];
1706 const Vert *v1 = tri[(close_edge + 1) % 3];
1707 const Span<Edge> edges = tmtopo.vert_edges(v0);
1708 if (dbg_level > 0) {
1709 std::cout <<
"look for edge containing " << v0 <<
" and " << v1 <<
"\n";
1710 std::cout <<
" in edges: ";
1711 for (
Edge e : edges) {
1712 std::cout <<
e <<
" ";
1716 for (
Edge e : edges) {
1717 if ((
e.v0() == v0 &&
e.v1() == v1) || (
e.v0() == v1 &&
e.v1() == v0)) {
1724 int cv = close_vert;
1725 const Vert *vert_cv = tri[cv];
1728 vert_cv = tri[(cv + 1) % 3];
1731 etest = find_good_sorting_edge(
v, vert_cv, tmtopo);
1734 if (dbg_level > 0) {
1735 std::cout <<
"etest = " << etest <<
"\n";
1737 int c = find_cell_for_point_near_edge(
v->co_exact, etest, tm, tmtopo, pinfo, arena);
1738 if (dbg_level > 0) {
1739 std::cout <<
"find_containing_cell returns " << c <<
"\n";
1757static mpq_class closest_on_tri_to_point(
const mpq3 &p,
1771 constexpr int dbg_level = 0;
1772 if (dbg_level > 0) {
1773 std::cout <<
"CLOSEST_ON_TRI_TO_POINT p = " << p <<
"\n";
1774 std::cout <<
" a = " << a <<
", b = " <<
b <<
", c = " << c <<
"\n";
1784 mpq_class d1 = math::dot_with_buffer(ab, ap, m);
1785 mpq_class d2 = math::dot_with_buffer(ac, ap, m);
1786 if (d1 <= 0 && d2 <= 0) {
1790 if (dbg_level > 0) {
1791 std::cout <<
" answer = a\n";
1793 return math::distance_squared_with_buffer(p, a, m);
1798 mpq_class d3 = math::dot_with_buffer(ab, bp, m);
1799 mpq_class d4 = math::dot_with_buffer(ac, bp, m);
1800 if (d3 >= 0 && d4 <= d3) {
1804 if (dbg_level > 0) {
1805 std::cout <<
" answer = b\n";
1807 return math::distance_squared_with_buffer(p,
b, m);
1810 mpq_class vc = d1 * d4 - d3 * d2;
1811 if (vc <= 0 && d1 >= 0 && d3 <= 0) {
1812 mpq_class
v = d1 / (d1 - d3);
1819 if (dbg_level > 0) {
1820 std::cout <<
" answer = on ab at " << r <<
"\n";
1822 return math::distance_squared_with_buffer(p, r, m);
1827 mpq_class d5 = math::dot_with_buffer(ab, cp, m);
1828 mpq_class d6 = math::dot_with_buffer(ac, cp, m);
1829 if (d6 >= 0 && d5 <= d6) {
1833 if (dbg_level > 0) {
1834 std::cout <<
" answer = c\n";
1836 return math::distance_squared_with_buffer(p, c, m);
1839 mpq_class vb = d5 * d2 - d1 * d6;
1840 if (vb <= 0 && d2 >= 0 && d6 <= 0) {
1841 mpq_class
w = d2 / (d2 - d6);
1848 if (dbg_level > 0) {
1849 std::cout <<
" answer = on ac at " << r <<
"\n";
1851 return math::distance_squared_with_buffer(p, r, m);
1854 mpq_class va = d3 * d6 - d5 * d4;
1855 if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {
1856 mpq_class
w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
1864 if (dbg_level > 0) {
1865 std::cout <<
" answer = on bc at " << r <<
"\n";
1867 return math::distance_squared_with_buffer(p, r, m);
1870 mpq_class denom = 1 / (va + vb + vc);
1871 mpq_class
v = vb * denom;
1872 mpq_class
w = vc * denom;
1880 if (dbg_level > 0) {
1881 std::cout <<
" answer = inside at " << r <<
"\n";
1883 return math::distance_squared_with_buffer(p, r, m);
1886static float closest_on_tri_to_point_float_dist_squared(
const float3 &p,
1899struct ComponentContainer {
1900 int containing_component{NO_INDEX};
1901 int nearest_cell{NO_INDEX};
1902 mpq_class dist_to_cell;
1904 ComponentContainer(
int cc,
int cell, mpq_class d)
1905 : containing_component(cc), nearest_cell(cell), dist_to_cell(d)
1920 const PatchesInfo &pinfo,
1921 const TriMeshTopology &tmtopo,
1925 constexpr int dbg_level = 0;
1926 if (dbg_level > 0) {
1927 std::cout <<
"FIND_COMPONENT_CONTAINERS for comp " << comp <<
"\n";
1930 int test_p = components[comp][0];
1931 int test_t = pinfo.patch(test_p).tri(0);
1932 const Vert *test_v = tm.face(test_t)[0].vert[0];
1933 if (dbg_level > 0) {
1934 std::cout <<
"test vertex in comp: " << test_v <<
"\n";
1936 const double3 &test_v_d = test_v->co;
1937 float3 test_v_f(test_v_d[0], test_v_d[1], test_v_d[2]);
1941 for (
int comp_other : components.index_range()) {
1942 if (comp == comp_other) {
1945 if (dbg_level > 0) {
1946 std::cout <<
"comp_other = " << comp_other <<
"\n";
1948 if (!bbs_might_intersect(comp_bb[comp], comp_bb[comp_other])) {
1949 if (dbg_level > 0) {
1950 std::cout <<
"bounding boxes don't overlap\n";
1954 int nearest_tri = NO_INDEX;
1955 int nearest_tri_close_vert = -1;
1956 int nearest_tri_close_edge = -1;
1957 mpq_class nearest_tri_dist_squared;
1958 float nearest_tri_dist_squared_float =
FLT_MAX;
1959 for (
int p : components[comp_other]) {
1960 const Patch &patch = pinfo.patch(p);
1961 for (
int t : patch.tris()) {
1962 const Face &tri = *tm.face(t);
1963 if (dbg_level > 1) {
1964 std::cout <<
"tri " << t <<
" = " << &tri <<
"\n";
1969 float d2_f = closest_on_tri_to_point_float_dist_squared(
1970 test_v_f, tri[0]->co, tri[1]->co, tri[2]->co);
1971 if (d2_f - FLT_EPSILON > nearest_tri_dist_squared_float) {
1974 mpq_class d2 = closest_on_tri_to_point(test_v->co_exact,
1987 if (dbg_level > 1) {
1988 std::cout <<
" close_edge=" << close_edge <<
" close_vert=" << close_vert
1989 <<
" dsquared=" << d2.get_d() <<
"\n";
1991 if (nearest_tri == NO_INDEX || d2 < nearest_tri_dist_squared) {
1993 nearest_tri_close_edge = close_edge;
1994 nearest_tri_close_vert = close_vert;
1995 nearest_tri_dist_squared = d2;
1996 nearest_tri_dist_squared_float = d2_f;
2000 if (dbg_level > 0) {
2001 std::cout <<
"closest tri to comp=" << comp <<
" in comp_other=" << comp_other <<
" is t"
2002 << nearest_tri <<
"\n";
2003 const Face *tn = tm.face(nearest_tri);
2004 std::cout <<
"tri = " << tn <<
"\n";
2005 std::cout <<
" (" << tn->vert[0]->co <<
"," << tn->vert[1]->co <<
"," << tn->vert[2]->co
2008 int containing_cell = find_containing_cell(test_v,
2010 nearest_tri_close_edge,
2011 nearest_tri_close_vert,
2017 if (dbg_level > 0) {
2018 std::cout <<
"containing cell = " << containing_cell <<
"\n";
2020 if (containing_cell != ambient_cell[comp_other]) {
2021 ans.
append(ComponentContainer(comp_other, containing_cell, nearest_tri_dist_squared));
2033 const PatchesInfo &pinfo,
2037 const int comp_grainsize = 16;
2042 threading::parallel_for(components.index_range(), comp_grainsize, [&](
IndexRange comp_range) {
2043 for (int c : comp_range) {
2044 BoundingBox &bb = comp_bb[c];
2045 double &maxa = max_abs[c];
2046 for (int p : components[c]) {
2047 const Patch &patch = pinfo.patch(p);
2048 for (int t : patch.tris()) {
2049 const Face &tri = *im.face(t);
2050 for (const Vert *v : tri) {
2052 for (int i = 0; i < 3; ++i) {
2053 maxa = max_dd(maxa, fabs(v->co[i]));
2060 double all_max_abs = 0.0;
2061 for (
int c : components.index_range()) {
2062 all_max_abs =
max_dd(all_max_abs, max_abs[c]);
2064 constexpr float pad_factor = 10.0f;
2065 float pad = all_max_abs == 0.0 ? FLT_EPSILON : 2 * FLT_EPSILON * all_max_abs;
2067 for (
int c : components.index_range()) {
2068 comp_bb[c].expand(
pad);
2079static void finish_patch_cell_graph(
const IMesh &tm,
2082 const TriMeshTopology &tmtopo,
2085 constexpr int dbg_level = 0;
2086 if (dbg_level > 0) {
2087 std::cout <<
"FINISH_PATCH_CELL_GRAPH\n";
2090 if (components.
size() <= 1) {
2091 if (dbg_level > 0) {
2092 std::cout <<
"one component so finish_patch_cell_graph does no work\n";
2096 if (dbg_level > 0) {
2097 std::cout <<
"components:\n";
2099 std::cout << comp <<
": " << components[comp] <<
"\n";
2104 ambient_cell[comp] = find_ambient_cell(tm, &components[comp], tmtopo, pinfo, arena);
2106 if (dbg_level > 0) {
2107 std::cout <<
"ambient cells:\n";
2109 std::cout << comp <<
": " << ambient_cell[comp] <<
"\n";
2112 int tot_components = components.
size();
2114 if (tot_components > 1) {
2116 populate_comp_bbs(components, pinfo, tm, comp_bb);
2118 comp_cont[comp] = find_component_containers(
2119 comp, components, ambient_cell, tm, pinfo, tmtopo, comp_bb, arena);
2121 if (dbg_level > 0) {
2122 std::cout <<
"component containers:\n";
2123 for (
int comp : comp_cont.index_range()) {
2124 std::cout << comp <<
": ";
2125 for (
const ComponentContainer &cc : comp_cont[comp]) {
2126 std::cout <<
"[containing_comp=" << cc.containing_component
2127 <<
", nearest_cell=" << cc.nearest_cell <<
", d2=" << cc.dist_to_cell <<
"] ";
2133 if (dbg_level > 1) {
2134 write_obj_cell_patch(tm, cinfo, pinfo,
false,
"beforemerge");
2138 for (
int comp : comp_cont.index_range()) {
2139 if (comp_cont[comp].is_empty()) {
2140 outer_components.
append(comp);
2143 ComponentContainer &
closest = comp_cont[comp][0];
2144 for (
int i = 1;
i < comp_cont[comp].size(); ++
i) {
2145 if (comp_cont[comp][
i].dist_to_cell <
closest.dist_to_cell) {
2149 int comp_ambient = ambient_cell[comp];
2150 int cont_cell =
closest.nearest_cell;
2151 if (dbg_level > 0) {
2152 std::cout <<
"merge comp " << comp <<
"'s ambient cell=" << comp_ambient <<
" to cell "
2153 << cont_cell <<
"\n";
2155 merge_cells(cont_cell, comp_ambient, cinfo, pinfo);
2159 if (outer_components.
size() > 1) {
2160 int merged_ambient = ambient_cell[outer_components[0]];
2161 for (
int i = 1;
i < outer_components.size(); ++
i) {
2162 if (dbg_level > 0) {
2163 std::cout <<
"merge comp " << outer_components[
i]
2164 <<
"'s ambient cell=" << ambient_cell[outer_components[
i]] <<
" to cell "
2165 << merged_ambient <<
"\n";
2167 merge_cells(merged_ambient, ambient_cell[outer_components[
i]], cinfo, pinfo);
2170 if (dbg_level > 0) {
2171 std::cout <<
"after FINISH_PATCH_CELL_GRAPH\nCells\n";
2172 for (
int i : cinfo.index_range()) {
2173 if (cinfo.cell(
i).merged_to() == NO_INDEX) {
2174 std::cout <<
i <<
": " << cinfo.cell(
i) <<
"\n";
2177 std::cout <<
"Patches\n";
2178 for (
int i : pinfo.index_range()) {
2179 std::cout <<
i <<
": " << pinfo.patch(
i) <<
"\n";
2181 if (dbg_level > 1) {
2182 write_obj_cell_patch(tm, cinfo, pinfo,
false,
"finished");
2200static void propagate_windings_and_in_output_volume(PatchesInfo &pinfo,
2208 if (dbg_level > 0) {
2209 std::cout <<
"PROPAGATE_WINDINGS, ambient cell = " << c_ambient <<
"\n";
2211 Cell &cell_ambient = cinfo.cell(c_ambient);
2212 cell_ambient.seed_ambient_winding();
2215 queue.
reserve(cinfo.tot_cell());
2218 while (queue_head < queue.
size()) {
2219 int c = queue[queue_head++];
2220 if (dbg_level > 1) {
2221 std::cout <<
"process cell " << c <<
"\n";
2223 Cell &cell = cinfo.cell(c);
2224 for (
int p : cell.patches()) {
2225 Patch &patch = pinfo.patch(p);
2226 bool p_above_c = patch.cell_below == c;
2227 int c_neighbor = p_above_c ? patch.cell_above : patch.cell_below;
2228 if (dbg_level > 1) {
2229 std::cout <<
" patch " << p <<
" p_above_c = " << p_above_c <<
"\n";
2230 std::cout <<
" c_neighbor = " << c_neighbor <<
"\n";
2232 Cell &cell_neighbor = cinfo.cell(c_neighbor);
2233 if (!cell_neighbor.winding_assigned()) {
2234 int winding_delta = p_above_c ? -1 : 1;
2235 int t = patch.tri(0);
2236 int shape = shape_fn(t);
2239 if (dbg_level > 1) {
2240 std::cout <<
" representative tri " << t <<
": in shape " << shape <<
"\n";
2242 cell_neighbor.set_winding_and_in_output_volume(cell, shape, winding_delta, op);
2243 if (dbg_level > 1) {
2244 std::cout <<
" now cell_neighbor = " << cell_neighbor <<
"\n";
2246 queue.
append(c_neighbor);
2251 if (dbg_level > 0) {
2252 std::cout <<
"\nPROPAGATE_WINDINGS result\n";
2253 for (
int i = 0;
i < cinfo.tot_cell(); ++
i) {
2254 std::cout <<
i <<
": " << cinfo.cell(
i) <<
"\n";
2268static bool apply_bool_op(BoolOpType bool_optype,
const Array<int> &winding)
2270 int nw = winding.
size();
2272 switch (bool_optype) {
2273 case BoolOpType::Intersect: {
2274 for (
int i = 0;
i < nw; ++
i) {
2275 if (winding[
i] == 0) {
2281 case BoolOpType::Union: {
2282 for (
int i = 0;
i < nw; ++
i) {
2283 if (winding[
i] != 0) {
2289 case BoolOpType::Difference: {
2291 if (winding[0] == 0) {
2297 for (
int i = 1;
i < nw; ++
i) {
2298 if (winding[
i] >= 1) {
2315 const IMesh &tm_subdivided,
2316 const PatchesInfo &pinfo,
2317 const CellsInfo &cinfo,
2321 if (dbg_level > 0) {
2322 std::cout <<
"extract_zero_volume_cell_tris\n";
2326 for (
int p : pinfo.index_range()) {
2327 const Patch &patch = pinfo.patch(p);
2328 if (cinfo.cell(patch.cell_above).zero_volume() || cinfo.cell(patch.cell_below).zero_volume()) {
2329 adj_to_zv[p] =
true;
2332 adj_to_zv[p] =
false;
2337 Array<bool> allocated_to_stack(pinfo.tot_patch(),
false);
2338 for (
int p : pinfo.index_range()) {
2339 if (!adj_to_zv[p] || allocated_to_stack[p]) {
2342 int stack_index = patch_stacks.
size();
2346 allocated_to_stack[p] =
true;
2353 const Patch *pwalk_patch = &pinfo.patch(pwalk);
2354 int c = pwalk_patch->cell_above;
2355 const Cell *cell = &cinfo.cell(c);
2356 while (cell->zero_volume()) {
2359 int pother = cell->patch_other(pwalk);
2360 bool flip = pinfo.patch(pother).cell_above == c;
2363 allocated_to_stack[pother] =
true;
2365 pwalk_patch = &pinfo.patch(pwalk);
2366 c = flip ? pwalk_patch->cell_below : pwalk_patch->cell_above;
2367 cell = &cinfo.cell(c);
2369 const Cell *above_stack_cell = cell;
2372 pwalk_patch = &pinfo.patch(pwalk);
2373 c = pwalk_patch->cell_below;
2374 cell = &cinfo.cell(c);
2375 while (cell->zero_volume()) {
2377 int pother = cell->patch_other(pwalk);
2378 bool flip = pinfo.patch(pother).cell_below == c;
2381 allocated_to_stack[pother] =
true;
2383 pwalk_patch = &pinfo.patch(pwalk);
2384 c = flip ? pwalk_patch->cell_above : pwalk_patch->cell_below;
2385 cell = &cinfo.cell(c);
2387 const Cell *below_stack_cell = cell;
2388 if (dbg_level > 0) {
2389 std::cout <<
"process zero-volume patch stack " << stack <<
"\n";
2390 std::cout <<
"flipped = ";
2391 for (
bool b : flipped) {
2392 std::cout <<
b <<
" ";
2396 if (above_stack_cell->in_output_volume() ^ below_stack_cell->in_output_volume()) {
2397 bool need_flipped_tri = above_stack_cell->in_output_volume();
2398 if (dbg_level > 0) {
2399 std::cout <<
"need tri " << (need_flipped_tri ?
"flipped" :
"") <<
"\n";
2401 int t_to_add = NO_INDEX;
2402 for (
int i : stack.index_range()) {
2403 if (flipped[
i] == need_flipped_tri) {
2404 t_to_add = pinfo.patch(stack[
i]).tri(0);
2405 if (dbg_level > 0) {
2406 std::cout <<
"using tri " << t_to_add <<
"\n";
2408 r_tris.
append(tm_subdivided.face(t_to_add));
2412 if (t_to_add == NO_INDEX) {
2413 const Face *f = tm_subdivided.face(pinfo.patch(p).tri(0));
2414 const Face &tri = *f;
2416 std::array<const Vert *, 3> flipped_vs = {tri[0], tri[2], tri[1]};
2417 std::array<int, 3> flipped_e_origs = {
2418 tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
2419 std::array<bool, 3> flipped_is_intersect = {
2420 tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
2421 Face *flipped_f = arena->add_face(
2422 flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
2423 r_tris.
append(flipped_f);
2440static IMesh extract_from_in_output_volume_diffs(
const IMesh &tm_subdivided,
2441 const PatchesInfo &pinfo,
2442 const CellsInfo &cinfo,
2445 constexpr int dbg_level = 0;
2446 if (dbg_level > 0) {
2447 std::cout <<
"\nEXTRACT_FROM_FLAG_DIFFS\n";
2450 out_tris.
reserve(tm_subdivided.face_size());
2451 bool any_zero_volume_cell =
false;
2452 for (
int t : tm_subdivided.face_index_range()) {
2453 int p = pinfo.tri_patch(t);
2454 const Patch &patch = pinfo.patch(p);
2455 const Cell &cell_above = cinfo.cell(patch.cell_above);
2456 const Cell &cell_below = cinfo.cell(patch.cell_below);
2457 if (dbg_level > 0) {
2458 std::cout <<
"tri " << t <<
": cell_above=" << patch.cell_above
2459 <<
" cell_below=" << patch.cell_below <<
"\n";
2460 std::cout <<
" in_output_volume_above=" << cell_above.in_output_volume()
2461 <<
" in_output_volume_below=" << cell_below.in_output_volume() <<
"\n";
2463 bool adjacent_zero_volume_cell = cell_above.zero_volume() || cell_below.zero_volume();
2464 any_zero_volume_cell |= adjacent_zero_volume_cell;
2465 if (cell_above.in_output_volume() ^ cell_below.in_output_volume() &&
2466 !adjacent_zero_volume_cell)
2468 bool flip = cell_above.in_output_volume();
2469 if (dbg_level > 0) {
2470 std::cout <<
"need tri " << t <<
" flip=" << flip <<
"\n";
2472 Face *f = tm_subdivided.face(t);
2475 std::array<const Vert *, 3> flipped_vs = {tri[0], tri[2], tri[1]};
2476 std::array<int, 3> flipped_e_origs = {
2477 tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
2478 std::array<bool, 3> flipped_is_intersect = {
2479 tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
2480 Face *flipped_f = arena->add_face(
2481 flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
2482 out_tris.
append(flipped_f);
2489 if (any_zero_volume_cell) {
2490 extract_zero_volume_cell_tris(out_tris, tm_subdivided, pinfo, cinfo, arena);
2492 return IMesh(out_tris);
2495static const char *bool_optype_name(BoolOpType op)
2498 case BoolOpType::None:
2500 case BoolOpType::Intersect:
2502 case BoolOpType::Union:
2504 case BoolOpType::Difference:
2505 return "difference";
2511static double3 calc_point_inside_tri_db(
const Face &tri)
2513 const Vert *v0 = tri.vert[0];
2514 const Vert *v1 = tri.vert[1];
2515 const Vert *
v2 = tri.vert[2];
2516 double3 ans = v0->co / 3 + v1->co / 3 +
v2->co / 3;
2519class InsideShapeTestData {
2522 FunctionRef<int(
int)> shape_fn;
2525 Array<int> hit_parity;
2527 InsideShapeTestData(
const IMesh &tm, FunctionRef<
int(
int)> shape_fn,
int nshapes)
2528 : tm(tm), shape_fn(shape_fn), nshapes(nshapes)
2533static void inside_shape_callback(
void *userdata,
2538 const int dbg_level = 0;
2539 if (dbg_level > 0) {
2540 std::cout <<
"inside_shape_callback, index = " << index <<
"\n";
2542 InsideShapeTestData *
data =
static_cast<InsideShapeTestData *
>(userdata);
2543 const Face &tri = *
data->tm.face(index);
2544 int shape =
data->shape_fn(tri.orig);
2552 for (
int i = 0;
i < 3; ++
i) {
2553 fv0[
i] =
float(tri.vert[0]->co[
i]);
2554 fv1[
i] =
float(tri.vert[1]->co[
i]);
2555 fv2[
i] =
float(tri.vert[2]->co[
i]);
2557 if (dbg_level > 0) {
2558 std::cout <<
" fv0=(" << fv0[0] <<
"," << fv0[1] <<
"," << fv0[2] <<
")\n";
2559 std::cout <<
" fv1=(" << fv1[0] <<
"," << fv1[1] <<
"," << fv1[2] <<
")\n";
2560 std::cout <<
" fv2=(" << fv2[0] <<
"," << fv2[1] <<
"," << fv2[2] <<
")\n";
2563 ray->
origin, ray->
direction, fv0, fv1, fv2, &dist,
nullptr, FLT_EPSILON))
2568 int parity =
orient3d(tri.vert[0]->co, tri.vert[1]->co, tri.vert[2]->co, o_db);
2569 if (dbg_level > 0) {
2570 std::cout <<
"origin at " << o_db <<
", parity = " << parity <<
"\n";
2572 data->hit_parity[shape] += parity;
2585static void test_tri_inside_shapes(
const IMesh &tm,
2592 const int dbg_level = 0;
2593 if (dbg_level > 0) {
2594 std::cout <<
"test_point_inside_shapes, t_index = " << test_t_index <<
"\n";
2596 Face &tri_test = *tm.face(test_t_index);
2597 int shape = shape_fn(tri_test.orig);
2599 in_shape.
fill(0.0f);
2602 double3 test_point = calc_point_inside_tri_db(tri_test);
2604 tri_test.populate_plane(
false);
2606 const double offset_amount = 1
e-5;
2607 double3 offset_test_point = test_point + offset_amount *
norm;
2608 if (dbg_level > 0) {
2609 std::cout <<
"test tri is in shape " << shape <<
"\n";
2610 std::cout <<
"test point = " << test_point <<
"\n";
2611 std::cout <<
"offset_test_point = " << offset_test_point <<
"\n";
2617 constexpr int rays_num = 6;
2618 constexpr float r1 = 0.9987025295199663f;
2619 constexpr float ra = 0.04993512647599832f;
2620 constexpr float rb = 0.009987025295199663f;
2621 const float test_rays[rays_num][3] = {
2622 {r1, ra, rb}, {-r1, -ra, -rb}, {rb, r1, ra}, {-rb, -r1, -ra}, {ra, rb, r1}, {-ra, -rb, -r1}};
2623 InsideShapeTestData
data(tm, shape_fn, nshapes);
2626 const float co[3] = {
2627 float(offset_test_point[0]),
float(offset_test_point[1]),
float(offset_test_point[2])};
2628 for (
int i = 0;
i < rays_num; ++
i) {
2629 if (dbg_level > 0) {
2630 std::cout <<
"shoot ray " <<
i <<
"(" << test_rays[
i][0] <<
"," << test_rays[
i][1] <<
","
2631 << test_rays[
i][2] <<
")\n";
2634 if (dbg_level > 0) {
2635 std::cout <<
"ray " <<
i <<
" result:";
2636 for (
int j = 0; j < nshapes; ++j) {
2637 std::cout <<
" " <<
data.hit_parity[j];
2641 for (
int j = 0; j < nshapes; ++j) {
2642 if (j != shape &&
data.hit_parity[j] > 0) {
2646 data.hit_parity.fill(0);
2648 for (
int j = 0; j < nshapes; ++j) {
2653 in_shape[j] =
float(count_insides[j]) /
float(rays_num);
2655 if (dbg_level > 0) {
2656 std::cout <<
"shape " << j <<
" inside = " << in_shape[j] <<
"\n";
2667static BVHTree *raycast_tree(
const IMesh &tm)
2670 for (
int i : tm.face_index_range()) {
2671 const Face *f = tm.face(
i);
2673 for (
int j = 0; j < 3; ++j) {
2674 const Vert *
v = f->vert[j];
2675 for (
int k = 0; k < 3; ++k) {
2676 t_cos[3 * j + k] =
float(
v->co[k]);
2689static bool raycast_test_remove(BoolOpType op,
Array<int> &winding,
int shape,
bool *r_do_flip)
2691 constexpr int dbg_level = 0;
2697 bool in_output_volume_0 = apply_bool_op(op, winding);
2699 bool in_output_volume_1 = apply_bool_op(op, winding);
2700 bool do_remove = in_output_volume_0 == in_output_volume_1;
2701 bool do_flip = !do_remove && op == BoolOpType::Difference && shape != 0;
2702 if (dbg_level > 0) {
2703 std::cout <<
"winding = ";
2704 for (
int i = 0;
i < winding.
size(); ++
i) {
2705 std::cout << winding[
i] <<
" ";
2707 std::cout <<
"\niv0=" << in_output_volume_0 <<
", iv1=" << in_output_volume_1 <<
"\n";
2708 std::cout <<
" remove=" << do_remove <<
", flip=" << do_flip <<
"\n";
2710 *r_do_flip = do_flip;
2715static void raycast_add_flipped(
Vector<Face *> &out_faces,
const Face &tri, IMeshArena *arena)
2719 Array<int> flipped_e_origs = {tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
2721 tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
2722 Face *flipped_f = arena->add_face(flipped_vs, tri.orig, flipped_e_origs, flipped_is_intersect);
2723 out_faces.
append(flipped_f);
2734static IMesh raycast_tris_boolean(
2735 const IMesh &tm, BoolOpType op,
int nshapes,
FunctionRef<
int(
int)> shape_fn, IMeshArena *arena)
2737 constexpr int dbg_level = 0;
2738 if (dbg_level > 0) {
2739 std::cout <<
"RAYCAST_TRIS_BOOLEAN\n";
2744 out_faces.
reserve(tm.face_size());
2746 tbb::spin_mutex mtx;
2748 const int grainsize = 256;
2750 Array<float> in_shape(nshapes, 0);
2751 Array<int> winding(nshapes, 0);
2752 for (int t : range) {
2753 Face &tri = *tm.face(t);
2754 int shape = shape_fn(tri.orig);
2755 if (dbg_level > 0) {
2756 std::cout <<
"process triangle " << t <<
" = " << &tri <<
"\n";
2757 std::cout <<
"shape = " << shape <<
"\n";
2759 test_tri_inside_shapes(tm, shape_fn, nshapes, t, tree, in_shape);
2760 for (int other_shape = 0; other_shape < nshapes; ++other_shape) {
2761 if (other_shape == shape) {
2771 bool need_high_confidence = (op == BoolOpType::Difference && shape != 0) ||
2772 op == BoolOpType::Intersect;
2773 bool inside = in_shape[other_shape] >= (need_high_confidence ? 0.5f : 0.1f);
2774 if (dbg_level > 0) {
2775 std::cout <<
"test point is " << (inside ?
"inside" :
"outside") <<
" other_shape "
2776 << other_shape <<
" val = " << in_shape[other_shape] <<
"\n";
2778 winding[other_shape] = inside;
2781 bool do_remove = raycast_test_remove(op, winding, shape, &do_flip);
2784 tbb::spin_mutex::scoped_lock lock(mtx);
2788 out_faces.append(&tri);
2791 raycast_add_flipped(out_faces, tri, arena);
2798 ans.set_faces(out_faces);
2806static IMesh raycast_patches_boolean(
const IMesh &tm,
2810 const PatchesInfo &pinfo,
2813 constexpr int dbg_level = 0;
2814 if (dbg_level > 0) {
2815 std::cout <<
"RAYCAST_PATCHES_BOOLEAN\n";
2820 out_faces.
reserve(tm.face_size());
2823 for (
int p : pinfo.index_range()) {
2824 const Patch &patch = pinfo.patch(p);
2827 int test_t_index = patch.tri(patch.tot_tri() / 2);
2828 Face &tri_test = *tm.face(test_t_index);
2830 int shape = shape_fn(tri_test.orig);
2831 if (dbg_level > 0) {
2832 std::cout <<
"process patch " << p <<
" = " << patch <<
"\n";
2833 std::cout <<
"test tri = " << test_t_index <<
" = " << &tri_test <<
"\n";
2834 std::cout <<
"shape = " << shape <<
"\n";
2839 test_tri_inside_shapes(tm, shape_fn, nshapes, test_t_index,
tree, in_shape);
2840 for (
int other_shape = 0; other_shape < nshapes; ++other_shape) {
2841 if (other_shape == shape) {
2844 bool need_high_confidence = (op == BoolOpType::Difference && shape != 0) ||
2845 op == BoolOpType::Intersect;
2846 bool inside = in_shape[other_shape] >= (need_high_confidence ? 0.5f : 0.1f);
2847 if (dbg_level > 0) {
2848 std::cout <<
"test point is " << (inside ?
"inside" :
"outside") <<
" other_shape "
2849 << other_shape <<
" val = " << in_shape[other_shape] <<
"\n";
2851 winding[other_shape] = inside;
2854 bool do_remove = raycast_test_remove(op, winding, shape, &do_flip);
2856 for (
int t : patch.tris()) {
2857 Face *f = tm.face(t);
2862 raycast_add_flipped(out_faces, *f, arena);
2869 ans.set_faces(out_faces);
2877static std::pair<int, int> find_tris_common_edge(
const Face &tri1,
const Face &tri2)
2879 for (
int i = 0;
i < 3; ++
i) {
2880 for (
int j = 0; j < 3; ++j) {
2881 if (tri1[(
i + 1) % 3] == tri2[j] && tri1[
i] == tri2[(j + 1) % 3]) {
2882 return std::pair<int, int>(
i, j);
2886 return std::pair<int, int>(-1, -1);
2893 const Vert *v1 =
nullptr;
2894 const Vert *
v2 =
nullptr;
2897 int right_face = -1;
2900 bool dissolvable =
false;
2902 bool is_intersect =
false;
2904 MergeEdge() =
default;
2906 MergeEdge(
const Vert *va,
const Vert *vb)
2908 if (va->id < vb->id) {
2921 Vector<const Vert *> vert;
2929struct FaceMergeState {
2933 Vector<MergeFace> face;
2938 Vector<MergeEdge> edge;
2943 Map<std::pair<int, int>,
int> edge_map;
2946static std::ostream &
operator<<(std::ostream &os,
const FaceMergeState &fms)
2949 for (
int f : fms.face.index_range()) {
2950 const MergeFace &mf = fms.face[f];
2951 std::cout << f <<
": orig=" << mf.orig <<
" verts ";
2952 for (
const Vert *
v : mf.vert) {
2953 std::cout <<
v <<
" ";
2956 std::cout <<
" edges " << mf.edge <<
"\n";
2957 std::cout <<
" merge_to = " << mf.merge_to <<
"\n";
2960 for (
int e : fms.edge.index_range()) {
2961 const MergeEdge &me = fms.edge[
e];
2962 std::cout <<
e <<
": (" << me.v1 <<
"," << me.v2 <<
") left=" << me.left_face
2963 <<
" right=" << me.right_face <<
" dis=" << me.dissolvable <<
" orig=" << me.orig
2964 <<
" is_int=" << me.is_intersect <<
"\n";
2979static void init_face_merge_state(FaceMergeState *fms,
2982 const double3 &
norm)
2984 constexpr int dbg_level = 0;
2986 fms->face.reserve(tris.
size() + 1);
2987 fms->edge.reserve(3 * tris.
size());
2988 fms->edge_map.reserve(3 * tris.
size());
2989 if (dbg_level > 0) {
2990 std::cout <<
"\nINIT_FACE_MERGE_STATE\n";
2994 const Face &tri = *tm.face(tris[t]);
2995 if (dbg_level > 0) {
2996 std::cout <<
"process tri = " << &tri <<
"\n";
3000 if (dbg_level > 0) {
3001 std::cout <<
"triangle has wrong orientation, skipping\n";
3005 mf.vert.append(tri[0]);
3006 mf.vert.append(tri[1]);
3007 mf.vert.append(tri[2]);
3009 int f = fms->face.append_and_get_index(mf);
3010 if (dbg_level > 1) {
3011 std::cout <<
"appended MergeFace for tri at f = " << f <<
"\n";
3013 for (
int i = 0;
i < 3; ++
i) {
3014 int inext = (
i + 1) % 3;
3015 MergeEdge new_me(mf.vert[
i], mf.vert[inext]);
3016 std::pair<int, int> canon_vs(new_me.v1->id, new_me.v2->id);
3017 int me_index = fms->edge_map.lookup_default(canon_vs, -1);
3018 if (dbg_level > 1) {
3019 std::cout <<
"new_me = canon_vs = " << new_me.v1 <<
", " << new_me.v2 <<
"\n";
3020 std::cout <<
"me_index lookup = " << me_index <<
"\n";
3022 if (me_index == -1) {
3023 double3 vec = new_me.v2->co - new_me.v1->co;
3025 new_me.orig = tri.edge_orig[
i];
3026 new_me.is_intersect = tri.is_intersect[
i];
3027 new_me.dissolvable = (new_me.orig == NO_INDEX && !new_me.is_intersect);
3028 fms->edge.append(new_me);
3029 me_index = fms->edge.size() - 1;
3030 fms->edge_map.add_new(canon_vs, me_index);
3031 if (dbg_level > 1) {
3032 std::cout <<
"added new me with me_index = " << me_index <<
"\n";
3033 std::cout <<
" len_squared = " << new_me.len_squared <<
" orig = " << new_me.orig
3034 <<
", is_intersect" << new_me.is_intersect
3035 <<
", dissolvable = " << new_me.dissolvable <<
"\n";
3038 MergeEdge &me = fms->edge[me_index];
3039 if (dbg_level > 1) {
3040 std::cout <<
"retrieved me at index " << me_index <<
":\n";
3041 std::cout <<
" v1 = " << me.v1 <<
" v2 = " << me.v2 <<
"\n";
3042 std::cout <<
" dis = " << me.dissolvable <<
" int = " << me.is_intersect <<
"\n";
3043 std::cout <<
" left_face = " << me.left_face <<
" right_face = " << me.right_face <<
"\n";
3045 if (me.dissolvable && tri.edge_orig[
i] != NO_INDEX) {
3046 if (dbg_level > 1) {
3047 std::cout <<
"reassigning orig to " << tri.edge_orig[
i] <<
", dissolvable = false\n";
3049 me.dissolvable =
false;
3050 me.orig = tri.edge_orig[
i];
3052 if (me.dissolvable && tri.is_intersect[
i]) {
3053 if (dbg_level > 1) {
3054 std::cout <<
"reassigning dissolvable = false, is_intersect = true\n";
3056 me.dissolvable =
false;
3057 me.is_intersect =
true;
3060 if (me.v1 == mf.vert[
i]) {
3061 if (dbg_level > 1) {
3062 std::cout <<
"me.v1 == mf.vert[i] so set edge[" << me_index <<
"].left_face = " << f
3065 if (me.left_face != -1) {
3070 if (dbg_level > 1) {
3071 std::cout <<
"me.left_face was already occupied, so triangulation wasn't good\n";
3073 me.dissolvable =
false;
3076 fms->edge[me_index].left_face = f;
3080 if (dbg_level > 1) {
3081 std::cout <<
"me.v1 != mf.vert[i] so set edge[" << me_index <<
"].right_face = " << f
3084 if (me.right_face != -1) {
3086 if (dbg_level > 1) {
3087 std::cout <<
"me.right_face was already occupied, so triangulation wasn't good\n";
3089 me.dissolvable =
false;
3092 fms->edge[me_index].right_face = f;
3095 fms->face[f].edge.append(me_index);
3098 if (dbg_level > 0) {
3109static bool dissolve_leaves_valid_bmesh(FaceMergeState *fms,
3110 const MergeEdge &me,
3112 const MergeFace &mf_left,
3113 const MergeFace &mf_right)
3115 int a_edge_start = mf_left.edge.first_index_of_try(me_index);
3117 int alen = mf_left.vert.size();
3118 int blen = mf_right.vert.size();
3119 int b_left_face = me.right_face;
3122 for (
int a_e_index = (a_edge_start + 1) % alen; ok && a_e_index != a_edge_start;
3123 a_e_index = (a_e_index + 1) % alen)
3125 const MergeEdge &a_me_cur = fms->edge[mf_left.edge[a_e_index]];
3126 if (a_me_cur.right_face == b_left_face) {
3133 for (
int a_v_index = 0; ok && a_v_index < alen; ++a_v_index) {
3134 const Vert *a_v = mf_left.vert[a_v_index];
3135 if (!
ELEM(a_v, me.v1, me.v2)) {
3136 for (
int b_v_index = 0; b_v_index < blen; ++b_v_index) {
3137 const Vert *b_v = mf_right.vert[b_v_index];
3154static void splice_faces(
3155 FaceMergeState *fms, MergeEdge &me,
int me_index, MergeFace &mf_left, MergeFace &mf_right)
3157 int a_edge_start = mf_left.edge.first_index_of_try(me_index);
3158 int b_edge_start = mf_right.edge.first_index_of_try(me_index);
3159 BLI_assert(a_edge_start != -1 && b_edge_start != -1);
3160 int alen = mf_left.vert.size();
3161 int blen = mf_right.vert.size();
3164 splice_vert.
reserve(alen + blen - 2);
3165 splice_edge.
reserve(alen + blen - 2);
3167 while (ai < a_edge_start) {
3168 splice_vert.
append(mf_left.vert[ai]);
3169 splice_edge.
append(mf_left.edge[ai]);
3172 int bi = b_edge_start + 1;
3173 while (bi != b_edge_start) {
3176 if (bi == b_edge_start) {
3180 splice_vert.
append(mf_right.vert[bi]);
3181 splice_edge.
append(mf_right.edge[bi]);
3182 if (mf_right.vert[bi] == fms->edge[mf_right.edge[bi]].v1) {
3183 fms->edge[mf_right.edge[bi]].left_face = me.left_face;
3186 fms->edge[mf_right.edge[bi]].right_face = me.left_face;
3190 ai = a_edge_start + 1;
3192 splice_vert.
append(mf_left.vert[ai]);
3193 splice_edge.
append(mf_left.edge[ai]);
3196 mf_right.merge_to = me.left_face;
3197 mf_left.vert = splice_vert;
3198 mf_left.edge = splice_edge;
3210static void do_dissolve(FaceMergeState *fms)
3212 const int dbg_level = 0;
3213 if (dbg_level > 1) {
3214 std::cout <<
"\nDO_DISSOLVE\n";
3217 for (
int e : fms->edge.index_range()) {
3218 if (fms->edge[
e].dissolvable) {
3227 dissolve_edges.
begin(), dissolve_edges.
end(), [fms](
const int &a,
const int &
b) ->
bool {
3228 return (fms->edge[a].len_squared > fms->edge[b].len_squared);
3230 if (dbg_level > 0) {
3231 std::cout <<
"Sorted dissolvable edges: " << dissolve_edges <<
"\n";
3233 for (
int me_index : dissolve_edges) {
3234 MergeEdge &me = fms->edge[me_index];
3235 if (me.left_face == -1 || me.right_face == -1) {
3238 MergeFace &mf_left = fms->face[me.left_face];
3239 MergeFace &mf_right = fms->face[me.right_face];
3240 if (!dissolve_leaves_valid_bmesh(fms, me, me_index, mf_left, mf_right)) {
3243 if (dbg_level > 0) {
3244 std::cout <<
"Removing edge " << me_index <<
"\n";
3246 splice_faces(fms, me, me_index, mf_left, mf_right);
3247 if (dbg_level > 1) {
3248 std::cout <<
"state after removal:\n";
3266 const IMesh &imesh_in,
3269 constexpr int dbg_level = 0;
3270 if (dbg_level > 0) {
3271 std::cout <<
"merge_tris_for_face\n";
3272 std::cout <<
"tris: " << tris <<
"\n";
3275 if (tris.
size() <= 1) {
3276 if (tris.
size() == 1) {
3277 ans.
append(tm.face(tris[0]));
3282 double3 first_tri_normal = tm.face(tris[0])->plane->norm;
3283 double3 second_tri_normal = tm.face(tris[1])->plane->norm;
3284 if (tris.
size() == 2 &&
math::dot(first_tri_normal, second_tri_normal) > 0.0) {
3287 Face &tri1 = *tm.face(tris[0]);
3288 Face &tri2 = *tm.face(tris[1]);
3289 Face *in_face = imesh_in.face(tri1.orig);
3290 if (in_face->size() == 4) {
3291 std::pair<int, int> estarts = find_tris_common_edge(tri1, tri2);
3292 if (estarts.first != -1 && tri1.edge_orig[estarts.first] == NO_INDEX) {
3293 if (dbg_level > 0) {
3294 std::cout <<
"try recovering orig quad case\n";
3295 std::cout <<
"tri1 = " << &tri1 <<
"\n";
3296 std::cout <<
"tri1 = " << &tri2 <<
"\n";
3298 int i0 = estarts.first;
3299 int i1 = (i0 + 1) % 3;
3300 int i2 = (i0 + 2) % 3;
3301 int j2 = (estarts.second + 2) % 3;
3302 Face tryface({tri1[i1], tri1[i2], tri1[i0], tri2[j2]}, -1, -1, {}, {});
3303 if (tryface.cyclic_equal(*in_face)) {
3304 if (dbg_level > 0) {
3305 std::cout <<
"inface = " << in_face <<
"\n";
3306 std::cout <<
"quad recovery worked\n";
3318 double3 first_tri_normal_rev = -first_tri_normal;
3319 for (
const double3 &
norm : {first_tri_normal, first_tri_normal_rev}) {
3321 init_face_merge_state(&fms, tris, tm,
norm);
3323 if (dbg_level > 0) {
3324 std::cout <<
"faces in merged result:\n";
3326 for (
const MergeFace &mf : fms.face) {
3327 if (mf.merge_to == -1) {
3330 for (
int i : mf.edge.index_range()) {
3331 e_orig[
i] = fms.edge[mf.edge[
i]].orig;
3332 is_intersect[
i] = fms.edge[mf.edge[
i]].is_intersect;
3334 Face *facep = arena->add_face(mf.vert, mf.orig, e_orig, is_intersect);
3336 if (dbg_level > 0) {
3337 std::cout <<
" " << facep <<
"\n";
3345static bool approx_in_line(
const double3 &a,
const double3 &
b,
const double3 &c)
3350 return fabs(cos_ang - 1.0) < 1
e-4;
3359static Array<bool> find_dissolve_verts(IMesh &imesh_out,
int *r_count_dissolve)
3361 imesh_out.populate_vert();
3364 for (
int v_index : imesh_out.vert_index_range()) {
3365 const Vert &vert = *imesh_out.vert(v_index);
3366 dissolve[v_index] = (vert.orig == NO_INDEX);
3372 imesh_out.vert_size(), std::pair<const Vert *, const Vert *>(
nullptr,
nullptr));
3373 for (
int f : imesh_out.face_index_range()) {
3374 const Face &face = *imesh_out.face(f);
3375 for (
int i : face.index_range()) {
3376 const Vert *
v = face[
i];
3377 int v_index = imesh_out.lookup_vert(
v);
3379 if (dissolve[v_index]) {
3380 const Vert *n1 = face[face.next_pos(
i)];
3381 const Vert *n2 = face[face.prev_pos(
i)];
3382 const Vert *f_n1 = neighbors[v_index].first;
3383 const Vert *f_n2 = neighbors[v_index].second;
3384 if (f_n1 !=
nullptr) {
3386 if (!((n1 == f_n2 && n2 == f_n1) || (n1 == f_n1 && n2 == f_n2))) {
3388 dissolve[v_index] =
false;
3393 neighbors[v_index] = std::pair<const Vert *, const Vert *>(n1, n2);
3399 for (
int v_out : imesh_out.vert_index_range()) {
3400 if (dissolve[v_out]) {
3401 dissolve[v_out] =
false;
3402 const std::pair<const Vert *, const Vert *> &nbrs = neighbors[v_out];
3403 if (nbrs.first !=
nullptr) {
3405 const Vert *v_v_out = imesh_out.vert(v_out);
3406 if (approx_in_line(nbrs.first->co, v_v_out->co, nbrs.second->co)) {
3407 dissolve[v_out] =
true;
3413 if (r_count_dissolve !=
nullptr) {
3414 *r_count_dissolve =
count;
3424static void dissolve_verts(IMesh *imesh,
const Array<bool> dissolve, IMeshArena *arena)
3426 constexpr int inline_face_size = 100;
3428 bool any_faces_erased =
false;
3429 for (
int f : imesh->face_index_range()) {
3430 const Face &face = *imesh->face(f);
3431 face_pos_erase.
clear();
3433 for (
const Vert *
v : face) {
3434 int v_index = imesh->lookup_vert(
v);
3436 if (dissolve[v_index]) {
3437 face_pos_erase.
append(
true);
3441 face_pos_erase.
append(
false);
3444 if (erase_num > 0) {
3445 any_faces_erased |= imesh->erase_face_positions(f, face_pos_erase, arena);
3448 imesh->set_dirty_verts();
3449 if (any_faces_erased) {
3450 imesh->remove_null_faces();
3465static IMesh polymesh_from_trimesh_with_dissolve(
const IMesh &tm_out,
3466 const IMesh &imesh_in,
3469 const int dbg_level = 0;
3470 if (dbg_level > 1) {
3471 std::cout <<
"\nPOLYMESH_FROM_TRIMESH_WITH_DISSOLVE\n";
3474 const int grainsize = 1024;
3475 threading::parallel_for(tm_out.face_index_range(), grainsize, [&](
IndexRange range) {
3476 for (int i : range) {
3477 Face *tri = tm_out.face(i);
3478 tri->populate_plane(false);
3484 int tot_in_face = imesh_in.face_size();
3486 for (
int t : tm_out.face_index_range()) {
3487 const Face &tri = *tm_out.face(t);
3488 int in_face = tri.orig;
3489 face_output_tris[in_face].append(t);
3491 if (dbg_level > 1) {
3492 std::cout <<
"face_output_tris:\n";
3493 for (
int f : face_output_tris.index_range()) {
3494 std::cout << f <<
": " << face_output_tris[f] <<
"\n";
3502 int tot_out_face = 0;
3503 for (
int in_f : imesh_in.face_index_range()) {
3504 if (dbg_level > 1) {
3505 std::cout <<
"merge tris for face " << in_f <<
"\n";
3507 int out_tris_for_face_num = face_output_tris.size();
3508 if (out_tris_for_face_num == 0) {
3511 face_output_face[in_f] = merge_tris_for_face(face_output_tris[in_f], tm_out, imesh_in, arena);
3512 tot_out_face += face_output_face[in_f].size();
3515 int out_f_index = 0;
3516 for (
int in_f : imesh_in.face_index_range()) {
3518 if (f_faces.
size() > 0) {
3519 std::copy(f_faces.
begin(), f_faces.
end(), &face[out_f_index]);
3520 out_f_index += f_faces.
size();
3523 IMesh imesh_out(face);
3528 Array<bool> v_dissolve = find_dissolve_verts(imesh_out, &count_dissolve);
3529 if (count_dissolve > 0) {
3530 dissolve_verts(&imesh_out, v_dissolve, arena);
3532 if (dbg_level > 1) {
3533 write_obj_mesh(imesh_out,
"boolean_post_dissolve");
3539IMesh boolean_trimesh(IMesh &tm_in,
3547 constexpr int dbg_level = 0;
3548 if (dbg_level > 0) {
3549 std::cout <<
"BOOLEAN of " << nshapes <<
" operand" << (nshapes == 1 ?
"" :
"s")
3550 <<
" op=" << bool_optype_name(op) <<
"\n";
3551 if (dbg_level > 1) {
3552 tm_in.populate_vert();
3553 std::cout <<
"boolean_trimesh input:\n" << tm_in;
3554 write_obj_mesh(tm_in,
"boolean_in");
3557 if (tm_in.face_size() == 0) {
3558 return IMesh(tm_in);
3562 std::cout <<
" boolean_trimesh, timing begins\n";
3565 IMesh tm_si = trimesh_nary_intersect(tm_in, nshapes, shape_fn, use_self, arena);
3566 if (dbg_level > 1) {
3567 write_obj_mesh(tm_si,
"boolean_tm_si");
3568 std::cout <<
"\nboolean_tm_input after intersection:\n" << tm_si;
3572 std::cout <<
" intersected, time = " << intersect_time - start_time <<
"\n";
3576 if (tm_si.face_size() == 0 || op == BoolOpType::None) {
3579 auto si_shape_fn = [shape_fn, tm_si](
int t) {
return shape_fn(tm_si.face(t)->orig); };
3580 TriMeshTopology tm_si_topo(tm_si);
3583 std::cout <<
" topology built, time = " << topo_time - intersect_time <<
"\n";
3585 bool pwn = is_pwn(tm_si, tm_si_topo);
3588 std::cout <<
" pwn checked, time = " << pwn_time - topo_time <<
"\n";
3592 if (dbg_level > 0) {
3593 std::cout <<
"Input is not PWN, using raycast method\n";
3595 if (hole_tolerant) {
3596 tm_out = raycast_tris_boolean(tm_si, op, nshapes, shape_fn, arena);
3599 PatchesInfo pinfo = find_patches(tm_si, tm_si_topo);
3600 tm_out = raycast_patches_boolean(tm_si, op, nshapes, shape_fn, pinfo, arena);
3604 std::cout <<
" raycast_boolean done, time = " << raycast_time - pwn_time <<
"\n";
3608 PatchesInfo pinfo = find_patches(tm_si, tm_si_topo);
3611 std::cout <<
" patches found, time = " << patch_time - pwn_time <<
"\n";
3613 CellsInfo cinfo = find_cells(tm_si, tm_si_topo, pinfo);
3614 if (dbg_level > 0) {
3615 std::cout <<
"Input is PWN\n";
3619 std::cout <<
" cells found, time = " << cell_time - pwn_time <<
"\n";
3621 finish_patch_cell_graph(tm_si, cinfo, pinfo, tm_si_topo, arena);
3624 std::cout <<
" finished patch-cell graph, time = " << finish_pc_time - cell_time <<
"\n";
3626 bool pc_ok = patch_cell_graph_ok(cinfo, pinfo);
3629 std::cout <<
"Something funny about input or a bug in boolean\n";
3630 return IMesh(tm_in);
3632 cinfo.init_windings(nshapes);
3633 int c_ambient = find_ambient_cell(tm_si,
nullptr, tm_si_topo, pinfo, arena);
3636 std::cout <<
" ambient cell found, time = " << amb_time - finish_pc_time <<
"\n";
3638 if (c_ambient == NO_INDEX) {
3640 std::cout <<
"Could not find an ambient cell; input not valid?\n";
3641 return IMesh(tm_si);
3643 propagate_windings_and_in_output_volume(pinfo, cinfo, c_ambient, op, nshapes, si_shape_fn);
3646 std::cout <<
" windings propagated, time = " << propagate_time - amb_time <<
"\n";
3648 tm_out = extract_from_in_output_volume_diffs(tm_si, pinfo, cinfo, arena);
3651 std::cout <<
" extracted, time = " << extract_time - propagate_time <<
"\n";
3653 if (dbg_level > 0) {
3655 TriMeshTopology tm_out_topo(tm_out);
3656 if (!is_pwn(tm_out, tm_out_topo)) {
3657 std::cout <<
"OUTPUT IS NOT PWN!\n";
3661 if (dbg_level > 1) {
3662 write_obj_mesh(tm_out,
"boolean_tm_output");
3663 std::cout <<
"boolean tm output:\n" << tm_out;
3667 std::cout <<
" boolean_trimesh done, total time = " << end_time - start_time <<
"\n";
3672static void dump_test_spec(IMesh &imesh)
3674 std::cout <<
"test spec = " << imesh.vert_size() <<
" " << imesh.face_size() <<
"\n";
3675 for (
const Vert *
v : imesh.vertices()) {
3676 std::cout <<
v->co_exact[0] <<
" " <<
v->co_exact[1] <<
" " <<
v->co_exact[2] <<
" # "
3677 <<
v->co[0] <<
" " <<
v->co[1] <<
" " <<
v->co[2] <<
"\n";
3679 for (
const Face *f : imesh.faces()) {
3680 for (
const Vert *fv : *f) {
3681 std::cout << imesh.lookup_vert(fv) <<
" ";
3687IMesh boolean_mesh(IMesh &imesh,
3693 IMesh *imesh_triangulated,
3696 constexpr int dbg_level = 0;
3697 if (dbg_level > 0) {
3698 std::cout <<
"\nBOOLEAN_MESH\n"
3699 << nshapes <<
" operand" << (nshapes == 1 ?
"" :
"s")
3700 <<
" op=" << bool_optype_name(op) <<
"\n";
3701 if (dbg_level > 1) {
3702 write_obj_mesh(imesh,
"boolean_mesh_in");
3704 if (dbg_level > 2) {
3705 dump_test_spec(imesh);
3709 IMesh *tm_in = imesh_triangulated;
3710 IMesh our_triangulation;
3713 std::cout <<
"boolean_mesh, timing begins\n";
3715 if (tm_in ==
nullptr) {
3716 our_triangulation = triangulate_polymesh(imesh, arena);
3717 tm_in = &our_triangulation;
3721 std::cout <<
"triangulated, time = " << tri_time - start_time <<
"\n";
3723 if (dbg_level > 1) {
3724 write_obj_mesh(*tm_in,
"boolean_tm_in");
3726 IMesh tm_out = boolean_trimesh(*tm_in, op, nshapes, shape_fn, use_self, hole_tolerant, arena);
3729 std::cout <<
"boolean_trimesh done, time = " << bool_tri_time - tri_time <<
"\n";
3731 if (dbg_level > 1) {
3732 std::cout <<
"bool_trimesh_output:\n" << tm_out;
3733 write_obj_mesh(tm_out,
"bool_trimesh_output");
3735 IMesh ans = polymesh_from_trimesh_with_dissolve(tm_out, imesh, arena);
3738 std::cout <<
"polymesh from dissolving, time = " << dissolve_time - bool_tri_time <<
"\n";
3740 if (dbg_level > 0) {
3741 std::cout <<
"boolean_mesh output:\n" << ans;
3742 if (dbg_level > 2) {
3743 ans.populate_vert();
3744 dump_test_spec(ans);
3749 std::cout <<
"boolean_mesh done, total time = " << end_time - start_time <<
"\n";
File and directory operations.
const char * BLI_dir_home(void)
BVHTree * BLI_bvhtree_new(int maxsize, float epsilon, char tree_type, char axis)
void BLI_bvhtree_balance(BVHTree *tree)
void BLI_bvhtree_free(BVHTree *tree)
void BLI_bvhtree_insert(BVHTree *tree, int index, const float co[3], int numpoints)
void BLI_bvhtree_ray_cast_all(const BVHTree *tree, const float co[3], const float dir[3], float radius, float hit_dist, BVHTree_RayCastCallback callback, void *userdata)
MINLINE double max_dd(double a, double b)
Math vector functions needed specifically for mesh intersect and boolean.
void closest_on_tri_to_point_v3(float r[3], const float p[3], const float v1[3], const float v2[3], const float v3[3])
bool isect_ray_tri_epsilon_v3(const float ray_origin[3], const float ray_direction[3], const float v0[3], const float v1[3], const float v2[3], float *r_lambda, float r_uv[2], float epsilon)
MINLINE float len_squared_v3v3(const float a[3], const float b[3]) ATTR_WARN_UNUSED_RESULT
MINLINE void copy_v3fl_v3db(float r[3], const double a[3])
double BLI_time_now_seconds(void)
#define UNUSED_VARS_NDEBUG(...)
std::ostream & operator<<(std::ostream &stream, bUUID uuid)
bool operator==(const AssetWeakReference &a, const AssetWeakReference &b)
int pad[32 - sizeof(int)]
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
unsigned long long int uint64_t
SIMD_FORCE_INLINE btVector3 & operator[](int i)
Get a mutable reference to a row of the matrix as a vector.
SIMD_FORCE_INLINE const btScalar & w() const
Return the w value.
SIMD_FORCE_INLINE btScalar norm() const
Return the norm (length) of the vector.
bool closest(btVector3 &v)
void fill(const T &value) const
const Value * lookup_ptr(const Key &key) const
void print_stats(const char *name) const
ValueIterator values() const &
const Value & lookup(const Key &key) const
Value lookup_default(const Key &key, const Value &default_value) const
void add_new(const Key &key, const Value &value)
ItemIterator items() const &
bool contains(const Key &key) const
void add_new(const Key &key)
constexpr int64_t size() const
constexpr const T * end() const
constexpr IndexRange index_range() const
constexpr const T * begin() const
constexpr bool is_empty() const
void push(const T &value)
int64_t append_and_get_index(const T &value)
void append(const T &value)
IndexRange index_range() const
void reserve(const int64_t min_capacity)
void append_non_duplicates(const T &value)
BLI_INLINE float fb(float length, float L)
ccl_device_inline float len_squared(const float2 a)
ccl_device_inline float2 fabs(const float2 a)
T length_squared(const VecBase< T, Size > &a)
std::ostream & operator<<(std::ostream &stream, EulerOrder order)
T dot(const QuaternionBase< T > &a, const QuaternionBase< T > &b)
AxisSigned cross(const AxisSigned a, const AxisSigned b)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
uint64_t get_default_hash(const T &v, const Args &...args)
int orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d)
VecBase< double, 3 > double3
static void init(bNodeTree *, bNode *node)