diff options
-rw-r--r-- | source/blender/blenkernel/BKE_mesh_boolean_convert.h | 21 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/mesh_boolean_convert.cc | 130 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_nodetree.c | 2 | ||||
-rw-r--r-- | source/blender/modifiers/intern/MOD_boolean.c | 1 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_boolean.cc | 164 |
5 files changed, 165 insertions, 153 deletions
diff --git a/source/blender/blenkernel/BKE_mesh_boolean_convert.h b/source/blender/blenkernel/BKE_mesh_boolean_convert.h index c126d714542..7e285d3676e 100644 --- a/source/blender/blenkernel/BKE_mesh_boolean_convert.h +++ b/source/blender/blenkernel/BKE_mesh_boolean_convert.h @@ -29,6 +29,7 @@ extern "C" { Mesh *BKE_mesh_boolean(const Mesh **meshes, const float (*obmats[])[4][4], + const float (*target_transform)[4][4], const short **material_remaps, const int meshes_len, const bool use_self, @@ -38,3 +39,23 @@ Mesh *BKE_mesh_boolean(const Mesh **meshes, #ifdef __cplusplus } #endif + +#ifdef __cplusplus + +# include "BLI_float4x4.hh" +# include "BLI_mesh_boolean.hh" +# include "BLI_span.hh" + +namespace blender::meshintersect { + +Mesh *direct_mesh_boolean(blender::Span<const Mesh *> meshes, + blender::Span<const float4x4 *> obmats, + const float4x4 &target_transform, + blender::Span<const short *> material_remaps, + const bool use_self, + const bool hole_tolerant, + const int boolean_mode); + +} // namespace blender::meshintersect + +#endif diff --git a/source/blender/blenkernel/intern/mesh_boolean_convert.cc b/source/blender/blenkernel/intern/mesh_boolean_convert.cc index 824f791d400..de9ad00ebde 100644 --- a/source/blender/blenkernel/intern/mesh_boolean_convert.cc +++ b/source/blender/blenkernel/intern/mesh_boolean_convert.cc @@ -51,8 +51,9 @@ constexpr int estimated_max_facelen = 100; /* Used for initial size of some Vect * so this is a hack to clean up such matrices. * Would be better to change the transformation code itself. */ -static void clean_obmat(float4x4 &cleaned, const float4x4 &mat) +static float4x4 clean_obmat(const float4x4 &mat) { + float4x4 cleaned; const float fuzz = 1e-6f; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { @@ -69,6 +70,7 @@ static void clean_obmat(float4x4 &cleaned, const float4x4 &mat) cleaned.values[i][j] = f; } } + return cleaned; } /* `MeshesToIMeshInfo` keeps track of information used when combining a number @@ -92,7 +94,7 @@ class MeshesToIMeshInfo { Array<Face *> mesh_to_imesh_face; /* Transformation matrix to transform a coordinate in the corresponding * Mesh to the local space of the first Mesh. */ - Array<float4x4> to_obj0; + Array<float4x4> to_target_transform; /* For each input mesh, how to remap the material slot numbers to * the material slots in the first mesh. */ Array<const short *> material_remaps; @@ -242,6 +244,7 @@ const MEdge *MeshesToIMeshInfo::input_medge_for_orig_index(int orig_index, static IMesh meshes_to_imesh(Span<const Mesh *> meshes, Span<const float4x4 *> obmats, Span<const short *> material_remaps, + const float4x4 &target_transform, IMeshArena &arena, MeshesToIMeshInfo *r_info) { @@ -270,7 +273,7 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, r_info->mesh_vert_offset = Array<int>(nmeshes); r_info->mesh_edge_offset = Array<int>(nmeshes); r_info->mesh_poly_offset = Array<int>(nmeshes); - r_info->to_obj0 = Array<float4x4>(nmeshes); + r_info->to_target_transform = Array<float4x4>(nmeshes); r_info->material_remaps = Array<const short *>(nmeshes); int v = 0; int e = 0; @@ -283,19 +286,10 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, Vector<int, estimated_max_facelen> face_edge_orig; /* To convert the coordinates of objects 1, 2, etc. into the local space - * of object 0, we multiply each object's `obmat` by the inverse of - * object 0's `obmat`. Exact Boolean works better if these matrices - * are 'cleaned' -- see the comment for the `clean_obmat` function, above. */ - float4x4 obj0_mat; - float4x4 inv_obj0_mat; - if (obmats[0] == nullptr) { - unit_m4(obj0_mat.values); - unit_m4(inv_obj0_mat.values); - } - else { - clean_obmat(obj0_mat, *obmats[0]); - invert_m4_m4(inv_obj0_mat.values, obj0_mat.values); - } + * of the target. We multiply each object's `obmat` by the inverse of the + * target matrix. Exact Boolean works better if these matrices are 'cleaned' + * -- see the comment for the `clean_obmat` function, above. */ + const float4x4 inv_target_mat = clean_obmat(target_transform).inverted(); /* For each input `Mesh`, make `Vert`s and `Face`s for the corresponding * `MVert`s and `MPoly`s, and keep track of the original indices (using the @@ -303,45 +297,41 @@ static IMesh meshes_to_imesh(Span<const Mesh *> meshes, * When making `Face`s, we also put in the original indices for `MEdge`s that * make up the `MPoly`s using the same scheme. */ for (int mi : meshes.index_range()) { - float4x4 objn_to_obj0_mat; const Mesh *me = meshes[mi]; - if (mi == 0) { - r_info->mesh_vert_offset[mi] = 0; - r_info->mesh_edge_offset[mi] = 0; - r_info->mesh_poly_offset[mi] = 0; - unit_m4(r_info->to_obj0[0].values); - r_info->material_remaps[0] = nullptr; + r_info->mesh_vert_offset[mi] = v; + r_info->mesh_edge_offset[mi] = e; + r_info->mesh_poly_offset[mi] = f; + /* Get matrix that transforms a coordinate in objects[mi]'s local space + * to the target space space.*/ + const float4x4 objn_mat = (obmats[mi] == nullptr) ? float4x4::identity() : + clean_obmat(*obmats[mi]); + r_info->to_target_transform[mi] = inv_target_mat * objn_mat; + + if (mi < material_remaps.size() && mi != 0) { + r_info->material_remaps[mi] = material_remaps[mi]; } else { - r_info->mesh_vert_offset[mi] = v; - r_info->mesh_edge_offset[mi] = e; - r_info->mesh_poly_offset[mi] = f; - /* Get matrix that transforms a coordinate in objects[mi]'s local space - * to object[0]'s local space.*/ - float4x4 objn_mat; - if (obmats[mi] == nullptr) { - unit_m4(objn_mat.values); - } - else { - clean_obmat(objn_mat, *obmats[mi]); - } - objn_to_obj0_mat = inv_obj0_mat * objn_mat; - r_info->to_obj0[mi] = objn_to_obj0_mat; - if (mi < material_remaps.size()) { - r_info->material_remaps[mi] = material_remaps[mi]; - } - else { - r_info->material_remaps[mi] = nullptr; + r_info->material_remaps[mi] = nullptr; + } + + /* Skip the matrix multiplication for each point when there is no transform for a mesh, + * for example when the first mesh is already in the target space. (Note the logic directly + * above, which uses an identity matrix with a null input transform). */ + if (obmats[mi] == nullptr) { + for (const MVert &vert : Span(me->mvert, me->totvert)) { + const float3 co = float3(vert.co); + r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v); + ++v; } } - for (int vi = 0; vi < me->totvert; ++vi) { - float3 co = me->mvert[vi].co; - if (mi > 0) { - co = objn_to_obj0_mat * co; + else { + for (const MVert &vert : Span(me->mvert, me->totvert)) { + const float3 co = r_info->to_target_transform[mi] * float3(vert.co); + r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v); + ++v; } - r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(mpq3(co.x, co.y, co.z), v); - ++v; } + for (const MPoly &poly : Span(me->mpoly, me->totpoly)) { int flen = poly.totloop; face_vert.clear(); @@ -596,7 +586,7 @@ static void copy_or_interp_loop_attributes(Mesh *dest_mesh, cos_2d = (float(*)[2])BLI_array_alloca(cos_2d, orig_mp->totloop); weights = Array<float>(orig_mp->totloop); src_blocks_ofs = Array<const void *>(orig_mp->totloop); - get_poly2d_cos(orig_me, orig_mp, cos_2d, mim.to_obj0[orig_me_index], axis_mat); + get_poly2d_cos(orig_me, orig_mp, cos_2d, mim.to_target_transform[orig_me_index], axis_mat); } CustomData *target_cd = &dest_mesh->ldata; for (int i = 0; i < mp->totloop; ++i) { @@ -778,17 +768,21 @@ static Mesh *imesh_to_mesh(IMesh *im, MeshesToIMeshInfo &mim) return result; } +#endif // WITH_GMP + /** * Do Exact Boolean directly, without a round trip through #BMesh. * The Mesh operands are in `meshes`, with corresponding transforms in in `obmats`. */ -static Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, - Span<const float4x4 *> obmats, - Span<const short *> material_remaps, - const bool use_self, - const bool hole_tolerant, - const BoolOpType boolean_mode) +Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, + Span<const float4x4 *> obmats, + const float4x4 &target_transform, + Span<const short *> material_remaps, + const bool use_self, + const bool hole_tolerant, + const int boolean_mode) { +#ifdef WITH_GMP const int dbg_level = 0; BLI_assert(meshes.size() == obmats.size()); const int meshes_len = meshes.size(); @@ -800,7 +794,7 @@ static Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, } MeshesToIMeshInfo mim; IMeshArena arena; - IMesh m_in = meshes_to_imesh(meshes, obmats, material_remaps, arena, &mim); + IMesh m_in = meshes_to_imesh(meshes, obmats, material_remaps, target_transform, arena, &mim); std::function<int(int)> shape_fn = [&mim](int f) { for (int mi = 0; mi < mim.mesh_poly_offset.size() - 1; ++mi) { if (f < mim.mesh_poly_offset[mi + 1]) { @@ -809,17 +803,27 @@ static Mesh *direct_mesh_boolean(Span<const Mesh *> meshes, } return static_cast<int>(mim.mesh_poly_offset.size()) - 1; }; - IMesh m_out = boolean_mesh( - m_in, boolean_mode, meshes_len, shape_fn, use_self, hole_tolerant, nullptr, &arena); + IMesh m_out = boolean_mesh(m_in, + static_cast<BoolOpType>(boolean_mode), + meshes_len, + shape_fn, + use_self, + hole_tolerant, + nullptr, + &arena); if (dbg_level > 1) { std::cout << m_out; write_obj_mesh(m_out, "m_out"); } return imesh_to_mesh(&m_out, mim); +#else // WITH_GMP + UNUSED_VARS( + meshes, obmats, material_remaps, target_transform, use_self, hole_tolerant, boolean_mode); + return nullptr; +#endif // WITH_GMP } -#endif // WITH_GMP } // namespace blender::meshintersect extern "C" { @@ -835,6 +839,7 @@ extern "C" { * for the pointers to be nullptr, meaning the transformation is the identity. */ Mesh *BKE_mesh_boolean(const Mesh **meshes, const float (*obmats[])[4][4], + const float (*target_transform)[4][4], const short **material_remaps, const int meshes_len, const bool use_self, @@ -842,25 +847,28 @@ Mesh *BKE_mesh_boolean(const Mesh **meshes, const int boolean_mode) { const blender::float4x4 **transforms = (const blender::float4x4 **)obmats; + const blender::float4x4 &target_float4x4 = *(const blender::float4x4 *)target_transform; return blender::meshintersect::direct_mesh_boolean( blender::Span(meshes, meshes_len), blender::Span(transforms, meshes_len), + target_float4x4, blender::Span(material_remaps, material_remaps ? meshes_len : 0), use_self, hole_tolerant, - static_cast<blender::meshintersect::BoolOpType>(boolean_mode)); + boolean_mode); } #else Mesh *BKE_mesh_boolean(const Mesh **UNUSED(meshes), const float (*obmats[])[4][4], + const float (*target_transform)[4][4], const short **UNUSED(material_remaps), const int UNUSED(meshes_len), const bool UNUSED(use_self), const bool UNUSED(hole_tolerant), const int UNUSED(boolean_mode)) { - UNUSED_VARS(obmats); + UNUSED_VARS(obmats, target_transform); return NULL; } diff --git a/source/blender/makesrna/intern/rna_nodetree.c b/source/blender/makesrna/intern/rna_nodetree.c index 277cfada44d..1ef0658f889 100644 --- a/source/blender/makesrna/intern/rna_nodetree.c +++ b/source/blender/makesrna/intern/rna_nodetree.c @@ -8785,7 +8785,7 @@ static void def_geo_boolean(StructRNA *srna) RNA_def_property_enum_items(prop, rna_node_geometry_boolean_method_items); RNA_def_property_enum_default(prop, GEO_NODE_BOOLEAN_INTERSECT); RNA_def_property_ui_text(prop, "Operation", ""); - RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_update"); + RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update"); } static void def_geo_triangulate(StructRNA *srna) diff --git a/source/blender/modifiers/intern/MOD_boolean.c b/source/blender/modifiers/intern/MOD_boolean.c index ffb40bea9ba..7d6b654cecb 100644 --- a/source/blender/modifiers/intern/MOD_boolean.c +++ b/source/blender/modifiers/intern/MOD_boolean.c @@ -694,6 +694,7 @@ static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, const bool hole_tolerant = (bmd->flag & eBooleanModifierFlag_HoleTolerant) != 0; result = BKE_mesh_boolean((const Mesh **)meshes, (const float(**)[4][4])obmats, + &ctx->object->obmat, (const short **)material_remaps, BLI_array_len(meshes), use_self, diff --git a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc index 9f331190420..c3017dc2610 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_boolean.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_boolean.cc @@ -14,29 +14,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "MEM_guardedalloc.h" - -#include "BLI_alloca.h" -#include "BLI_math_matrix.h" - #include "DNA_mesh_types.h" -#include "DNA_modifier_types.h" -#include "RNA_enum_types.h" +#include "BKE_mesh.h" +#include "BKE_mesh_boolean_convert.h" #include "UI_interface.h" #include "UI_resources.h" -#include "BKE_mesh.h" - -#include "bmesh.h" -#include "tools/bmesh_boolean.h" - #include "node_geometry_util.hh" static bNodeSocketTemplate geo_node_boolean_in[] = { {SOCK_GEOMETRY, N_("Geometry 1")}, - {SOCK_GEOMETRY, N_("Geometry 2")}, + {SOCK_GEOMETRY, + N_("Geometry 2"), + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + 0.0f, + PROP_NONE, + SOCK_MULTI_INPUT}, + {SOCK_BOOLEAN, N_("Self Intersection")}, + {SOCK_BOOLEAN, N_("Hole Tolerant")}, {-1, ""}, }; @@ -50,109 +51,88 @@ static void geo_node_boolean_layout(uiLayout *layout, bContext *UNUSED(C), Point uiItemR(layout, ptr, "operation", 0, "", ICON_NONE); } -static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) -{ - return BM_elem_flag_test(f, BM_ELEM_DRAW) ? 1 : 0; -} - -static Mesh *mesh_boolean_calc(const Mesh *mesh_a, const Mesh *mesh_b, int boolean_mode) +static void geo_node_boolean_update(bNodeTree *UNUSED(ntree), bNode *node) { - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh_a, mesh_b); - - BMesh *bm; - { - struct BMeshCreateParams bmesh_create_params = {0}; - bmesh_create_params.use_toolflags = false; - bm = BM_mesh_create(&allocsize, &bmesh_create_params); - } - - { - struct BMeshFromMeshParams bmesh_from_mesh_params = {0}; - bmesh_from_mesh_params.calc_face_normal = true; - BM_mesh_bm_from_me(bm, mesh_b, &bmesh_from_mesh_params); - BM_mesh_bm_from_me(bm, mesh_a, &bmesh_from_mesh_params); - } - - const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); - int tottri; - BMLoop *(*looptris)[3] = (BMLoop * - (*)[3])(MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__)); - BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); - - const int i_faces_end = mesh_b->totpoly; - - /* We need face normals because of 'BM_face_split_edgenet' - * we could calculate on the fly too (before calling split). */ - - int i = 0; - BMIter iter; - BMFace *bm_face; - BM_ITER_MESH (bm_face, &iter, bm, BM_FACES_OF_MESH) { - normalize_v3(bm_face->no); + GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)node->custom1; - /* Temp tag to test which side split faces are from. */ - BM_elem_flag_enable(bm_face, BM_ELEM_DRAW); + bNodeSocket *geometry_1_socket = (bNodeSocket *)node->inputs.first; + bNodeSocket *geometry_2_socket = geometry_1_socket->next; - i++; - if (i == i_faces_end) { + switch (operation) { + case GEO_NODE_BOOLEAN_INTERSECT: + case GEO_NODE_BOOLEAN_UNION: + nodeSetSocketAvailability(geometry_1_socket, false); + nodeSetSocketAvailability(geometry_2_socket, true); + node_sock_label(geometry_2_socket, N_("Geometry")); + break; + case GEO_NODE_BOOLEAN_DIFFERENCE: + nodeSetSocketAvailability(geometry_1_socket, true); + nodeSetSocketAvailability(geometry_2_socket, true); + node_sock_label(geometry_2_socket, N_("Geometry 2")); break; - } } +} - BM_mesh_boolean( - bm, looptris, tottri, bm_face_isect_pair, nullptr, 2, false, false, false, boolean_mode); - - Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh_a); - BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - MEM_freeN(looptris); - - return result; +static void geo_node_boolean_init(bNodeTree *UNUSED(tree), bNode *node) +{ + node->custom1 = GEO_NODE_BOOLEAN_DIFFERENCE; } namespace blender::nodes { + static void geo_node_boolean_exec(GeoNodeExecParams params) { - GeometrySet geometry_set_in_a = params.extract_input<GeometrySet>("Geometry 1"); - GeometrySet geometry_set_in_b = params.extract_input<GeometrySet>("Geometry 2"); - GeometrySet geometry_set_out; - GeometryNodeBooleanOperation operation = (GeometryNodeBooleanOperation)params.node().custom1; + const bool use_self = params.get_input<bool>("Self Intersection"); + const bool hole_tolerant = params.get_input<bool>("Hole Tolerant"); + if (operation < 0 || operation > 2) { BLI_assert(false); - params.set_output("Geometry", std::move(geometry_set_out)); + params.set_output("Geometry", std::move(GeometrySet())); return; } - /* TODO: Boolean does support an input of multiple meshes. Currently they must all be - * converted to BMesh before running the operation though. D9957 will make it possible - * to use the mesh structure directly. */ - geometry_set_in_a = geometry_set_realize_instances(geometry_set_in_a); - geometry_set_in_b = geometry_set_realize_instances(geometry_set_in_b); + Vector<const Mesh *> meshes; + Vector<const float4x4 *> transforms; + + GeometrySet set_a; + if (operation == GEO_NODE_BOOLEAN_DIFFERENCE) { + set_a = params.extract_input<GeometrySet>("Geometry 1"); + /* Note that it technically wouldn't be necessary to realize the instances for the first + * geometry input, but the boolean code expects the first shape for the difference operation + * to be a single mesh. */ + set_a = geometry_set_realize_instances(set_a); + const Mesh *mesh_in_a = set_a.get_mesh_for_read(); + if (mesh_in_a != nullptr) { + meshes.append(mesh_in_a); + transforms.append(nullptr); + } + } - const Mesh *mesh_in_a = geometry_set_in_a.get_mesh_for_read(); - const Mesh *mesh_in_b = geometry_set_in_b.get_mesh_for_read(); + /* The instance transform matrices are owned by the instance group, so we have to + * keep all of them around for use during the boolean operation. */ + Vector<bke::GeometryInstanceGroup> set_groups; + Vector<GeometrySet> geometry_sets = params.extract_multi_input<GeometrySet>("Geometry 2"); + for (const GeometrySet &geometry_set : geometry_sets) { + bke::geometry_set_gather_instances(geometry_set, set_groups); + } - if (mesh_in_a == nullptr || mesh_in_b == nullptr) { - if (operation == GEO_NODE_BOOLEAN_UNION) { - if (mesh_in_a != nullptr) { - params.set_output("Geometry", geometry_set_in_a); + for (const bke::GeometryInstanceGroup &set_group : set_groups) { + const Mesh *mesh_in = set_group.geometry_set.get_mesh_for_read(); + if (mesh_in != nullptr) { + meshes.append_n_times(mesh_in, set_group.transforms.size()); + for (const int i : set_group.transforms.index_range()) { + transforms.append(set_group.transforms.begin() + i); } - else { - params.set_output("Geometry", geometry_set_in_b); - } - } - else { - params.set_output("Geometry", geometry_set_in_a); } - return; } - Mesh *mesh_out = mesh_boolean_calc(mesh_in_a, mesh_in_b, operation); - geometry_set_out = GeometrySet::create_with_mesh(mesh_out); + Mesh *result = blender::meshintersect::direct_mesh_boolean( + meshes, transforms, float4x4::identity(), {}, use_self, hole_tolerant, operation); - params.set_output("Geometry", std::move(geometry_set_out)); + params.set_output("Geometry", GeometrySet::create_with_mesh(result)); } + } // namespace blender::nodes void register_node_type_geo_boolean() @@ -162,6 +142,8 @@ void register_node_type_geo_boolean() geo_node_type_base(&ntype, GEO_NODE_BOOLEAN, "Boolean", NODE_CLASS_GEOMETRY, 0); node_type_socket_templates(&ntype, geo_node_boolean_in, geo_node_boolean_out); ntype.draw_buttons = geo_node_boolean_layout; + ntype.updatefunc = geo_node_boolean_update; + node_type_init(&ntype, geo_node_boolean_init); ntype.geometry_node_execute = blender::nodes::geo_node_boolean_exec; nodeRegisterType(&ntype); } |