BMesh: Add option to use BMesh boolean modifier

This uses a bmesh-intersection, BLI_kdtree and watertight intersections to perform boolean operations.

For now keep both BMesh and Carve booleans usable at once for testing & bug reports,
however we plan to phase out Carve by next release.
This commit is contained in:
Campbell Barton 2015-12-11 20:24:39 +11:00
parent 8ae8b62a0e
commit 8cd7b42877
5 changed files with 268 additions and 15 deletions

@ -156,6 +156,15 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
col.label(text="Object:")
col.prop(md, "object", text="")
layout.prop(md, "use_bmesh")
if md.use_bmesh:
box = layout.box()
box.label("BMesh Options:")
box.prop(md, "use_bmesh_separate")
box.prop(md, "use_bmesh_dissolve")
box.prop(md, "use_bmesh_connect_regions")
box.prop(md, "threshold")
def BUILD(self, layout, ob, md):
split = layout.split()

@ -94,8 +94,26 @@ extern const BMAllocTemplate bm_mesh_chunksize_default;
(bm)->totvert), (bm)->totedge, (bm)->totloop, (bm)->totface}
#define BMALLOC_TEMPLATE_FROM_ME(me) { (CHECK_TYPE_INLINE(me, Mesh *), \
(me)->totvert), (me)->totedge, (me)->totloop, (me)->totpoly}
#define BMALLOC_TEMPLATE_FROM_DM(dm) { (CHECK_TYPE_INLINE(dm, DerivedMesh *), \
(dm)->getNumVerts(dm)), (dm)->getNumEdges(dm), (dm)->getNumLoops(dm), (dm)->getNumPolys(dm)}
#define _VA_BMALLOC_TEMPLATE_FROM_DM_1(dm) { \
(CHECK_TYPE_INLINE(dm, DerivedMesh *), \
(dm)->getNumVerts(dm)), \
(dm)->getNumEdges(dm), \
(dm)->getNumLoops(dm), \
(dm)->getNumPolys(dm), \
}
#define _VA_BMALLOC_TEMPLATE_FROM_DM_2(dm_a, dm_b) { \
(CHECK_TYPE_INLINE(dm_a, DerivedMesh *), \
CHECK_TYPE_INLINE(dm_b, DerivedMesh *), \
(dm_a)->getNumVerts(dm_a)) + (dm_b)->getNumVerts(dm_b), \
(dm_a)->getNumEdges(dm_a) + (dm_b)->getNumEdges(dm_b), \
(dm_a)->getNumLoops(dm_a) + (dm_b)->getNumLoops(dm_b), \
(dm_a)->getNumPolys(dm_a) + (dm_b)->getNumPolys(dm_b), \
}
#define BMALLOC_TEMPLATE_FROM_DM(...) VA_NARGS_CALL_OVERLOAD(_VA_BMALLOC_TEMPLATE_FROM_DM_, __VA_ARGS__)
enum {
BM_MESH_CREATE_USE_TOOLFLAGS = (1 << 0)

@ -640,7 +640,9 @@ typedef struct BooleanModifierData {
ModifierData modifier;
struct Object *object;
int operation, pad;
char operation;
char bm_flag, pad[2];
float threshold;
} BooleanModifierData;
typedef enum {
@ -649,6 +651,14 @@ typedef enum {
eBooleanModifierOp_Difference = 2,
} BooleanModifierOp;
/* temp bm_flag (debugging only) */
enum {
eBooleanModifierBMeshFlag_Enabled = (1 << 0),
eBooleanModifierBMeshFlag_BMesh_Separate = (1 << 1),
eBooleanModifierBMeshFlag_BMesh_NoDissolve = (1 << 2),
eBooleanModifierBMeshFlag_BMesh_NoConnectRegions = (1 << 3),
};
typedef struct MDefInfluence {
int vertex;
float weight;

@ -1883,6 +1883,34 @@ static void rna_def_modifier_boolean(BlenderRNA *brna)
RNA_def_property_enum_items(prop, prop_operation_items);
RNA_def_property_ui_text(prop, "Operation", "");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
/* BMesh intersection options */
prop = RNA_def_property(srna, "use_bmesh", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "bm_flag", eBooleanModifierBMeshFlag_Enabled);
RNA_def_property_ui_text(prop, "Use BMesh", "Use BMesh boolean calculation");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_bmesh_separate", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "bm_flag", eBooleanModifierBMeshFlag_BMesh_Separate);
RNA_def_property_ui_text(prop, "Separate", "Keep edges separate");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_bmesh_dissolve", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "bm_flag", eBooleanModifierBMeshFlag_BMesh_NoDissolve);
RNA_def_property_ui_text(prop, "Dissolve", "Dissolve verts created from tessellated intersection");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "use_bmesh_connect_regions", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_negative_sdna(prop, NULL, "bm_flag", eBooleanModifierBMeshFlag_BMesh_NoConnectRegions);
RNA_def_property_ui_text(prop, "Calculate Holes", "Connect regions (needed for hole filling)");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
prop = RNA_def_property(srna, "threshold", PROP_FLOAT, PROP_DISTANCE);
RNA_def_property_float_sdna(prop, NULL, "threshold");
RNA_def_property_range(prop, 0, 1.0f);
RNA_def_property_ui_range(prop, 0, 1, 1, 7);
RNA_def_property_ui_text(prop, "Threshold", "");
RNA_def_property_update(prop, 0, "rna_Modifier_update");
}
static void rna_def_modifier_array(BlenderRNA *brna)

@ -1,4 +1,3 @@
/*
* ***** BEGIN GPL LICENSE BLOCK *****
*
@ -33,6 +32,11 @@
* \ingroup modifiers
*/
#define USE_BMESH
#ifdef WITH_MOD_BOOLEAN
# define USE_CARVE WITH_MOD_BOOLEAN
#endif
#include <stdio.h>
#include "DNA_object_types.h"
@ -48,6 +52,18 @@
#include "MOD_boolean_util.h"
#include "MOD_util.h"
#ifdef USE_BMESH
#include "BLI_math_geom.h"
#include "MEM_guardedalloc.h"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "tools/bmesh_intersect.h"
#endif
#include "PIL_time.h"
#include "PIL_time_utildefines.h"
static void copyData(ModifierData *md, ModifierData *target)
{
#if 0
@ -104,7 +120,8 @@ static void updateDepsgraph(ModifierData *md,
DEG_add_object_relation(node, ob, DEG_OB_COMP_TRANSFORM, "Boolean Modifier");
}
#ifdef WITH_MOD_BOOLEAN
#if defined(USE_CARVE) || defined(USE_BMESH)
static DerivedMesh *get_quick_derivedMesh(DerivedMesh *derivedData, DerivedMesh *dm, int operation)
{
DerivedMesh *result = NULL;
@ -129,10 +146,157 @@ static DerivedMesh *get_quick_derivedMesh(DerivedMesh *derivedData, DerivedMesh
return result;
}
#endif /* defined(USE_CARVE) || defined(USE_BMESH) */
static DerivedMesh *applyModifier(ModifierData *md, Object *ob,
DerivedMesh *derivedData,
ModifierApplyFlag flag)
/* -------------------------------------------------------------------- */
/* BMESH */
#ifdef USE_BMESH
/* has no meaning for faces, do this so we can tell which face is which */
#define BM_FACE_TAG BM_ELEM_DRAW
/**
* Compare selected/unselected.
*/
static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data))
{
return BM_elem_flag_test(f, BM_FACE_TAG) ? 1 : 0;
}
static DerivedMesh *applyModifier_bmesh(
ModifierData *md, Object *ob,
DerivedMesh *dm,
ModifierApplyFlag flag)
{
BooleanModifierData *bmd = (BooleanModifierData *) md;
DerivedMesh *dm_other;
if (!bmd->object)
return dm;
dm_other = get_dm_for_modifier(bmd->object, flag);
if (dm_other) {
DerivedMesh *result;
/* when one of objects is empty (has got no faces) we could speed up
* calculation a bit returning one of objects' derived meshes (or empty one)
* Returning mesh is depended on modifiers operation (sergey) */
result = get_quick_derivedMesh(dm, dm_other, bmd->operation);
if (result == NULL) {
BMesh *bm;
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_DM(dm, dm_other);
TIMEIT_START(boolean_bmesh);
bm = BM_mesh_create(&allocsize);
DM_to_bmesh_ex(dm_other, bm, true);
DM_to_bmesh_ex(dm, bm, true);
if (1) {
/* create tessface & intersect */
const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
int tottri;
BMLoop *(*looptris)[3];
looptris = MEM_mallocN(sizeof(*looptris) * looptris_tot, __func__);
BM_bmesh_calc_tessellation(bm, looptris, &tottri);
/* postpone this until after tessellating
* so we can use the original normals before the vertex are moved */
{
BMIter iter;
int i;
const int i_verts_end = dm_other->getNumVerts(dm_other);
const int i_faces_end = dm_other->getNumPolys(dm_other);
float imat[4][4];
float omat[4][4];
invert_m4_m4(imat, ob->obmat);
mul_m4_m4m4(omat, imat, bmd->object->obmat);
BMVert *eve;
i = 0;
BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
mul_m4_v3(omat, eve->co);
if (++i == i_verts_end) {
break;
}
}
/* we need face normals because of 'BM_face_split_edgenet'
* we could calculate on the fly too (before calling split). */
float nmat[4][4];
invert_m4_m4(nmat, omat);
BMFace *efa;
i = 0;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
mul_transposed_mat3_m4_v3(nmat, efa->no);
normalize_v3(efa->no);
BM_elem_flag_enable(efa, BM_FACE_TAG); /* temp tag to test which side split faces are from */
if (++i == i_faces_end) {
break;
}
}
}
/* not needed, but normals for 'dm' will be invalid,
* currently this is ok for 'BM_mesh_intersect' */
// BM_mesh_normals_update(bm);
BM_mesh_intersect(
bm,
looptris, tottri,
bm_face_isect_pair, NULL,
false,
(bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_Separate) != 0,
(bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoDissolve) == 0,
(bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoConnectRegions) == 0,
bmd->operation,
bmd->threshold);
MEM_freeN(looptris);
}
result = CDDM_from_bmesh(bm, true);
BM_mesh_free(bm);
result->dirty |= DM_DIRTY_NORMALS;
TIMEIT_END(boolean_bmesh);
return result;
}
/* if new mesh returned, return it; otherwise there was
* an error, so delete the modifier object */
if (result)
return result;
else
modifier_setError(md, "Cannot execute boolean operation");
}
return dm;
}
#endif /* USE_BMESH */
/* -------------------------------------------------------------------- */
/* CARVE */
#ifdef USE_CARVE
static DerivedMesh *applyModifier_carve(
ModifierData *md, Object *ob,
DerivedMesh *derivedData,
ModifierApplyFlag flag)
{
BooleanModifierData *bmd = (BooleanModifierData *) md;
DerivedMesh *dm;
@ -151,12 +315,12 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob,
result = get_quick_derivedMesh(derivedData, dm, bmd->operation);
if (result == NULL) {
// TIMEIT_START(NewBooleanDerivedMesh)
TIMEIT_START(boolean_carve);
result = NewBooleanDerivedMesh(dm, bmd->object, derivedData, ob,
1 + bmd->operation);
// TIMEIT_END(NewBooleanDerivedMesh)
TIMEIT_END(boolean_carve);
}
/* if new mesh returned, return it; otherwise there was
@ -169,14 +333,16 @@ static DerivedMesh *applyModifier(ModifierData *md, Object *ob,
return derivedData;
}
#else // WITH_MOD_BOOLEAN
static DerivedMesh *applyModifier(ModifierData *UNUSED(md), Object *UNUSED(ob),
DerivedMesh *derivedData,
ModifierApplyFlag UNUSED(flag))
#endif /* USE_CARVE */
static DerivedMesh *applyModifier_nop(
ModifierData *UNUSED(md), Object *UNUSED(ob),
DerivedMesh *derivedData,
ModifierApplyFlag UNUSED(flag))
{
return derivedData;
}
#endif // WITH_MOD_BOOLEAN
static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *UNUSED(md))
{
@ -187,6 +353,28 @@ static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *UNUSED(
return dataMask;
}
static DerivedMesh *applyModifier(
ModifierData *md, Object *ob,
DerivedMesh *derivedData,
ModifierApplyFlag flag)
{
BooleanModifierData *bmd = (BooleanModifierData *)md;
const int method = (bmd->bm_flag & eBooleanModifierBMeshFlag_Enabled) ? 1 : 0;
switch (method) {
#ifdef USE_CARVE
case 0:
return applyModifier_carve(md, ob, derivedData, flag);
#endif
#ifdef USE_BMESH
case 1:
return applyModifier_bmesh(md, ob, derivedData, flag);
#endif
default:
return applyModifier_nop(md, ob, derivedData, flag);
}
}
ModifierTypeInfo modifierType_Boolean = {
/* name */ "Boolean",