37# include <tbb/parallel_reduce.h>
38# include <tbb/spin_mutex.h>
51 const Vert *v_[2] = {
nullptr,
nullptr};
55 Edge(
const Vert *v0,
const Vert *v1)
57 if (v0->id <= v1->id) {
67 const Vert *v0()
const
72 const Vert *v1()
const
84 return v_[0]->id == other.v_[0]->id && v_[1]->id == other.v_[1]->id;
93static std::ostream &
operator<<(std::ostream &os,
const Edge &
e)
95 if (
e.v0() ==
nullptr) {
100 os <<
"(" <<
e.v0() <<
"," <<
e.v1() <<
")";
105static std::ostream &
operator<<(std::ostream &os,
const Span<int> a)
109 if (i != a.size() - 1) {
116static std::ostream &
operator<<(std::ostream &os,
const Array<int> &iarr)
118 os << Span<int>(iarr);
123class TriMeshTopology : NonCopyable {
130 TriMeshTopology(
const IMesh &tm);
135 int other_tri_if_manifold(Edge
e,
int t)
const
138 if (p !=
nullptr && (*p)->size() == 2) {
139 return ((**p)[0] == t) ? (**p)[1] : (**p)[0];
145 const Vector<int> *edge_tris(Edge
e)
const
152 Span<Edge> vert_edges(
const Vert *
v)
const
159 return edge_tri_.
items();
163TriMeshTopology::TriMeshTopology(
const IMesh &tm)
165 const int dbg_level = 0;
167 std::cout <<
"TRIMESHTOPOLOGY CONSTRUCTION\n";
171 const int estimate_num_edges = 2 * tm.face_size();
172 const int estimate_verts_num = tm.face_size();
173 edge_tri_.
reserve(estimate_num_edges);
174 vert_edges_.
reserve(estimate_verts_num);
175 for (
int t : tm.face_index_range()) {
176 const Face &tri = *tm.face(t);
178 for (
int i = 0; i < 3; ++i) {
179 const Vert *
v = tri[i];
180 const Vert *vnext = tri[(i + 1) % 3];
183 if (edges ==
nullptr) {
184 vert_edges_.
add_new(
v, Vector<Edge>());
192 edge_tri_.
add_new(
e,
new Vector<int>{t});
195 (*p)->append_non_duplicates(t);
201 std::cout <<
"After TriMeshTopology construction\n";
202 for (
auto item : edge_tri_.
items()) {
203 std::cout <<
"tris for edge " << item.key <<
": " << *item.value <<
"\n";
204 constexpr bool print_stats =
false;
209 for (
auto item : vert_edges_.
items()) {
210 std::cout <<
"edges for vert " << item.key <<
":\n";
211 for (
const Edge &
e : item.value) {
212 std::cout <<
" " <<
e <<
"\n";
219TriMeshTopology::~TriMeshTopology()
221 Vector<Vector<int> *> values;
224 for (
auto *item : edge_tri_.
values()) {
229 for (int i : range) {
257 IndexRange tri_range()
const
259 return IndexRange(tri_.
size());
262 Span<int> tris()
const
264 return Span<int>(tri_);
267 int cell_above{NO_INDEX};
268 int cell_below{NO_INDEX};
269 int component{NO_INDEX};
274 os <<
"Patch " << patch.tris();
275 if (patch.cell_above != NO_INDEX) {
276 os <<
" cell_above=" << patch.cell_above;
279 os <<
" cell_above not set";
281 if (patch.cell_below != NO_INDEX) {
282 os <<
" cell_below=" << patch.cell_below;
285 os <<
" cell_below not set";
292 Vector<Patch> patch_;
294 Array<int> tri_patch_;
299 explicit PatchesInfo(
int ntri)
301 constexpr int max_expected_patch_patch_incidences = 100;
302 tri_patch_ = Array<int>(ntri, NO_INDEX);
303 pp_edge_.
reserve(max_expected_patch_patch_incidences);
306 int tri_patch(
int t)
const
308 return tri_patch_[t];
317 void grow_patch(
int patch_index,
int t)
319 tri_patch_[t] = patch_index;
320 patch_[patch_index].add_tri(t);
323 bool tri_is_assigned(
int t)
const
325 return tri_patch_[t] != NO_INDEX;
328 const Patch &patch(
int patch_index)
const
330 return patch_[patch_index];
333 Patch &patch(
int patch_index)
335 return patch_[patch_index];
338 int tot_patch()
const
340 return patch_.
size();
343 IndexRange index_range()
const
345 return IndexRange(patch_.
size());
348 const Patch *begin()
const
350 return patch_.
begin();
353 const Patch *end()
const
360 return patch_.
begin();
368 void add_new_patch_patch_edge(
int p1,
int p2, Edge
e)
370 pp_edge_.
add_new(std::pair<int, int>(p1, p2),
e);
371 pp_edge_.
add_new(std::pair<int, int>(p2, p1),
e);
374 Edge patch_patch_edge(
int p1,
int p2)
385static bool apply_bool_op(BoolOpType bool_optype,
const Array<int> &winding);
395 int merged_to_{NO_INDEX};
396 bool winding_assigned_{
false};
398 bool in_output_volume_{
false};
401 bool zero_volume_{
false};
406 void add_patch(
int p)
409 zero_volume_ =
false;
412 const Set<int> &patches()
const
418 int patch_other(
int p)
const
420 if (patches_.
size() != 2) {
423 for (
int pother : patches_) {
431 Span<int> winding()
const
433 return Span<int>(winding_);
436 void init_winding(
int winding_len)
438 winding_ = Array<int>(winding_len);
441 void seed_ambient_winding()
444 winding_assigned_ =
true;
447 void set_winding_and_in_output_volume(
const Cell &from_cell,
450 BoolOpType bool_optype)
452 std::copy(from_cell.winding().begin(), from_cell.winding().end(), winding_.
begin());
454 winding_[shape] += delta;
456 winding_assigned_ =
true;
457 in_output_volume_ = apply_bool_op(bool_optype, winding_);
460 bool in_output_volume()
const
462 return in_output_volume_;
465 bool winding_assigned()
const
467 return winding_assigned_;
470 bool zero_volume()
const
475 int merged_to()
const
480 void set_merged_to(
int c)
489 void check_for_zero_volume(
const PatchesInfo &pinfo,
const IMesh &mesh);
492static std::ostream &
operator<<(std::ostream &os,
const Cell &cell)
494 os <<
"Cell patches";
495 for (
int p : cell.patches()) {
496 std::cout <<
" " << p;
498 if (cell.winding().size() > 0) {
499 os <<
" winding=" << cell.winding();
500 os <<
" in_output_volume=" << cell.in_output_volume();
502 os <<
" zv=" << cell.zero_volume();
507static bool tris_have_same_verts(
const IMesh &mesh,
int t1,
int t2)
509 const Face &tri1 = *mesh.face(t1);
510 const Face &tri2 = *mesh.face(t2);
511 BLI_assert(tri1.size() == 3 && tri2.size() == 3);
512 if (tri1.vert[0] == tri2.vert[0]) {
513 return ((tri1.vert[1] == tri2.vert[1] && tri1.vert[2] == tri2.vert[2]) ||
514 (tri1.vert[1] == tri2.vert[2] && tri1.vert[2] == tri2.vert[1]));
516 if (tri1.vert[0] == tri2.vert[1]) {
517 return ((tri1.vert[1] == tri2.vert[0] && tri1.vert[2] == tri2.vert[2]) ||
518 (tri1.vert[1] == tri2.vert[2] && tri1.vert[2] == tri2.vert[0]));
520 if (tri1.vert[0] == tri2.vert[2]) {
521 return ((tri1.vert[1] == tri2.vert[0] && tri1.vert[2] == tri2.vert[1]) ||
522 (tri1.vert[1] == tri2.vert[1] && tri1.vert[2] == tri2.vert[0]));
532void Cell::check_for_zero_volume(
const PatchesInfo &pinfo,
const IMesh &mesh)
534 if (patches_.size() == 2) {
535 int p1_index = NO_INDEX;
536 int p2_index = NO_INDEX;
537 for (
int p : patches_) {
538 if (p1_index == NO_INDEX) {
545 BLI_assert(p1_index != NO_INDEX && p2_index != NO_INDEX);
546 const Patch &p1 = pinfo.patch(p1_index);
547 const Patch &p2 = pinfo.patch(p2_index);
548 if (p1.tot_tri() == 1 && p2.tot_tri() == 1) {
549 if (tris_have_same_verts(mesh, p1.tri(0), p2.tri(0))) {
561 CellsInfo() =
default;
574 const Cell &cell(
int c)
const
584 IndexRange index_range()
const
589 const Cell *begin()
const
591 return cell_.
begin();
594 const Cell *end()
const
601 return cell_.
begin();
609 void init_windings(
int winding_len)
611 for (Cell &cell : cell_) {
612 cell.init_winding(winding_len);
620static void write_obj_cell_patch(
const IMesh &m,
621 const CellsInfo &cinfo,
622 const PatchesInfo &pinfo,
624 const std::string &name)
632 const char *objdir =
"/tmp/";
635 std::string fname = std::string(objdir) + name + std::string(
"_cellpatch.obj");
639 std::cout <<
"Could not open file " << fname <<
"\n";
646 f <<
"o cellpatch\n";
647 for (
const Vert *
v : mm.vertices()) {
649 f <<
"v " << dv[0] <<
" " << dv[1] <<
" " << dv[2] <<
"\n";
652 for (
int p : pinfo.index_range()) {
653 f <<
"g patch" << p <<
"\n";
654 const Patch &patch = pinfo.patch(p);
655 for (
int t : patch.tris()) {
656 const Face &tri = *mm.face(t);
658 for (
const Vert *
v : tri) {
659 f << mm.lookup_vert(
v) + 1 <<
" ";
665 for (
int c : cinfo.index_range()) {
666 f <<
"g cell" << c <<
"\n";
667 const Cell &cell = cinfo.cell(c);
668 for (
int p : cell.patches()) {
669 const Patch &patch = pinfo.patch(p);
670 for (
int t : patch.tris()) {
671 const Face &tri = *mm.face(t);
673 for (
const Vert *
v : tri) {
674 f << mm.lookup_vert(
v) + 1 <<
" ";
683static void merge_cells(
int merge_to,
int merge_from, CellsInfo &cinfo, PatchesInfo &pinfo)
685 if (merge_to == merge_from) {
688 Cell &merge_from_cell = cinfo.cell(merge_from);
689 Cell &merge_to_cell = cinfo.cell(merge_to);
690 int final_merge_to = merge_to;
691 while (merge_to_cell.merged_to() != NO_INDEX) {
692 final_merge_to = merge_to_cell.merged_to();
693 merge_to_cell = cinfo.cell(final_merge_to);
695 for (
int cell_p : merge_from_cell.patches()) {
696 merge_to_cell.add_patch(cell_p);
697 Patch &patch = pinfo.patch(cell_p);
698 if (patch.cell_above == merge_from) {
699 patch.cell_above = merge_to;
701 if (patch.cell_below == merge_from) {
702 patch.cell_below = merge_to;
705 merge_from_cell.set_merged_to(final_merge_to);
711static PatchesInfo find_patches(
const IMesh &tm,
const TriMeshTopology &tmtopo)
713 const int dbg_level = 0;
715 std::cout <<
"\nFIND_PATCHES\n";
717 int ntri = tm.face_size();
718 PatchesInfo pinfo(ntri);
724 threading::parallel_for(tm.face_index_range(), 2048, [&](
IndexRange range) {
725 for (int t : range) {
726 const Face &tri = *tm.face(t);
727 for (int i = 0; i < 3; ++i) {
728 Edge e(tri[i], tri[(i + 1) % 3]);
729 t_others[t][i] = tmtopo.other_tri_if_manifold(e, t);
733 for (
int t : tm.face_index_range()) {
734 if (pinfo.tri_patch(t) == -1) {
735 cur_patch_grow.
push(t);
736 int cur_patch_index = pinfo.add_patch();
737 while (!cur_patch_grow.
is_empty()) {
738 int tcand = cur_patch_grow.
pop();
740 std::cout <<
"pop tcand = " << tcand <<
"; assigned = " << pinfo.tri_is_assigned(tcand)
743 if (pinfo.tri_is_assigned(tcand)) {
747 std::cout <<
"grow patch from seed tcand=" << tcand <<
"\n";
749 pinfo.grow_patch(cur_patch_index, tcand);
750 const Face &tri = *tm.face(tcand);
751 for (
int i = 0; i < 3; ++i) {
752 Edge e(tri[i], tri[(i + 1) % 3]);
753 int t_other = t_others[tcand][i];
755 std::cout <<
" edge " <<
e <<
" generates t_other=" << t_other <<
"\n";
757 if (t_other != NO_INDEX) {
758 if (!pinfo.tri_is_assigned(t_other)) {
760 std::cout <<
" push t_other = " << t_other <<
"\n";
762 cur_patch_grow.
push(t_other);
768 std::cout <<
" e non-manifold case\n";
771 if (etris !=
nullptr) {
773 int t_other = (*etris)[i];
774 if (t_other != tcand && pinfo.tri_is_assigned(t_other)) {
775 int p_other = pinfo.tri_patch(t_other);
776 if (p_other == cur_patch_index) {
779 if (pinfo.patch_patch_edge(cur_patch_index, p_other).v0() ==
nullptr) {
780 pinfo.add_new_patch_patch_edge(cur_patch_index, p_other,
e);
782 std::cout <<
"added patch_patch_edge (" << cur_patch_index <<
"," << p_other
783 <<
") = " <<
e <<
"\n";
795 std::cout <<
"\nafter FIND_PATCHES: found " << pinfo.tot_patch() <<
" patches\n";
796 for (
int p : pinfo.index_range()) {
797 std::cout << p <<
": " << pinfo.patch(p) <<
"\n";
800 std::cout <<
"\ntriangle map\n";
801 for (
int t : tm.face_index_range()) {
802 std::cout << t <<
": " << tm.face(t) <<
" patch " << pinfo.tri_patch(t) <<
"\n";
805 std::cout <<
"\npatch-patch incidences\n";
806 for (
int p1 : pinfo.index_range()) {
807 for (
int p2 : pinfo.index_range()) {
808 Edge e = pinfo.patch_patch_edge(p1, p2);
809 if (
e.v0() !=
nullptr) {
810 std::cout <<
"p" << p1 <<
" and p" << p2 <<
" share edge " <<
e <<
"\n";
824static const Vert *find_flap_vert(
const Face &tri,
const Edge e,
bool *r_rev)
828 if (tri[0] ==
e.v0()) {
829 if (tri[1] ==
e.v1()) {
834 if (tri[2] !=
e.v1()) {
841 else if (tri[1] ==
e.v0()) {
842 if (tri[2] ==
e.v1()) {
847 if (tri[0] !=
e.v1()) {
855 if (tri[2] !=
e.v0()) {
858 if (tri[0] ==
e.v1()) {
863 if (tri[1] !=
e.v1()) {
887static int sort_tris_class(
const Face &tri,
const Face &tri0,
const Edge e)
889 const int dbg_level = 0;
891 std::cout <<
"classify e = " <<
e <<
"\n";
893 mpq3 a0 = tri0[0]->co_exact;
894 mpq3 a1 = tri0[1]->co_exact;
895 mpq3 a2 = tri0[2]->co_exact;
898 const Vert *flapv0 = find_flap_vert(tri0,
e, &rev0);
899 const Vert *flapv = find_flap_vert(tri,
e, &rev);
901 std::cout <<
" t0 = " << tri0[0] <<
" " << tri0[1] <<
" " << tri0[2];
902 std::cout <<
" rev0 = " << rev0 <<
" flapv0 = " << flapv0 <<
"\n";
903 std::cout <<
" t = " << tri[0] <<
" " << tri[1] <<
" " << tri[2];
904 std::cout <<
" rev = " << rev <<
" flapv = " << flapv <<
"\n";
906 BLI_assert(flapv !=
nullptr && flapv0 !=
nullptr);
907 const mpq3 flap = flapv->co_exact;
909 int orient =
orient3d(a0, a1, a2, flap);
914 else if (orient < 0) {
918 ans = flapv == flapv0 ? 1 : 2;
921 std::cout <<
" orient = " << orient <<
" ans = " << ans <<
"\n";
926constexpr int EXTRA_TRI_INDEX = INT_MAX;
934static void sort_by_signed_triangle_index(
Vector<int> &g,
937 const Face *extra_tri)
941 const Face &tri = g[i] == EXTRA_TRI_INDEX ? *extra_tri : *tm.face(g[i]);
943 find_flap_vert(tri,
e, &rev);
944 signed_g[i] = rev ? -g[i] : g[i];
946 std::sort(signed_g.begin(), signed_g.end());
949 g[i] =
abs(signed_g[i]);
968 const IMesh &tm,
const Edge e,
const Span<int> tris,
const int t0,
const Face *extra_tri)
980 const int dbg_level = 0;
988 std::cout <<
"sort_tris_around_edge " <<
e <<
"\n";
989 std::cout <<
"tris = " << tris <<
"\n";
990 std::cout <<
"t0 = " << t0 <<
"\n";
996 std::array<Vector<int> *, 4> groups = {&g1, &g2, &g3, &g4};
997 const Face &triref = *tm.face(tris[0]);
1003 BLI_assert(t < tm.face_size() || (t == EXTRA_TRI_INDEX && extra_tri !=
nullptr));
1004 const Face &tri = (t == EXTRA_TRI_INDEX) ? *extra_tri : *tm.face(t);
1005 if (dbg_level > 2) {
1006 std::cout <<
"classifying tri " << t <<
" with respect to " << tris[0] <<
"\n";
1008 int group_num = sort_tris_class(tri, triref,
e);
1009 if (dbg_level > 2) {
1010 std::cout <<
" classify result : " << group_num <<
"\n";
1012 groups[group_num - 1]->append(t);
1014 if (dbg_level > 1) {
1015 std::cout <<
"g1 = " << g1 <<
"\n";
1016 std::cout <<
"g2 = " << g2 <<
"\n";
1017 std::cout <<
"g3 = " << g3 <<
"\n";
1018 std::cout <<
"g4 = " << g4 <<
"\n";
1020 if (g1.
size() > 1) {
1021 sort_by_signed_triangle_index(g1,
e, tm, extra_tri);
1022 if (dbg_level > 1) {
1023 std::cout <<
"g1 sorted: " << g1 <<
"\n";
1026 if (g2.
size() > 1) {
1027 sort_by_signed_triangle_index(g2,
e, tm, extra_tri);
1028 if (dbg_level > 1) {
1029 std::cout <<
"g2 sorted: " << g2 <<
"\n";
1032 if (g3.
size() > 1) {
1033 Array<int> g3sorted = sort_tris_around_edge(tm,
e, g3, t0, extra_tri);
1035 if (dbg_level > 1) {
1036 std::cout <<
"g3 sorted: " << g3 <<
"\n";
1039 if (g4.
size() > 1) {
1040 Array<int> g4sorted = sort_tris_around_edge(tm,
e, g4, t0, extra_tri);
1042 if (dbg_level > 1) {
1043 std::cout <<
"g4 sorted: " << g4 <<
"\n";
1048 int *p = ans.begin();
1049 if (tris[0] == t0) {
1050 p = std::copy(g1.
begin(), g1.
end(), p);
1051 p = std::copy(g4.
begin(), g4.
end(), p);
1052 p = std::copy(g2.
begin(), g2.
end(), p);
1053 std::copy(g3.
begin(), g3.
end(), p);
1056 p = std::copy(g3.
begin(), g3.
end(), p);
1057 p = std::copy(g1.
begin(), g1.
end(), p);
1058 p = std::copy(g4.
begin(), g4.
end(), p);
1059 std::copy(g2.
begin(), g2.
end(), p);
1061 if (dbg_level > 0) {
1062 std::cout <<
"sorted tris = " << ans <<
"\n";
1073static void find_cells_from_edge(
const IMesh &tm,
1074 const TriMeshTopology &tmtopo,
1079 const int dbg_level = 0;
1080 if (dbg_level > 0) {
1081 std::cout <<
"FIND_CELLS_FROM_EDGE " <<
e <<
"\n";
1085 Array<int> sorted_tris = sort_tris_around_edge(
1086 tm,
e,
Span<int>(*edge_tris), (*edge_tris)[0],
nullptr);
1088 int n_edge_tris = edge_tris->
size();
1090 for (
int i = 0; i < n_edge_tris; ++i) {
1091 edge_patches[i] = pinfo.tri_patch(sorted_tris[i]);
1092 if (dbg_level > 1) {
1093 std::cout <<
"edge_patches[" << i <<
"] = " << edge_patches[i] <<
"\n";
1096 for (
int i = 0; i < n_edge_tris; ++i) {
1097 int inext = (i + 1) % n_edge_tris;
1098 int r_index = edge_patches[i];
1099 int rnext_index = edge_patches[inext];
1100 Patch &r = pinfo.patch(r_index);
1101 Patch &rnext = pinfo.patch(rnext_index);
1104 find_flap_vert(*tm.face(sorted_tris[i]),
e, &r_flipped);
1105 find_flap_vert(*tm.face(sorted_tris[inext]),
e, &rnext_flipped);
1106 int *r_follow_cell = r_flipped ? &r.cell_below : &r.cell_above;
1107 int *rnext_prev_cell = rnext_flipped ? &rnext.cell_above : &rnext.cell_below;
1108 if (dbg_level > 0) {
1109 std::cout <<
"process patch pair " << r_index <<
" " << rnext_index <<
"\n";
1110 std::cout <<
" r_flipped = " << r_flipped <<
" rnext_flipped = " << rnext_flipped <<
"\n";
1111 std::cout <<
" r_follow_cell (" << (r_flipped ?
"below" :
"above")
1112 <<
") = " << *r_follow_cell <<
"\n";
1113 std::cout <<
" rnext_prev_cell (" << (rnext_flipped ?
"above" :
"below")
1114 <<
") = " << *rnext_prev_cell <<
"\n";
1116 if (*r_follow_cell == NO_INDEX && *rnext_prev_cell == NO_INDEX) {
1118 int c = cinfo.add_cell();
1120 *rnext_prev_cell = c;
1121 Cell &cell = cinfo.cell(c);
1122 cell.add_patch(r_index);
1123 cell.add_patch(rnext_index);
1124 cell.check_for_zero_volume(pinfo, tm);
1125 if (dbg_level > 0) {
1126 std::cout <<
" made new cell " << c <<
"\n";
1127 std::cout <<
" p" << r_index <<
"." << (r_flipped ?
"cell_below" :
"cell_above") <<
" = c"
1129 std::cout <<
" p" << rnext_index <<
"." << (rnext_flipped ?
"cell_above" :
"cell_below")
1130 <<
" = c" << c <<
"\n";
1133 else if (*r_follow_cell != NO_INDEX && *rnext_prev_cell == NO_INDEX) {
1134 int c = *r_follow_cell;
1135 *rnext_prev_cell = c;
1136 Cell &cell = cinfo.cell(c);
1137 cell.add_patch(rnext_index);
1138 cell.check_for_zero_volume(pinfo, tm);
1139 if (dbg_level > 0) {
1140 std::cout <<
" reuse r_follow: p" << rnext_index <<
"."
1141 << (rnext_flipped ?
"cell_above" :
"cell_below") <<
" = c" << c <<
"\n";
1144 else if (*r_follow_cell == NO_INDEX && *rnext_prev_cell != NO_INDEX) {
1145 int c = *rnext_prev_cell;
1147 Cell &cell = cinfo.cell(c);
1148 cell.add_patch(r_index);
1149 cell.check_for_zero_volume(pinfo, tm);
1150 if (dbg_level > 0) {
1151 std::cout <<
" reuse rnext prev: rprev_p" << r_index <<
"."
1152 << (r_flipped ?
"cell_below" :
"cell_above") <<
" = c" << c <<
"\n";
1156 if (*r_follow_cell != *rnext_prev_cell) {
1157 int follow_cell_num_patches = cinfo.cell(*r_follow_cell).patches().size();
1158 int prev_cell_num_patches = cinfo.cell(*rnext_prev_cell).patches().size();
1159 if (follow_cell_num_patches >= prev_cell_num_patches) {
1160 if (dbg_level > 0) {
1161 std::cout <<
" merge cell " << *rnext_prev_cell <<
" into cell " << *r_follow_cell
1164 merge_cells(*r_follow_cell, *rnext_prev_cell, cinfo, pinfo);
1168 if (dbg_level > 0) {
1169 std::cout <<
" merge cell " << *r_follow_cell <<
" into cell " << *rnext_prev_cell
1172 merge_cells(*rnext_prev_cell, *r_follow_cell, cinfo, pinfo);
1182static CellsInfo find_cells(
const IMesh &tm,
const TriMeshTopology &tmtopo, PatchesInfo &pinfo)
1184 const int dbg_level = 0;
1185 if (dbg_level > 0) {
1186 std::cout <<
"\nFIND_CELLS\n";
1191 for (
const auto item : pinfo.patch_patch_edge_map().items()) {
1192 int p = item.key.first;
1193 int q = item.key.second;
1195 const Edge &
e = item.value;
1198 find_cells_from_edge(tm, tmtopo, pinfo, cinfo,
e);
1207 for (
int p : pinfo.index_range()) {
1208 Patch &patch = pinfo.patch(p);
1209 if (patch.cell_above == NO_INDEX) {
1210 int c = cinfo.add_cell();
1211 patch.cell_above = c;
1212 Cell &cell = cinfo.cell(c);
1215 if (patch.cell_below == NO_INDEX) {
1216 int c = cinfo.add_cell();
1217 patch.cell_below = c;
1218 Cell &cell = cinfo.cell(c);
1222 if (dbg_level > 0) {
1223 std::cout <<
"\nFIND_CELLS found " << cinfo.tot_cell() <<
" cells\nCells\n";
1224 for (
int i : cinfo.index_range()) {
1225 std::cout << i <<
": " << cinfo.cell(i) <<
"\n";
1227 std::cout <<
"Patches\n";
1228 for (
int i : pinfo.index_range()) {
1229 std::cout << i <<
": " << pinfo.patch(i) <<
"\n";
1231 if (dbg_level > 1) {
1232 write_obj_cell_patch(tm, cinfo, pinfo,
false,
"postfindcells");
1243static Vector<Vector<int>> find_patch_components(
const CellsInfo &cinfo, PatchesInfo &pinfo)
1245 constexpr int dbg_level = 0;
1246 if (dbg_level > 0) {
1247 std::cout <<
"FIND_PATCH_COMPONENTS\n";
1249 if (pinfo.tot_patch() == 0) {
1252 int current_component = 0;
1253 Array<bool> cell_processed(cinfo.tot_cell(),
false);
1256 for (
int pstart : pinfo.index_range()) {
1257 Patch &patch_pstart = pinfo.patch(pstart);
1258 if (patch_pstart.component != NO_INDEX) {
1262 ans[current_component].
append(pstart);
1264 patch_pstart.component = current_component;
1266 int p = stack.
pop();
1267 Patch &patch = pinfo.patch(p);
1268 BLI_assert(patch.component == current_component);
1269 for (
int c : {patch.cell_above, patch.cell_below}) {
1270 if (cell_processed[c]) {
1273 cell_processed[c] =
true;
1274 for (
int pn : cinfo.cell(c).patches()) {
1275 Patch &patch_neighbor = pinfo.patch(pn);
1276 if (patch_neighbor.component == NO_INDEX) {
1277 patch_neighbor.component = current_component;
1279 ans[current_component].
append(pn);
1284 ++current_component;
1286 if (dbg_level > 0) {
1287 std::cout <<
"found " << ans.
size() <<
" components\n";
1289 std::cout << comp <<
": " << ans[comp] <<
"\n";
1299static bool patch_cell_graph_ok(
const CellsInfo &cinfo,
const PatchesInfo &pinfo)
1301 for (
int c : cinfo.index_range()) {
1302 const Cell &cell = cinfo.cell(c);
1303 if (cell.merged_to() != NO_INDEX) {
1306 if (cell.patches().is_empty()) {
1307 std::cout <<
"Patch/Cell graph disconnected at Cell " << c <<
" with no patches\n";
1310 for (
int p : cell.patches()) {
1311 if (p >= pinfo.tot_patch()) {
1312 std::cout <<
"Patch/Cell graph has bad patch index at Cell " << c <<
"\n";
1317 for (
int p : pinfo.index_range()) {
1318 const Patch &patch = pinfo.patch(p);
1319 if (patch.cell_above == NO_INDEX || patch.cell_below == NO_INDEX) {
1320 std::cout <<
"Patch/Cell graph disconnected at Patch " << p
1321 <<
" with one or two missing cells\n";
1324 if (patch.cell_above >= cinfo.tot_cell() || patch.cell_below >= cinfo.tot_cell()) {
1325 std::cout <<
"Patch/Cell graph has bad cell index at Patch " << p <<
"\n";
1345static bool is_pwn(
const IMesh &tm,
const TriMeshTopology &tmtopo)
1347 constexpr int dbg_level = 0;
1348 std::atomic<bool> is_pwn =
true;
1351 for (
auto item : tmtopo.edge_tri_map_items()) {
1356 if (!is_pwn.load()) {
1361 for (
int j : range) {
1366 for (
int t : *tris[j].second) {
1367 const Face &face = *tm.face(t);
1369 for (
int i : face.index_range()) {
1370 if (face[i] == edge.v0()) {
1371 if (face[(i + 1) % 3] == edge.v1()) {
1375 BLI_assert(face[(i + 3 - 1) % 3] == edge.v1());
1381 if (tot_orient != 0) {
1382 if (dbg_level > 0) {
1383 std::cout <<
"edge causing non-pwn: " << edge <<
"\n";
1390 return is_pwn.load();
1400static int find_cell_for_point_near_edge(
const mpq3 &p,
1403 const TriMeshTopology &tmtopo,
1404 const PatchesInfo &pinfo,
1407 constexpr int dbg_level = 0;
1408 if (dbg_level > 0) {
1409 std::cout <<
"FIND_CELL_FOR_POINT_NEAR_EDGE, p=" << p <<
" e=" <<
e <<
"\n";
1412 const Vert *dummy_vert = arena->add_or_find_vert(p, NO_INDEX);
1413 const Face *dummy_tri = arena->add_face({
e.v0(),
e.v1(), dummy_vert},
1415 {NO_INDEX, NO_INDEX, NO_INDEX},
1416 {
false,
false,
false});
1420 edge_tris[edge_tris.
size() - 1] = EXTRA_TRI_INDEX;
1421 Array<int> sorted_tris = sort_tris_around_edge(tm,
e, edge_tris, edge_tris[0], dummy_tri);
1422 if (dbg_level > 0) {
1423 std::cout <<
"sorted tris = " << sorted_tris <<
"\n";
1425 int *p_sorted_dummy = std::find(sorted_tris.
begin(), sorted_tris.
end(), EXTRA_TRI_INDEX);
1427 int dummy_index = p_sorted_dummy - sorted_tris.
begin();
1428 int prev_tri = (dummy_index == 0) ? sorted_tris[sorted_tris.
size() - 1] :
1429 sorted_tris[dummy_index - 1];
1430 if (dbg_level > 0) {
1431 int next_tri = (dummy_index == sorted_tris.
size() - 1) ? sorted_tris[0] :
1432 sorted_tris[dummy_index + 1];
1433 std::cout <<
"prev tri to dummy = " << prev_tri <<
"; next tri to dummy = " << next_tri
1436 const Patch &prev_patch = pinfo.patch(pinfo.tri_patch(prev_tri));
1437 if (dbg_level > 0) {
1438 std::cout <<
"prev_patch = " << prev_patch <<
"\n";
1441 find_flap_vert(*tm.face(prev_tri),
e, &prev_flipped);
1442 int c = prev_flipped ? prev_patch.cell_below : prev_patch.cell_above;
1443 if (dbg_level > 0) {
1444 std::cout <<
"find_cell_for_point_near_edge returns " << c <<
"\n";
1463static int find_ambient_cell(
const IMesh &tm,
1465 const TriMeshTopology &tmtopo,
1466 const PatchesInfo &pinfo,
1470 if (dbg_level > 0) {
1471 std::cout <<
"FIND_AMBIENT_CELL\n";
1475 const Vert *v_extreme;
1476 auto max_x_vert = [](
const Vert *a,
const Vert *
b) {
1477 return (a->co_exact.x >
b->co_exact.x) ? a :
b;
1479 if (component_patches ==
nullptr) {
1480 v_extreme = threading::parallel_reduce(
1481 tm.face_index_range(),
1485 const Vert *ans = init;
1486 for (int i : range) {
1487 const Face *f = tm.face(i);
1488 for (const Vert *v : *f) {
1489 if (v->co_exact.x > ans->co_exact.x) {
1499 if (dbg_level > 0) {
1500 std::cout <<
"restrict to patches " << *component_patches <<
"\n";
1502 int p0 = (*component_patches)[0];
1503 v_extreme = threading::parallel_reduce(
1506 (*tm.face(pinfo.patch(p0).tri(0)))[0],
1508 const Vert *ans = init;
1509 for (int pi : range) {
1510 int p = (*component_patches)[pi];
1511 const Vert *tris_ans = threading::parallel_reduce(
1512 IndexRange(pinfo.patch(p).tot_tri()),
1515 [&](IndexRange tris_range, const Vert *t_init) {
1516 const Vert *v_ans = t_init;
1517 for (int i : tris_range) {
1518 int t = pinfo.patch(p).tri(i);
1519 const Face *f = tm.face(t);
1520 for (const Vert *v : *f) {
1521 if (v->co_exact.x > v_ans->co_exact.x) {
1529 if (tris_ans->co_exact.x > ans->co_exact.x) {
1537 if (dbg_level > 0) {
1538 std::cout <<
"v_extreme = " << v_extreme <<
"\n";
1543 const Span<Edge> edges = tmtopo.vert_edges(v_extreme);
1544 const mpq_class &extreme_x = v_extreme->co_exact.x;
1545 const mpq_class &extreme_y = v_extreme->co_exact.y;
1547 mpq_class max_abs_slope = -1;
1548 for (
Edge e : edges) {
1549 const Vert *v_other = (
e.v0() == v_extreme) ?
e.v1() :
e.v0();
1550 const mpq3 &co_other = v_other->co_exact;
1551 mpq_class delta_x = co_other.x - extreme_x;
1557 mpq_class abs_slope =
abs((co_other.y - extreme_y) / delta_x);
1558 if (abs_slope > max_abs_slope) {
1560 max_abs_slope = abs_slope;
1563 if (dbg_level > 0) {
1564 std::cout <<
"ehull = " << ehull <<
" slope = " << max_abs_slope <<
"\n";
1568 mpq3 p_in_ambient = v_extreme->co_exact;
1569 p_in_ambient.x += 1;
1570 int c_ambient = find_cell_for_point_near_edge(p_in_ambient, ehull, tm, tmtopo, pinfo, arena);
1571 if (dbg_level > 0) {
1572 std::cout <<
"FIND_AMBIENT_CELL returns " << c_ambient <<
"\n";
1586static Edge find_good_sorting_edge(
const Vert *testp,
1587 const Vert *closestp,
1588 const TriMeshTopology &tmtopo)
1590 constexpr int dbg_level = 0;
1591 if (dbg_level > 0) {
1592 std::cout <<
"FIND_GOOD_SORTING_EDGE testp = " << testp <<
", closestp = " << closestp <<
"\n";
1600 const mpq3 &co_closest = closestp->co_exact;
1601 const mpq3 &co_test = testp->co_exact;
1603 mpq3 abscissa = co_test - co_closest;
1606 for (axis = 0; axis < 3; ++axis) {
1607 if (abscissa[axis] != 0) {
1612 int axis_next = (axis + 1) % 3;
1613 int axis_next_next = (axis_next + 1) % 3;
1615 ordinate[axis] = abscissa[axis_next];
1616 ordinate[axis_next] = -abscissa[axis];
1617 ordinate[axis_next_next] = 0;
1620 if (dbg_level > 0) {
1621 std::cout <<
"abscissa = " << abscissa <<
"\n";
1622 std::cout <<
"ordinate = " << ordinate <<
"\n";
1623 std::cout <<
"normal = " <<
normal <<
"\n";
1626 mpq_class max_abs_slope = -1;
1628 const Span<Edge> edges = tmtopo.vert_edges(closestp);
1629 for (
Edge e : edges) {
1630 const Vert *v_other = (
e.v0() == closestp) ?
e.v1() :
e.v0();
1631 const mpq3 &co_other = v_other->co_exact;
1632 mpq3 evec = co_other - co_closest;
1634 mpq3 proj_evec = evec - (
math::dot(evec, normal) / nlen2) * normal;
1638 mpq_class evec_a =
math::dot(proj_evec, abscissa);
1639 mpq_class evec_o =
math::dot(proj_evec, ordinate);
1640 if (dbg_level > 0) {
1641 std::cout <<
"e = " <<
e <<
"\n";
1642 std::cout <<
"v_other = " << v_other <<
"\n";
1643 std::cout <<
"evec = " << evec <<
", proj_evec = " << proj_evec <<
"\n";
1644 std::cout <<
"evec_a = " << evec_a <<
", evec_o = " << evec_o <<
"\n";
1649 if (dbg_level > 0) {
1650 std::cout <<
"perpendicular esort is " << esort <<
"\n";
1654 mpq_class abs_slope =
abs(evec_o / evec_a);
1655 if (abs_slope > max_abs_slope) {
1657 max_abs_slope = abs_slope;
1658 if (dbg_level > 0) {
1659 std::cout <<
"with abs_slope " << abs_slope <<
" new esort is " << esort <<
"\n";
1678static int find_containing_cell(
const Vert *
v,
1682 const PatchesInfo &pinfo,
1684 const TriMeshTopology &tmtopo,
1687 constexpr int dbg_level = 0;
1688 if (dbg_level > 0) {
1689 std::cout <<
"FIND_CONTAINING_CELL v=" <<
v <<
", t=" << t <<
"\n";
1691 const Face &tri = *tm.face(t);
1693 if (close_edge == -1 && close_vert == -1) {
1697 if (close_edge != -1) {
1698 const Vert *v0 = tri[close_edge];
1699 const Vert *v1 = tri[(close_edge + 1) % 3];
1700 const Span<Edge> edges = tmtopo.vert_edges(v0);
1701 if (dbg_level > 0) {
1702 std::cout <<
"look for edge containing " << v0 <<
" and " << v1 <<
"\n";
1703 std::cout <<
" in edges: ";
1704 for (
Edge e : edges) {
1705 std::cout <<
e <<
" ";
1709 for (
Edge e : edges) {
1710 if ((
e.v0() == v0 &&
e.v1() == v1) || (
e.v0() == v1 &&
e.v1() == v0)) {
1717 int cv = close_vert;
1718 const Vert *vert_cv = tri[cv];
1721 vert_cv = tri[(cv + 1) % 3];
1724 etest = find_good_sorting_edge(
v, vert_cv, tmtopo);
1727 if (dbg_level > 0) {
1728 std::cout <<
"etest = " << etest <<
"\n";
1730 int c = find_cell_for_point_near_edge(
v->co_exact, etest, tm, tmtopo, pinfo, arena);
1731 if (dbg_level > 0) {
1732 std::cout <<
"find_containing_cell returns " << c <<
"\n";
1750static mpq_class closest_on_tri_to_point(
const mpq3 &p,
1764 constexpr int dbg_level = 0;
1765 if (dbg_level > 0) {
1766 std::cout <<
"CLOSEST_ON_TRI_TO_POINT p = " << p <<
"\n";
1767 std::cout <<
" a = " << a <<
", b = " <<
b <<
", c = " << c <<
"\n";
1777 mpq_class d1 = math::dot_with_buffer(ab, ap, m);
1778 mpq_class d2 = math::dot_with_buffer(ac, ap, m);
1779 if (d1 <= 0 && d2 <= 0) {
1783 if (dbg_level > 0) {
1784 std::cout <<
" answer = a\n";
1786 return math::distance_squared_with_buffer(p, a, m);
1791 mpq_class d3 = math::dot_with_buffer(ab, bp, m);
1792 mpq_class d4 = math::dot_with_buffer(ac, bp, m);
1793 if (d3 >= 0 && d4 <= d3) {
1797 if (dbg_level > 0) {
1798 std::cout <<
" answer = b\n";
1800 return math::distance_squared_with_buffer(p,
b, m);
1803 mpq_class vc = d1 * d4 - d3 * d2;
1804 if (vc <= 0 && d1 >= 0 && d3 <= 0) {
1805 mpq_class
v = d1 / (d1 - d3);
1812 if (dbg_level > 0) {
1813 std::cout <<
" answer = on ab at " << r <<
"\n";
1815 return math::distance_squared_with_buffer(p, r, m);
1820 mpq_class d5 = math::dot_with_buffer(ab, cp, m);
1821 mpq_class d6 = math::dot_with_buffer(ac, cp, m);
1822 if (d6 >= 0 && d5 <= d6) {
1826 if (dbg_level > 0) {
1827 std::cout <<
" answer = c\n";
1829 return math::distance_squared_with_buffer(p, c, m);
1832 mpq_class vb = d5 * d2 - d1 * d6;
1833 if (vb <= 0 && d2 >= 0 && d6 <= 0) {
1834 mpq_class
w = d2 / (d2 - d6);
1841 if (dbg_level > 0) {
1842 std::cout <<
" answer = on ac at " << r <<
"\n";
1844 return math::distance_squared_with_buffer(p, r, m);
1847 mpq_class va = d3 * d6 - d5 * d4;
1848 if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) {
1849 mpq_class
w = (d4 - d3) / ((d4 - d3) + (d5 - d6));
1857 if (dbg_level > 0) {
1858 std::cout <<
" answer = on bc at " << r <<
"\n";
1860 return math::distance_squared_with_buffer(p, r, m);
1863 mpq_class denom = 1 / (va + vb + vc);
1864 mpq_class
v = vb * denom;
1865 mpq_class
w = vc * denom;
1873 if (dbg_level > 0) {
1874 std::cout <<
" answer = inside at " << r <<
"\n";
1876 return math::distance_squared_with_buffer(p, r, m);
1879static float closest_on_tri_to_point_float_dist_squared(
const float3 &p,
1892struct ComponentContainer {
1893 int containing_component{NO_INDEX};
1894 int nearest_cell{NO_INDEX};
1895 mpq_class dist_to_cell;
1897 ComponentContainer(
int cc,
int cell, mpq_class d)
1898 : containing_component(cc), nearest_cell(cell), dist_to_cell(d)
1913 const PatchesInfo &pinfo,
1914 const TriMeshTopology &tmtopo,
1918 constexpr int dbg_level = 0;
1919 if (dbg_level > 0) {
1920 std::cout <<
"FIND_COMPONENT_CONTAINERS for comp " << comp <<
"\n";
1923 int test_p = components[comp][0];
1924 int test_t = pinfo.patch(test_p).tri(0);
1925 const Vert *test_v = tm.face(test_t)[0].vert[0];
1926 if (dbg_level > 0) {
1927 std::cout <<
"test vertex in comp: " << test_v <<
"\n";
1929 const double3 &test_v_d = test_v->co;
1930 float3 test_v_f(test_v_d[0], test_v_d[1], test_v_d[2]);
1934 for (
int comp_other : components.index_range()) {
1935 if (comp == comp_other) {
1938 if (dbg_level > 0) {
1939 std::cout <<
"comp_other = " << comp_other <<
"\n";
1941 if (!bbs_might_intersect(comp_bb[comp], comp_bb[comp_other])) {
1942 if (dbg_level > 0) {
1943 std::cout <<
"bounding boxes don't overlap\n";
1947 int nearest_tri = NO_INDEX;
1948 int nearest_tri_close_vert = -1;
1949 int nearest_tri_close_edge = -1;
1950 mpq_class nearest_tri_dist_squared;
1951 float nearest_tri_dist_squared_float =
FLT_MAX;
1952 for (
int p : components[comp_other]) {
1953 const Patch &patch = pinfo.patch(p);
1954 for (
int t : patch.tris()) {
1955 const Face &tri = *tm.face(t);
1956 if (dbg_level > 1) {
1957 std::cout <<
"tri " << t <<
" = " << &tri <<
"\n";
1962 float d2_f = closest_on_tri_to_point_float_dist_squared(
1963 test_v_f, tri[0]->co, tri[1]->co, tri[2]->co);
1964 if (d2_f - FLT_EPSILON > nearest_tri_dist_squared_float) {
1967 mpq_class d2 = closest_on_tri_to_point(test_v->co_exact,
1980 if (dbg_level > 1) {
1981 std::cout <<
" close_edge=" << close_edge <<
" close_vert=" << close_vert
1982 <<
" dsquared=" << d2.get_d() <<
"\n";
1984 if (nearest_tri == NO_INDEX || d2 < nearest_tri_dist_squared) {
1986 nearest_tri_close_edge = close_edge;
1987 nearest_tri_close_vert = close_vert;
1988 nearest_tri_dist_squared = d2;
1989 nearest_tri_dist_squared_float = d2_f;
1993 if (dbg_level > 0) {
1994 std::cout <<
"closest tri to comp=" << comp <<
" in comp_other=" << comp_other <<
" is t"
1995 << nearest_tri <<
"\n";
1996 const Face *tn = tm.face(nearest_tri);
1997 std::cout <<
"tri = " << tn <<
"\n";
1998 std::cout <<
" (" << tn->vert[0]->co <<
"," << tn->vert[1]->co <<
"," << tn->vert[2]->co
2001 int containing_cell = find_containing_cell(test_v,
2003 nearest_tri_close_edge,
2004 nearest_tri_close_vert,
2010 if (dbg_level > 0) {
2011 std::cout <<
"containing cell = " << containing_cell <<
"\n";
2013 if (containing_cell != ambient_cell[comp_other]) {
2014 ans.
append(ComponentContainer(comp_other, containing_cell, nearest_tri_dist_squared));
2026 const PatchesInfo &pinfo,
2030 const int comp_grainsize = 16;
2035 threading::parallel_for(components.index_range(), comp_grainsize, [&](
IndexRange comp_range) {
2036 for (int c : comp_range) {
2037 BoundingBox &bb = comp_bb[c];
2038 double &maxa = max_abs[c];
2039 for (int p : components[c]) {
2040 const Patch &patch = pinfo.patch(p);
2041 for (int t : patch.tris()) {
2042 const Face &tri = *im.face(t);
2043 for (const Vert *v : tri) {
2045 for (int i = 0; i < 3; ++i) {
2046 maxa = max_dd(maxa, fabs(v->co[i]));
2053 double all_max_abs = 0.0;
2054 for (
int c : components.index_range()) {
2055 all_max_abs =
max_dd(all_max_abs, max_abs[c]);
2057 constexpr float pad_factor = 10.0f;
2058 float pad = all_max_abs == 0.0 ? FLT_EPSILON : 2 * FLT_EPSILON * all_max_abs;
2060 for (
int c : components.index_range()) {
2061 comp_bb[c].expand(
pad);
2072static void finish_patch_cell_graph(
const IMesh &tm,
2075 const TriMeshTopology &tmtopo,
2078 constexpr int dbg_level = 0;
2079 if (dbg_level > 0) {
2080 std::cout <<
"FINISH_PATCH_CELL_GRAPH\n";
2083 if (components.
size() <= 1) {
2084 if (dbg_level > 0) {
2085 std::cout <<
"one component so finish_patch_cell_graph does no work\n";
2089 if (dbg_level > 0) {
2090 std::cout <<
"components:\n";
2092 std::cout << comp <<
": " << components[comp] <<
"\n";
2097 ambient_cell[comp] = find_ambient_cell(tm, &components[comp], tmtopo, pinfo, arena);
2099 if (dbg_level > 0) {
2100 std::cout <<
"ambient cells:\n";
2102 std::cout << comp <<
": " << ambient_cell[comp] <<
"\n";
2105 int tot_components = components.
size();
2107 if (tot_components > 1) {
2109 populate_comp_bbs(components, pinfo, tm, comp_bb);
2111 comp_cont[comp] = find_component_containers(
2112 comp, components, ambient_cell, tm, pinfo, tmtopo, comp_bb, arena);
2114 if (dbg_level > 0) {
2115 std::cout <<
"component containers:\n";
2116 for (
int comp : comp_cont.index_range()) {
2117 std::cout << comp <<
": ";
2118 for (
const ComponentContainer &cc : comp_cont[comp]) {
2119 std::cout <<
"[containing_comp=" << cc.containing_component
2120 <<
", nearest_cell=" << cc.nearest_cell <<
", d2=" << cc.dist_to_cell <<
"] ";
2126 if (dbg_level > 1) {
2127 write_obj_cell_patch(tm, cinfo, pinfo,
false,
"beforemerge");
2131 for (
int comp : comp_cont.index_range()) {
2132 if (comp_cont[comp].is_empty()) {
2133 outer_components.
append(comp);
2136 ComponentContainer &
closest = comp_cont[comp][0];
2137 for (
int i = 1; i < comp_cont[comp].size(); ++i) {
2138 if (comp_cont[comp][i].dist_to_cell <
closest.dist_to_cell) {
2142 int comp_ambient = ambient_cell[comp];
2143 int cont_cell =
closest.nearest_cell;
2144 if (dbg_level > 0) {
2145 std::cout <<
"merge comp " << comp <<
"'s ambient cell=" << comp_ambient <<
" to cell "
2146 << cont_cell <<
"\n";
2148 merge_cells(cont_cell, comp_ambient, cinfo, pinfo);
2152 if (outer_components.
size() > 1) {
2153 int merged_ambient = ambient_cell[outer_components[0]];
2154 for (
int i = 1; i < outer_components.size(); ++i) {
2155 if (dbg_level > 0) {
2156 std::cout <<
"merge comp " << outer_components[i]
2157 <<
"'s ambient cell=" << ambient_cell[outer_components[i]] <<
" to cell "
2158 << merged_ambient <<
"\n";
2160 merge_cells(merged_ambient, ambient_cell[outer_components[i]], cinfo, pinfo);
2163 if (dbg_level > 0) {
2164 std::cout <<
"after FINISH_PATCH_CELL_GRAPH\nCells\n";
2165 for (
int i : cinfo.index_range()) {
2166 if (cinfo.cell(i).merged_to() == NO_INDEX) {
2167 std::cout << i <<
": " << cinfo.cell(i) <<
"\n";
2170 std::cout <<
"Patches\n";
2171 for (
int i : pinfo.index_range()) {
2172 std::cout << i <<
": " << pinfo.patch(i) <<
"\n";
2174 if (dbg_level > 1) {
2175 write_obj_cell_patch(tm, cinfo, pinfo,
false,
"finished");
2193static void propagate_windings_and_in_output_volume(PatchesInfo &pinfo,
2201 if (dbg_level > 0) {
2202 std::cout <<
"PROPAGATE_WINDINGS, ambient cell = " << c_ambient <<
"\n";
2204 Cell &cell_ambient = cinfo.cell(c_ambient);
2205 cell_ambient.seed_ambient_winding();
2208 queue.reserve(cinfo.tot_cell());
2210 queue.append(c_ambient);
2211 while (queue_head <
queue.size()) {
2212 int c =
queue[queue_head++];
2213 if (dbg_level > 1) {
2214 std::cout <<
"process cell " << c <<
"\n";
2216 Cell &cell = cinfo.cell(c);
2217 for (
int p : cell.patches()) {
2218 Patch &patch = pinfo.patch(p);
2219 bool p_above_c = patch.cell_below == c;
2220 int c_neighbor = p_above_c ? patch.cell_above : patch.cell_below;
2221 if (dbg_level > 1) {
2222 std::cout <<
" patch " << p <<
" p_above_c = " << p_above_c <<
"\n";
2223 std::cout <<
" c_neighbor = " << c_neighbor <<
"\n";
2225 Cell &cell_neighbor = cinfo.cell(c_neighbor);
2226 if (!cell_neighbor.winding_assigned()) {
2227 int winding_delta = p_above_c ? -1 : 1;
2228 int t = patch.tri(0);
2229 int shape = shape_fn(t);
2232 if (dbg_level > 1) {
2233 std::cout <<
" representative tri " << t <<
": in shape " << shape <<
"\n";
2235 cell_neighbor.set_winding_and_in_output_volume(cell, shape, winding_delta, op);
2236 if (dbg_level > 1) {
2237 std::cout <<
" now cell_neighbor = " << cell_neighbor <<
"\n";
2239 queue.append(c_neighbor);
2244 if (dbg_level > 0) {
2245 std::cout <<
"\nPROPAGATE_WINDINGS result\n";
2246 for (
int i = 0; i < cinfo.tot_cell(); ++i) {
2247 std::cout << i <<
": " << cinfo.cell(i) <<
"\n";
2261static bool apply_bool_op(BoolOpType bool_optype,
const Array<int> &winding)
2263 int nw = winding.
size();
2265 switch (bool_optype) {
2266 case BoolOpType::Intersect: {
2267 for (
int i = 0; i < nw; ++i) {
2268 if (winding[i] == 0) {
2274 case BoolOpType::Union: {
2275 for (
int i = 0; i < nw; ++i) {
2276 if (winding[i] != 0) {
2282 case BoolOpType::Difference: {
2284 if (winding[0] == 0) {
2290 for (
int i = 1; i < nw; ++i) {
2291 if (winding[i] >= 1) {
2308 const IMesh &tm_subdivided,
2309 const PatchesInfo &pinfo,
2310 const CellsInfo &cinfo,
2314 if (dbg_level > 0) {
2315 std::cout <<
"extract_zero_volume_cell_tris\n";
2319 for (
int p : pinfo.index_range()) {
2320 const Patch &patch = pinfo.patch(p);
2321 if (cinfo.cell(patch.cell_above).zero_volume() || cinfo.cell(patch.cell_below).zero_volume()) {
2322 adj_to_zv[p] =
true;
2325 adj_to_zv[p] =
false;
2330 Array<bool> allocated_to_stack(pinfo.tot_patch(),
false);
2331 for (
int p : pinfo.index_range()) {
2332 if (!adj_to_zv[p] || allocated_to_stack[p]) {
2335 int stack_index = patch_stacks.
size();
2339 allocated_to_stack[p] =
true;
2346 const Patch *pwalk_patch = &pinfo.patch(pwalk);
2347 int c = pwalk_patch->cell_above;
2348 const Cell *cell = &cinfo.cell(c);
2349 while (cell->zero_volume()) {
2352 int pother = cell->patch_other(pwalk);
2353 bool flip = pinfo.patch(pother).cell_above == c;
2356 allocated_to_stack[pother] =
true;
2358 pwalk_patch = &pinfo.patch(pwalk);
2359 c = flip ? pwalk_patch->cell_below : pwalk_patch->cell_above;
2360 cell = &cinfo.cell(c);
2362 const Cell *above_stack_cell = cell;
2365 pwalk_patch = &pinfo.patch(pwalk);
2366 c = pwalk_patch->cell_below;
2367 cell = &cinfo.cell(c);
2368 while (cell->zero_volume()) {
2370 int pother = cell->patch_other(pwalk);
2371 bool flip = pinfo.patch(pother).cell_below == c;
2374 allocated_to_stack[pother] =
true;
2376 pwalk_patch = &pinfo.patch(pwalk);
2377 c = flip ? pwalk_patch->cell_above : pwalk_patch->cell_below;
2378 cell = &cinfo.cell(c);
2380 const Cell *below_stack_cell = cell;
2381 if (dbg_level > 0) {
2382 std::cout <<
"process zero-volume patch stack " << stack <<
"\n";
2383 std::cout <<
"flipped = ";
2384 for (
bool b : flipped) {
2385 std::cout <<
b <<
" ";
2389 if (above_stack_cell->in_output_volume() ^ below_stack_cell->in_output_volume()) {
2390 bool need_flipped_tri = above_stack_cell->in_output_volume();
2391 if (dbg_level > 0) {
2392 std::cout <<
"need tri " << (need_flipped_tri ?
"flipped" :
"") <<
"\n";
2394 int t_to_add = NO_INDEX;
2395 for (
int i : stack.index_range()) {
2396 if (flipped[i] == need_flipped_tri) {
2397 t_to_add = pinfo.patch(stack[i]).tri(0);
2398 if (dbg_level > 0) {
2399 std::cout <<
"using tri " << t_to_add <<
"\n";
2401 r_tris.
append(tm_subdivided.face(t_to_add));
2405 if (t_to_add == NO_INDEX) {
2406 const Face *f = tm_subdivided.face(pinfo.patch(p).tri(0));
2407 const Face &tri = *f;
2409 std::array<const Vert *, 3> flipped_vs = {tri[0], tri[2], tri[1]};
2410 std::array<int, 3> flipped_e_origs = {
2411 tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
2412 std::array<bool, 3> flipped_is_intersect = {
2413 tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
2414 Face *flipped_f = arena->add_face(
2415 flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
2416 r_tris.
append(flipped_f);
2433static IMesh extract_from_in_output_volume_diffs(
const IMesh &tm_subdivided,
2434 const PatchesInfo &pinfo,
2435 const CellsInfo &cinfo,
2438 constexpr int dbg_level = 0;
2439 if (dbg_level > 0) {
2440 std::cout <<
"\nEXTRACT_FROM_FLAG_DIFFS\n";
2443 out_tris.
reserve(tm_subdivided.face_size());
2444 bool any_zero_volume_cell =
false;
2445 for (
int t : tm_subdivided.face_index_range()) {
2446 int p = pinfo.tri_patch(t);
2447 const Patch &patch = pinfo.patch(p);
2448 const Cell &cell_above = cinfo.cell(patch.cell_above);
2449 const Cell &cell_below = cinfo.cell(patch.cell_below);
2450 if (dbg_level > 0) {
2451 std::cout <<
"tri " << t <<
": cell_above=" << patch.cell_above
2452 <<
" cell_below=" << patch.cell_below <<
"\n";
2453 std::cout <<
" in_output_volume_above=" << cell_above.in_output_volume()
2454 <<
" in_output_volume_below=" << cell_below.in_output_volume() <<
"\n";
2456 bool adjacent_zero_volume_cell = cell_above.zero_volume() || cell_below.zero_volume();
2457 any_zero_volume_cell |= adjacent_zero_volume_cell;
2458 if (cell_above.in_output_volume() ^ cell_below.in_output_volume() &&
2459 !adjacent_zero_volume_cell)
2461 bool flip = cell_above.in_output_volume();
2462 if (dbg_level > 0) {
2463 std::cout <<
"need tri " << t <<
" flip=" << flip <<
"\n";
2465 Face *f = tm_subdivided.face(t);
2468 std::array<const Vert *, 3> flipped_vs = {tri[0], tri[2], tri[1]};
2469 std::array<int, 3> flipped_e_origs = {
2470 tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
2471 std::array<bool, 3> flipped_is_intersect = {
2472 tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
2473 Face *flipped_f = arena->add_face(
2474 flipped_vs, f->orig, flipped_e_origs, flipped_is_intersect);
2475 out_tris.
append(flipped_f);
2482 if (any_zero_volume_cell) {
2483 extract_zero_volume_cell_tris(out_tris, tm_subdivided, pinfo, cinfo, arena);
2485 return IMesh(out_tris);
2488static const char *bool_optype_name(BoolOpType op)
2491 case BoolOpType::None:
2493 case BoolOpType::Intersect:
2495 case BoolOpType::Union:
2497 case BoolOpType::Difference:
2498 return "difference";
2504static double3 calc_point_inside_tri_db(
const Face &tri)
2506 const Vert *v0 = tri.vert[0];
2507 const Vert *v1 = tri.vert[1];
2508 const Vert *
v2 = tri.vert[2];
2509 double3 ans = v0->co / 3 + v1->co / 3 +
v2->co / 3;
2512class InsideShapeTestData {
2515 FunctionRef<
int(
int)> shape_fn;
2518 Array<int> hit_parity;
2520 InsideShapeTestData(
const IMesh &tm, FunctionRef<
int(
int)> shape_fn,
int nshapes)
2521 : tm(tm), shape_fn(shape_fn), nshapes(nshapes)
2526static void inside_shape_callback(
void *userdata,
2531 const int dbg_level = 0;
2532 if (dbg_level > 0) {
2533 std::cout <<
"inside_shape_callback, index = " <<
index <<
"\n";
2535 InsideShapeTestData *
data =
static_cast<InsideShapeTestData *
>(userdata);
2536 const Face &tri = *
data->tm.face(index);
2537 int shape =
data->shape_fn(tri.orig);
2545 for (
int i = 0; i < 3; ++i) {
2546 fv0[i] =
float(tri.vert[0]->co[i]);
2547 fv1[i] =
float(tri.vert[1]->co[i]);
2548 fv2[i] =
float(tri.vert[2]->co[i]);
2550 if (dbg_level > 0) {
2551 std::cout <<
" fv0=(" << fv0[0] <<
"," << fv0[1] <<
"," << fv0[2] <<
")\n";
2552 std::cout <<
" fv1=(" << fv1[0] <<
"," << fv1[1] <<
"," << fv1[2] <<
")\n";
2553 std::cout <<
" fv2=(" << fv2[0] <<
"," << fv2[1] <<
"," << fv2[2] <<
")\n";
2556 ray->
origin, ray->
direction, fv0, fv1, fv2, &dist,
nullptr, FLT_EPSILON))
2561 int parity =
orient3d(tri.vert[0]->co, tri.vert[1]->co, tri.vert[2]->co, o_db);
2562 if (dbg_level > 0) {
2563 std::cout <<
"origin at " << o_db <<
", parity = " << parity <<
"\n";
2565 data->hit_parity[shape] += parity;
2578static void test_tri_inside_shapes(
const IMesh &tm,
2585 const int dbg_level = 0;
2586 if (dbg_level > 0) {
2587 std::cout <<
"test_point_inside_shapes, t_index = " << test_t_index <<
"\n";
2589 Face &tri_test = *tm.face(test_t_index);
2590 int shape = shape_fn(tri_test.orig);
2592 in_shape.
fill(0.0f);
2595 double3 test_point = calc_point_inside_tri_db(tri_test);
2597 tri_test.populate_plane(
false);
2599 const double offset_amount = 1
e-5;
2600 double3 offset_test_point = test_point + offset_amount *
norm;
2601 if (dbg_level > 0) {
2602 std::cout <<
"test tri is in shape " << shape <<
"\n";
2603 std::cout <<
"test point = " << test_point <<
"\n";
2604 std::cout <<
"offset_test_point = " << offset_test_point <<
"\n";
2610 constexpr int rays_num = 6;
2611 constexpr float r1 = 0.9987025295199663f;
2612 constexpr float ra = 0.04993512647599832f;
2613 constexpr float rb = 0.009987025295199663f;
2614 const float test_rays[rays_num][3] = {
2615 {r1, ra, rb}, {-r1, -ra, -rb}, {rb, r1, ra}, {-rb, -r1, -ra}, {ra, rb, r1}, {-ra, -rb, -r1}};
2616 InsideShapeTestData
data(tm, shape_fn, nshapes);
2619 const float co[3] = {
2620 float(offset_test_point[0]),
float(offset_test_point[1]),
float(offset_test_point[2])};
2621 for (
int i = 0; i < rays_num; ++i) {
2622 if (dbg_level > 0) {
2623 std::cout <<
"shoot ray " << i <<
"(" << test_rays[i][0] <<
"," << test_rays[i][1] <<
","
2624 << test_rays[i][2] <<
")\n";
2627 if (dbg_level > 0) {
2628 std::cout <<
"ray " << i <<
" result:";
2629 for (
int j = 0; j < nshapes; ++j) {
2630 std::cout <<
" " <<
data.hit_parity[j];
2634 for (
int j = 0; j < nshapes; ++j) {
2635 if (j != shape &&
data.hit_parity[j] > 0) {
2639 data.hit_parity.fill(0);
2641 for (
int j = 0; j < nshapes; ++j) {
2646 in_shape[j] =
float(count_insides[j]) /
float(rays_num);
2648 if (dbg_level > 0) {
2649 std::cout <<
"shape " << j <<
" inside = " << in_shape[j] <<
"\n";
2660static BVHTree *raycast_tree(
const IMesh &tm)
2663 for (
int i : tm.face_index_range()) {
2664 const Face *f = tm.face(i);
2666 for (
int j = 0; j < 3; ++j) {
2667 const Vert *
v = f->vert[j];
2668 for (
int k = 0; k < 3; ++k) {
2669 t_cos[3 * j + k] =
float(
v->co[k]);
2682static bool raycast_test_remove(BoolOpType op,
Array<int> &winding,
int shape,
bool *r_do_flip)
2684 constexpr int dbg_level = 0;
2690 bool in_output_volume_0 = apply_bool_op(op, winding);
2692 bool in_output_volume_1 = apply_bool_op(op, winding);
2693 bool do_remove = in_output_volume_0 == in_output_volume_1;
2694 bool do_flip = !do_remove && op == BoolOpType::Difference && shape != 0;
2695 if (dbg_level > 0) {
2696 std::cout <<
"winding = ";
2697 for (
int i = 0; i < winding.
size(); ++i) {
2698 std::cout << winding[i] <<
" ";
2700 std::cout <<
"\niv0=" << in_output_volume_0 <<
", iv1=" << in_output_volume_1 <<
"\n";
2701 std::cout <<
" remove=" << do_remove <<
", flip=" << do_flip <<
"\n";
2703 *r_do_flip = do_flip;
2708static void raycast_add_flipped(
Vector<Face *> &out_faces,
const Face &tri, IMeshArena *arena)
2712 Array<int> flipped_e_origs = {tri.edge_orig[2], tri.edge_orig[1], tri.edge_orig[0]};
2714 tri.is_intersect[2], tri.is_intersect[1], tri.is_intersect[0]};
2715 Face *flipped_f = arena->add_face(flipped_vs, tri.orig, flipped_e_origs, flipped_is_intersect);
2716 out_faces.
append(flipped_f);
2727static IMesh raycast_tris_boolean(
2728 const IMesh &tm, BoolOpType op,
int nshapes,
FunctionRef<
int(
int)> shape_fn, IMeshArena *arena)
2730 constexpr int dbg_level = 0;
2731 if (dbg_level > 0) {
2732 std::cout <<
"RAYCAST_TRIS_BOOLEAN\n";
2737 out_faces.
reserve(tm.face_size());
2739 tbb::spin_mutex mtx;
2741 const int grainsize = 256;
2743 Array<float> in_shape(nshapes, 0);
2744 Array<int> winding(nshapes, 0);
2745 for (int t : range) {
2746 Face &tri = *tm.face(t);
2747 int shape = shape_fn(tri.orig);
2748 if (dbg_level > 0) {
2749 std::cout <<
"process triangle " << t <<
" = " << &tri <<
"\n";
2750 std::cout <<
"shape = " << shape <<
"\n";
2752 test_tri_inside_shapes(tm, shape_fn, nshapes, t, tree, in_shape);
2753 for (int other_shape = 0; other_shape < nshapes; ++other_shape) {
2754 if (other_shape == shape) {
2764 bool need_high_confidence = (op == BoolOpType::Difference && shape != 0) ||
2765 op == BoolOpType::Intersect;
2766 bool inside = in_shape[other_shape] >= (need_high_confidence ? 0.5f : 0.1f);
2767 if (dbg_level > 0) {
2768 std::cout <<
"test point is " << (inside ?
"inside" :
"outside") <<
" other_shape "
2769 << other_shape <<
" val = " << in_shape[other_shape] <<
"\n";
2771 winding[other_shape] = inside;
2774 bool do_remove = raycast_test_remove(op, winding, shape, &do_flip);
2777 tbb::spin_mutex::scoped_lock lock(mtx);
2781 out_faces.append(&tri);
2784 raycast_add_flipped(out_faces, tri, arena);
2791 ans.set_faces(out_faces);
2799static IMesh raycast_patches_boolean(
const IMesh &tm,
2803 const PatchesInfo &pinfo,
2806 constexpr int dbg_level = 0;
2807 if (dbg_level > 0) {
2808 std::cout <<
"RAYCAST_PATCHES_BOOLEAN\n";
2813 out_faces.
reserve(tm.face_size());
2816 for (
int p : pinfo.index_range()) {
2817 const Patch &patch = pinfo.patch(p);
2820 int test_t_index = patch.tri(patch.tot_tri() / 2);
2821 Face &tri_test = *tm.face(test_t_index);
2823 int shape = shape_fn(tri_test.orig);
2824 if (dbg_level > 0) {
2825 std::cout <<
"process patch " << p <<
" = " << patch <<
"\n";
2826 std::cout <<
"test tri = " << test_t_index <<
" = " << &tri_test <<
"\n";
2827 std::cout <<
"shape = " << shape <<
"\n";
2832 test_tri_inside_shapes(tm, shape_fn, nshapes, test_t_index,
tree, in_shape);
2833 for (
int other_shape = 0; other_shape < nshapes; ++other_shape) {
2834 if (other_shape == shape) {
2837 bool need_high_confidence = (op == BoolOpType::Difference && shape != 0) ||
2838 op == BoolOpType::Intersect;
2839 bool inside = in_shape[other_shape] >= (need_high_confidence ? 0.5f : 0.1f);
2840 if (dbg_level > 0) {
2841 std::cout <<
"test point is " << (inside ?
"inside" :
"outside") <<
" other_shape "
2842 << other_shape <<
" val = " << in_shape[other_shape] <<
"\n";
2844 winding[other_shape] = inside;
2847 bool do_remove = raycast_test_remove(op, winding, shape, &do_flip);
2849 for (
int t : patch.tris()) {
2850 Face *f = tm.face(t);
2855 raycast_add_flipped(out_faces, *f, arena);
2862 ans.set_faces(out_faces);
2870static std::pair<int, int> find_tris_common_edge(
const Face &tri1,
const Face &tri2)
2872 for (
int i = 0; i < 3; ++i) {
2873 for (
int j = 0; j < 3; ++j) {
2874 if (tri1[(i + 1) % 3] == tri2[j] && tri1[i] == tri2[(j + 1) % 3]) {
2875 return std::pair<int, int>(i, j);
2879 return std::pair<int, int>(-1, -1);
2886 const Vert *v1 =
nullptr;
2887 const Vert *
v2 =
nullptr;
2890 int right_face = -1;
2893 bool dissolvable =
false;
2895 bool is_intersect =
false;
2897 MergeEdge() =
default;
2899 MergeEdge(
const Vert *va,
const Vert *vb)
2901 if (va->id < vb->id) {
2914 Vector<const Vert *> vert;
2922struct FaceMergeState {
2926 Vector<MergeFace> face;
2931 Vector<MergeEdge> edge;
2939static std::ostream &
operator<<(std::ostream &os,
const FaceMergeState &fms)
2942 for (
int f : fms.face.index_range()) {
2943 const MergeFace &mf = fms.face[f];
2944 std::cout << f <<
": orig=" << mf.orig <<
" verts ";
2945 for (
const Vert *
v : mf.vert) {
2946 std::cout <<
v <<
" ";
2949 std::cout <<
" edges " << mf.edge <<
"\n";
2950 std::cout <<
" merge_to = " << mf.merge_to <<
"\n";
2953 for (
int e : fms.edge.index_range()) {
2954 const MergeEdge &me = fms.edge[
e];
2955 std::cout <<
e <<
": (" << me.v1 <<
"," << me.v2 <<
") left=" << me.left_face
2956 <<
" right=" << me.right_face <<
" dis=" << me.dissolvable <<
" orig=" << me.orig
2957 <<
" is_int=" << me.is_intersect <<
"\n";
2972static void init_face_merge_state(FaceMergeState *fms,
2975 const double3 &
norm)
2977 constexpr int dbg_level = 0;
2979 fms->face.reserve(tris.
size() + 1);
2980 fms->edge.reserve(3 * tris.
size());
2981 fms->edge_map.reserve(3 * tris.
size());
2982 if (dbg_level > 0) {
2983 std::cout <<
"\nINIT_FACE_MERGE_STATE\n";
2987 const Face &tri = *tm.face(tris[t]);
2988 if (dbg_level > 0) {
2989 std::cout <<
"process tri = " << &tri <<
"\n";
2993 if (dbg_level > 0) {
2994 std::cout <<
"triangle has wrong orientation, skipping\n";
2998 mf.vert.append(tri[0]);
2999 mf.vert.append(tri[1]);
3000 mf.vert.append(tri[2]);
3002 int f = fms->face.append_and_get_index(mf);
3003 if (dbg_level > 1) {
3004 std::cout <<
"appended MergeFace for tri at f = " << f <<
"\n";
3006 for (
int i = 0; i < 3; ++i) {
3007 int inext = (i + 1) % 3;
3008 MergeEdge new_me(mf.vert[i], mf.vert[inext]);
3009 std::pair<int, int> canon_vs(new_me.v1->id, new_me.v2->id);
3010 int me_index = fms->edge_map.lookup_default(canon_vs, -1);
3011 if (dbg_level > 1) {
3012 std::cout <<
"new_me = canon_vs = " << new_me.v1 <<
", " << new_me.v2 <<
"\n";
3013 std::cout <<
"me_index lookup = " << me_index <<
"\n";
3015 if (me_index == -1) {
3016 double3 vec = new_me.v2->co - new_me.v1->co;
3018 new_me.orig = tri.edge_orig[i];
3019 new_me.is_intersect = tri.is_intersect[i];
3020 new_me.dissolvable = (new_me.orig == NO_INDEX && !new_me.is_intersect);
3021 fms->edge.append(new_me);
3022 me_index = fms->edge.size() - 1;
3023 fms->edge_map.add_new(canon_vs, me_index);
3024 if (dbg_level > 1) {
3025 std::cout <<
"added new me with me_index = " << me_index <<
"\n";
3026 std::cout <<
" len_squared = " << new_me.len_squared <<
" orig = " << new_me.orig
3027 <<
", is_intersect" << new_me.is_intersect
3028 <<
", dissolvable = " << new_me.dissolvable <<
"\n";
3031 MergeEdge &me = fms->edge[me_index];
3032 if (dbg_level > 1) {
3033 std::cout <<
"retrieved me at index " << me_index <<
":\n";
3034 std::cout <<
" v1 = " << me.v1 <<
" v2 = " << me.v2 <<
"\n";
3035 std::cout <<
" dis = " << me.dissolvable <<
" int = " << me.is_intersect <<
"\n";
3036 std::cout <<
" left_face = " << me.left_face <<
" right_face = " << me.right_face <<
"\n";
3038 if (me.dissolvable && tri.edge_orig[i] != NO_INDEX) {
3039 if (dbg_level > 1) {
3040 std::cout <<
"reassigning orig to " << tri.edge_orig[i] <<
", dissolvable = false\n";
3042 me.dissolvable =
false;
3043 me.orig = tri.edge_orig[i];
3045 if (me.dissolvable && tri.is_intersect[i]) {
3046 if (dbg_level > 1) {
3047 std::cout <<
"reassigning dissolvable = false, is_intersect = true\n";
3049 me.dissolvable =
false;
3050 me.is_intersect =
true;
3053 if (me.v1 == mf.vert[i]) {
3054 if (dbg_level > 1) {
3055 std::cout <<
"me.v1 == mf.vert[i] so set edge[" << me_index <<
"].left_face = " << f
3058 if (me.left_face != -1) {
3063 if (dbg_level > 1) {
3064 std::cout <<
"me.left_face was already occupied, so triangulation wasn't good\n";
3066 me.dissolvable =
false;
3069 fms->edge[me_index].left_face = f;
3073 if (dbg_level > 1) {
3074 std::cout <<
"me.v1 != mf.vert[i] so set edge[" << me_index <<
"].right_face = " << f
3077 if (me.right_face != -1) {
3079 if (dbg_level > 1) {
3080 std::cout <<
"me.right_face was already occupied, so triangulation wasn't good\n";
3082 me.dissolvable =
false;
3085 fms->edge[me_index].right_face = f;
3088 fms->face[f].edge.append(me_index);
3091 if (dbg_level > 0) {
3102static bool dissolve_leaves_valid_bmesh(FaceMergeState *fms,
3103 const MergeEdge &me,
3105 const MergeFace &mf_left,
3106 const MergeFace &mf_right)
3108 int a_edge_start = mf_left.edge.first_index_of_try(me_index);
3110 int alen = mf_left.vert.size();
3111 int blen = mf_right.vert.size();
3112 int b_left_face = me.right_face;
3115 for (
int a_e_index = (a_edge_start + 1) % alen; ok && a_e_index != a_edge_start;
3116 a_e_index = (a_e_index + 1) % alen)
3118 const MergeEdge &a_me_cur = fms->edge[mf_left.edge[a_e_index]];
3119 if (a_me_cur.right_face == b_left_face) {
3126 for (
int a_v_index = 0; ok && a_v_index < alen; ++a_v_index) {
3127 const Vert *a_v = mf_left.vert[a_v_index];
3128 if (!
ELEM(a_v, me.v1, me.v2)) {
3129 for (
int b_v_index = 0; b_v_index < blen; ++b_v_index) {
3130 const Vert *b_v = mf_right.vert[b_v_index];
3147static void splice_faces(
3148 FaceMergeState *fms, MergeEdge &me,
int me_index, MergeFace &mf_left, MergeFace &mf_right)
3150 int a_edge_start = mf_left.edge.first_index_of_try(me_index);
3151 int b_edge_start = mf_right.edge.first_index_of_try(me_index);
3152 BLI_assert(a_edge_start != -1 && b_edge_start != -1);
3153 int alen = mf_left.vert.size();
3154 int blen = mf_right.vert.size();
3157 splice_vert.
reserve(alen + blen - 2);
3158 splice_edge.
reserve(alen + blen - 2);
3160 while (ai < a_edge_start) {
3161 splice_vert.
append(mf_left.vert[ai]);
3162 splice_edge.
append(mf_left.edge[ai]);
3165 int bi = b_edge_start + 1;
3166 while (bi != b_edge_start) {
3169 if (bi == b_edge_start) {
3173 splice_vert.
append(mf_right.vert[bi]);
3174 splice_edge.
append(mf_right.edge[bi]);
3175 if (mf_right.vert[bi] == fms->edge[mf_right.edge[bi]].v1) {
3176 fms->edge[mf_right.edge[bi]].left_face = me.left_face;
3179 fms->edge[mf_right.edge[bi]].right_face = me.left_face;
3183 ai = a_edge_start + 1;
3185 splice_vert.
append(mf_left.vert[ai]);
3186 splice_edge.
append(mf_left.edge[ai]);
3189 mf_right.merge_to = me.left_face;
3190 mf_left.vert = splice_vert;
3191 mf_left.edge = splice_edge;
3203static void do_dissolve(FaceMergeState *fms)
3205 const int dbg_level = 0;
3206 if (dbg_level > 1) {
3207 std::cout <<
"\nDO_DISSOLVE\n";
3210 for (
int e : fms->edge.index_range()) {
3211 if (fms->edge[
e].dissolvable) {
3220 dissolve_edges.
begin(), dissolve_edges.
end(), [fms](
const int &a,
const int &
b) ->
bool {
3221 return (fms->edge[a].len_squared > fms->edge[b].len_squared);
3223 if (dbg_level > 0) {
3224 std::cout <<
"Sorted dissolvable edges: " << dissolve_edges <<
"\n";
3226 for (
int me_index : dissolve_edges) {
3227 MergeEdge &me = fms->edge[me_index];
3228 if (me.left_face == -1 || me.right_face == -1) {
3231 MergeFace &mf_left = fms->face[me.left_face];
3232 MergeFace &mf_right = fms->face[me.right_face];
3233 if (!dissolve_leaves_valid_bmesh(fms, me, me_index, mf_left, mf_right)) {
3236 if (dbg_level > 0) {
3237 std::cout <<
"Removing edge " << me_index <<
"\n";
3239 splice_faces(fms, me, me_index, mf_left, mf_right);
3240 if (dbg_level > 1) {
3241 std::cout <<
"state after removal:\n";
3259 const IMesh &imesh_in,
3262 constexpr int dbg_level = 0;
3263 if (dbg_level > 0) {
3264 std::cout <<
"merge_tris_for_face\n";
3265 std::cout <<
"tris: " << tris <<
"\n";
3268 if (tris.
size() <= 1) {
3269 if (tris.
size() == 1) {
3270 ans.
append(tm.face(tris[0]));
3275 double3 first_tri_normal = tm.face(tris[0])->plane->norm;
3276 double3 second_tri_normal = tm.face(tris[1])->plane->norm;
3277 if (tris.
size() == 2 &&
math::dot(first_tri_normal, second_tri_normal) > 0.0) {
3280 Face &tri1 = *tm.face(tris[0]);
3281 Face &tri2 = *tm.face(tris[1]);
3282 Face *in_face = imesh_in.face(tri1.orig);
3283 if (in_face->size() == 4) {
3284 std::pair<int, int> estarts = find_tris_common_edge(tri1, tri2);
3285 if (estarts.first != -1 && tri1.edge_orig[estarts.first] == NO_INDEX) {
3286 if (dbg_level > 0) {
3287 std::cout <<
"try recovering orig quad case\n";
3288 std::cout <<
"tri1 = " << &tri1 <<
"\n";
3289 std::cout <<
"tri1 = " << &tri2 <<
"\n";
3291 int i0 = estarts.first;
3292 int i1 = (i0 + 1) % 3;
3293 int i2 = (i0 + 2) % 3;
3294 int j2 = (estarts.second + 2) % 3;
3295 Face tryface({tri1[i1], tri1[i2], tri1[i0], tri2[j2]}, -1, -1, {}, {});
3296 if (tryface.cyclic_equal(*in_face)) {
3297 if (dbg_level > 0) {
3298 std::cout <<
"inface = " << in_face <<
"\n";
3299 std::cout <<
"quad recovery worked\n";
3311 double3 first_tri_normal_rev = -first_tri_normal;
3312 for (
const double3 &
norm : {first_tri_normal, first_tri_normal_rev}) {
3314 init_face_merge_state(&fms, tris, tm,
norm);
3316 if (dbg_level > 0) {
3317 std::cout <<
"faces in merged result:\n";
3319 for (
const MergeFace &mf : fms.face) {
3320 if (mf.merge_to == -1) {
3323 for (
int i : mf.edge.index_range()) {
3324 e_orig[i] = fms.edge[mf.edge[i]].orig;
3325 is_intersect[i] = fms.edge[mf.edge[i]].is_intersect;
3327 Face *facep = arena->add_face(mf.vert, mf.orig, e_orig, is_intersect);
3329 if (dbg_level > 0) {
3330 std::cout <<
" " << facep <<
"\n";
3338static bool approx_in_line(
const double3 &a,
const double3 &
b,
const double3 &c)
3343 return fabs(cos_ang - 1.0) < 1
e-4;
3352static Array<bool> find_dissolve_verts(IMesh &imesh_out,
int *r_count_dissolve)
3354 imesh_out.populate_vert();
3357 for (
int v_index : imesh_out.vert_index_range()) {
3358 const Vert &vert = *imesh_out.vert(v_index);
3359 dissolve[v_index] = (vert.orig == NO_INDEX);
3365 imesh_out.vert_size(), std::pair<const Vert *, const Vert *>(
nullptr,
nullptr));
3366 for (
int f : imesh_out.face_index_range()) {
3367 const Face &face = *imesh_out.face(f);
3368 for (
int i : face.index_range()) {
3369 const Vert *
v = face[i];
3370 int v_index = imesh_out.lookup_vert(
v);
3372 if (dissolve[v_index]) {
3373 const Vert *n1 = face[face.next_pos(i)];
3374 const Vert *n2 = face[face.prev_pos(i)];
3375 const Vert *f_n1 = neighbors[v_index].first;
3376 const Vert *f_n2 = neighbors[v_index].second;
3377 if (f_n1 !=
nullptr) {
3379 if (!((n1 == f_n2 && n2 == f_n1) || (n1 == f_n1 && n2 == f_n2))) {
3381 dissolve[v_index] =
false;
3386 neighbors[v_index] = std::pair<const Vert *, const Vert *>(n1, n2);
3392 for (
int v_out : imesh_out.vert_index_range()) {
3393 if (dissolve[v_out]) {
3394 dissolve[v_out] =
false;
3395 const std::pair<const Vert *, const Vert *> &nbrs = neighbors[v_out];
3396 if (nbrs.first !=
nullptr) {
3398 const Vert *v_v_out = imesh_out.vert(v_out);
3399 if (approx_in_line(nbrs.first->co, v_v_out->co, nbrs.second->co)) {
3400 dissolve[v_out] =
true;
3406 if (r_count_dissolve !=
nullptr) {
3407 *r_count_dissolve =
count;
3417static void dissolve_verts(IMesh *imesh,
const Array<bool> dissolve, IMeshArena *arena)
3419 constexpr int inline_face_size = 100;
3421 bool any_faces_erased =
false;
3422 for (
int f : imesh->face_index_range()) {
3423 const Face &face = *imesh->face(f);
3424 face_pos_erase.
clear();
3426 for (
const Vert *
v : face) {
3427 int v_index = imesh->lookup_vert(
v);
3429 if (dissolve[v_index]) {
3430 face_pos_erase.
append(
true);
3434 face_pos_erase.
append(
false);
3437 if (erase_num > 0) {
3438 any_faces_erased |= imesh->erase_face_positions(f, face_pos_erase, arena);
3441 imesh->set_dirty_verts();
3442 if (any_faces_erased) {
3443 imesh->remove_null_faces();
3458static IMesh polymesh_from_trimesh_with_dissolve(
const IMesh &tm_out,
3459 const IMesh &imesh_in,
3462 const int dbg_level = 0;
3463 if (dbg_level > 1) {
3464 std::cout <<
"\nPOLYMESH_FROM_TRIMESH_WITH_DISSOLVE\n";
3467 const int grainsize = 1024;
3468 threading::parallel_for(tm_out.face_index_range(), grainsize, [&](
IndexRange range) {
3469 for (int i : range) {
3470 Face *tri = tm_out.face(i);
3471 tri->populate_plane(false);
3477 int tot_in_face = imesh_in.face_size();
3479 for (
int t : tm_out.face_index_range()) {
3480 const Face &tri = *tm_out.face(t);
3481 int in_face = tri.orig;
3482 face_output_tris[in_face].append(t);
3484 if (dbg_level > 1) {
3485 std::cout <<
"face_output_tris:\n";
3486 for (
int f : face_output_tris.index_range()) {
3487 std::cout << f <<
": " << face_output_tris[f] <<
"\n";
3495 int tot_out_face = 0;
3496 for (
int in_f : imesh_in.face_index_range()) {
3497 if (dbg_level > 1) {
3498 std::cout <<
"merge tris for face " << in_f <<
"\n";
3500 int out_tris_for_face_num = face_output_tris.size();
3501 if (out_tris_for_face_num == 0) {
3504 face_output_face[in_f] = merge_tris_for_face(face_output_tris[in_f], tm_out, imesh_in, arena);
3505 tot_out_face += face_output_face[in_f].size();
3508 int out_f_index = 0;
3509 for (
int in_f : imesh_in.face_index_range()) {
3511 if (f_faces.
size() > 0) {
3512 std::copy(f_faces.
begin(), f_faces.
end(), &face[out_f_index]);
3513 out_f_index += f_faces.
size();
3516 IMesh imesh_out(face);
3521 Array<bool> v_dissolve = find_dissolve_verts(imesh_out, &count_dissolve);
3522 if (count_dissolve > 0) {
3523 dissolve_verts(&imesh_out, v_dissolve, arena);
3525 if (dbg_level > 1) {
3526 write_obj_mesh(imesh_out,
"boolean_post_dissolve");
3532IMesh boolean_trimesh(IMesh &tm_in,
3540 constexpr int dbg_level = 0;
3541 if (dbg_level > 0) {
3542 std::cout <<
"BOOLEAN of " << nshapes <<
" operand" << (nshapes == 1 ?
"" :
"s")
3543 <<
" op=" << bool_optype_name(op) <<
"\n";
3544 if (dbg_level > 1) {
3545 tm_in.populate_vert();
3546 std::cout <<
"boolean_trimesh input:\n" << tm_in;
3547 write_obj_mesh(tm_in,
"boolean_in");
3550 if (tm_in.face_size() == 0) {
3551 return IMesh(tm_in);
3555 std::cout <<
" boolean_trimesh, timing begins\n";
3558 IMesh tm_si = trimesh_nary_intersect(tm_in, nshapes, shape_fn, use_self, arena);
3559 if (dbg_level > 1) {
3560 write_obj_mesh(tm_si,
"boolean_tm_si");
3561 std::cout <<
"\nboolean_tm_input after intersection:\n" << tm_si;
3565 std::cout <<
" intersected, time = " << intersect_time - start_time <<
"\n";
3569 if (tm_si.face_size() == 0 || op == BoolOpType::None) {
3572 auto si_shape_fn = [shape_fn, tm_si](
int t) {
return shape_fn(tm_si.face(t)->orig); };
3573 TriMeshTopology tm_si_topo(tm_si);
3576 std::cout <<
" topology built, time = " << topo_time - intersect_time <<
"\n";
3578 bool pwn = is_pwn(tm_si, tm_si_topo);
3581 std::cout <<
" pwn checked, time = " << pwn_time - topo_time <<
"\n";
3585 if (dbg_level > 0) {
3586 std::cout <<
"Input is not PWN, using raycast method\n";
3588 if (hole_tolerant) {
3589 tm_out = raycast_tris_boolean(tm_si, op, nshapes, shape_fn, arena);
3592 PatchesInfo pinfo = find_patches(tm_si, tm_si_topo);
3593 tm_out = raycast_patches_boolean(tm_si, op, nshapes, shape_fn, pinfo, arena);
3597 std::cout <<
" raycast_boolean done, time = " << raycast_time - pwn_time <<
"\n";
3601 PatchesInfo pinfo = find_patches(tm_si, tm_si_topo);
3604 std::cout <<
" patches found, time = " << patch_time - pwn_time <<
"\n";
3606 CellsInfo cinfo = find_cells(tm_si, tm_si_topo, pinfo);
3607 if (dbg_level > 0) {
3608 std::cout <<
"Input is PWN\n";
3612 std::cout <<
" cells found, time = " << cell_time - pwn_time <<
"\n";
3614 finish_patch_cell_graph(tm_si, cinfo, pinfo, tm_si_topo, arena);
3617 std::cout <<
" finished patch-cell graph, time = " << finish_pc_time - cell_time <<
"\n";
3619 bool pc_ok = patch_cell_graph_ok(cinfo, pinfo);
3622 std::cout <<
"Something funny about input or a bug in boolean\n";
3623 return IMesh(tm_in);
3625 cinfo.init_windings(nshapes);
3626 int c_ambient = find_ambient_cell(tm_si,
nullptr, tm_si_topo, pinfo, arena);
3629 std::cout <<
" ambient cell found, time = " << amb_time - finish_pc_time <<
"\n";
3631 if (c_ambient == NO_INDEX) {
3633 std::cout <<
"Could not find an ambient cell; input not valid?\n";
3634 return IMesh(tm_si);
3636 propagate_windings_and_in_output_volume(pinfo, cinfo, c_ambient, op, nshapes, si_shape_fn);
3639 std::cout <<
" windings propagated, time = " << propagate_time - amb_time <<
"\n";
3641 tm_out = extract_from_in_output_volume_diffs(tm_si, pinfo, cinfo, arena);
3644 std::cout <<
" extracted, time = " << extract_time - propagate_time <<
"\n";
3646 if (dbg_level > 0) {
3648 TriMeshTopology tm_out_topo(tm_out);
3649 if (!is_pwn(tm_out, tm_out_topo)) {
3650 std::cout <<
"OUTPUT IS NOT PWN!\n";
3654 if (dbg_level > 1) {
3655 write_obj_mesh(tm_out,
"boolean_tm_output");
3656 std::cout <<
"boolean tm output:\n" << tm_out;
3660 std::cout <<
" boolean_trimesh done, total time = " << end_time - start_time <<
"\n";
3665static void dump_test_spec(IMesh &imesh)
3667 std::cout <<
"test spec = " << imesh.vert_size() <<
" " << imesh.face_size() <<
"\n";
3668 for (
const Vert *
v : imesh.vertices()) {
3669 std::cout <<
v->co_exact[0] <<
" " <<
v->co_exact[1] <<
" " <<
v->co_exact[2] <<
" # "
3670 <<
v->co[0] <<
" " <<
v->co[1] <<
" " <<
v->co[2] <<
"\n";
3672 for (
const Face *f : imesh.faces()) {
3673 for (
const Vert *fv : *f) {
3674 std::cout << imesh.lookup_vert(fv) <<
" ";
3680IMesh boolean_mesh(IMesh &imesh,
3686 IMesh *imesh_triangulated,
3689 constexpr int dbg_level = 0;
3690 if (dbg_level > 0) {
3691 std::cout <<
"\nBOOLEAN_MESH\n"
3692 << nshapes <<
" operand" << (nshapes == 1 ?
"" :
"s")
3693 <<
" op=" << bool_optype_name(op) <<
"\n";
3694 if (dbg_level > 1) {
3695 write_obj_mesh(imesh,
"boolean_mesh_in");
3697 if (dbg_level > 2) {
3698 dump_test_spec(imesh);
3702 IMesh *tm_in = imesh_triangulated;
3703 IMesh our_triangulation;
3706 std::cout <<
"boolean_mesh, timing begins\n";
3708 if (tm_in ==
nullptr) {
3709 our_triangulation = triangulate_polymesh(imesh, arena);
3710 tm_in = &our_triangulation;
3714 std::cout <<
"triangulated, time = " << tri_time - start_time <<
"\n";
3716 if (dbg_level > 1) {
3717 write_obj_mesh(*tm_in,
"boolean_tm_in");
3719 IMesh tm_out = boolean_trimesh(*tm_in, op, nshapes, shape_fn, use_self, hole_tolerant, arena);
3722 std::cout <<
"boolean_trimesh done, time = " << bool_tri_time - tri_time <<
"\n";
3724 if (dbg_level > 1) {
3725 std::cout <<
"bool_trimesh_output:\n" << tm_out;
3726 write_obj_mesh(tm_out,
"bool_trimesh_output");
3728 IMesh ans = polymesh_from_trimesh_with_dissolve(tm_out, imesh, arena);
3731 std::cout <<
"polymesh from dissolving, time = " << dissolve_time - bool_tri_time <<
"\n";
3733 if (dbg_level > 0) {
3734 std::cout <<
"boolean_mesh output:\n" << ans;
3735 if (dbg_level > 2) {
3736 ans.populate_vert();
3737 dump_test_spec(ans);
3742 std::cout <<
"boolean_mesh done, total time = " << end_time - start_time <<
"\n";
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])
const char * BLI_getenv(const char *env) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
double BLI_time_now_seconds(void)
#define UNUSED_VARS_NDEBUG(...)
typedef double(DMatrix)[4][4]
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Map
bool operator==(const AssetWeakReference &a, const AssetWeakReference &b)
int pad[32 - sizeof(int)]
ATTR_WARN_UNUSED_RESULT const BMVert * v2
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
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
const Value & lookup(const Key &key) const
Value lookup_default(const Key &key, const Value &default_value) const
ValueIterator values() 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)
local_group_size(16, 16) .push_constant(Type b
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
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)
ThreadQueue * queue
all scheduled work for the cpu
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))
int orient3d(const double3 &a, const double3 &b, const double3 &c, const double3 &d)
VecBase< double, 3 > double3
uint64_t get_default_hash(const T &v)
static void init(bNodeTree *, bNode *node)
unsigned __int64 uint64_t
ccl_device_inline int abs(int x)
std::ostream & operator<<(std::ostream &stream, bUUID uuid)