966 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			966 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * ***** BEGIN GPL LICENSE BLOCK *****
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or
 | 
						|
 * modify it under the terms of the GNU General Public License
 | 
						|
 * as published by the Free Software Foundation; either version 2
 | 
						|
 * of the License, or (at your option) any later version.
 | 
						|
 *
 | 
						|
 * This program is distributed in the hope that it will be useful,
 | 
						|
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 * GNU General Public License for more details.
 | 
						|
 *
 | 
						|
 * You should have received a copy of the GNU General Public License
 | 
						|
 * along with this program; if not, write to the Free Software Foundation,
 | 
						|
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 | 
						|
 *
 | 
						|
 * ***** END GPL LICENSE BLOCK *****
 | 
						|
 */
 | 
						|
 | 
						|
/** \file blender/editors/mesh/editmesh_intersect.c
 | 
						|
 *  \ingroup edmesh
 | 
						|
 */
 | 
						|
 | 
						|
#include "MEM_guardedalloc.h"
 | 
						|
 | 
						|
#include "DNA_object_types.h"
 | 
						|
 | 
						|
#include "BLI_math.h"
 | 
						|
#include "BLI_memarena.h"
 | 
						|
#include "BLI_stack.h"
 | 
						|
#include "BLI_buffer.h"
 | 
						|
#include "BLI_kdopbvh.h"
 | 
						|
#include "BLI_linklist_stack.h"
 | 
						|
 | 
						|
#include "BKE_layer.h"
 | 
						|
#include "BKE_editmesh_bvh.h"
 | 
						|
#include "BKE_context.h"
 | 
						|
#include "BKE_report.h"
 | 
						|
#include "BKE_editmesh.h"
 | 
						|
 | 
						|
#include "RNA_access.h"
 | 
						|
#include "RNA_define.h"
 | 
						|
 | 
						|
#include "WM_types.h"
 | 
						|
 | 
						|
#include "ED_mesh.h"
 | 
						|
#include "ED_screen.h"
 | 
						|
 | 
						|
#include "intern/bmesh_private.h"
 | 
						|
 | 
						|
#include "mesh_intern.h"  /* own include */
 | 
						|
 | 
						|
#include "tools/bmesh_intersect.h"
 | 
						|
#include "tools/bmesh_separate.h"
 | 
						|
 | 
						|
 | 
						|
/* detect isolated holes and fill them */
 | 
						|
#define USE_NET_ISLAND_CONNECT
 | 
						|
 | 
						|
/**
 | 
						|
 * Compare selected with its self.
 | 
						|
 */
 | 
						|
static int bm_face_isect_self(BMFace *f, void *UNUSED(user_data))
 | 
						|
{
 | 
						|
	if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Compare selected/unselected.
 | 
						|
 */
 | 
						|
static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data))
 | 
						|
{
 | 
						|
	if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	else if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * A flipped version of #bm_face_isect_pair
 | 
						|
 * use for boolean 'difference', which depends on order.
 | 
						|
 */
 | 
						|
static int bm_face_isect_pair_swap(BMFace *f, void *UNUSED(user_data))
 | 
						|
{
 | 
						|
	if (BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	else if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
 | 
						|
		return 0;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		return 1;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Use for intersect and boolean.
 | 
						|
 */
 | 
						|
static void edbm_intersect_select(BMEditMesh *em)
 | 
						|
{
 | 
						|
	BM_mesh_elem_hflag_disable_all(em->bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
 | 
						|
 | 
						|
	if (em->bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) {
 | 
						|
		BMIter iter;
 | 
						|
		BMEdge *e;
 | 
						|
 | 
						|
		BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) {
 | 
						|
			if (BM_elem_flag_test(e, BM_ELEM_TAG)) {
 | 
						|
				BM_edge_select_set(em->bm, e, true);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	EDBM_mesh_normals_update(em);
 | 
						|
	EDBM_update_generic(em, true, true);
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/* -------------------------------------------------------------------- */
 | 
						|
/* Cut intersections into geometry */
 | 
						|
 | 
						|
/** \name Simple Intersect (self-intersect)
 | 
						|
 * \{
 | 
						|
 */
 | 
						|
 | 
						|
enum {
 | 
						|
	ISECT_SEL           = 0,
 | 
						|
	ISECT_SEL_UNSEL     = 1,
 | 
						|
};
 | 
						|
 | 
						|
enum {
 | 
						|
	ISECT_SEPARATE_ALL           = 0,
 | 
						|
	ISECT_SEPARATE_CUT           = 1,
 | 
						|
	ISECT_SEPARATE_NONE          = 2,
 | 
						|
};
 | 
						|
 | 
						|
static int edbm_intersect_exec(bContext *C, wmOperator *op)
 | 
						|
{
 | 
						|
	const int mode = RNA_enum_get(op->ptr, "mode");
 | 
						|
	int (*test_fn)(BMFace *, void *);
 | 
						|
	bool use_separate_all = false;
 | 
						|
	bool use_separate_cut = false;
 | 
						|
	const int separate_mode = RNA_enum_get(op->ptr, "separate_mode");
 | 
						|
	const float eps = RNA_float_get(op->ptr, "threshold");
 | 
						|
	bool use_self;
 | 
						|
	bool has_isect;
 | 
						|
 | 
						|
	switch (mode) {
 | 
						|
		case ISECT_SEL:
 | 
						|
			test_fn = bm_face_isect_self;
 | 
						|
			use_self = true;
 | 
						|
			break;
 | 
						|
		default:  /* ISECT_SEL_UNSEL */
 | 
						|
			test_fn = bm_face_isect_pair;
 | 
						|
			use_self = false;
 | 
						|
			break;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (separate_mode) {
 | 
						|
		case ISECT_SEPARATE_ALL:
 | 
						|
			use_separate_all = true;
 | 
						|
			break;
 | 
						|
		case ISECT_SEPARATE_CUT:
 | 
						|
			if (use_self == false) {
 | 
						|
				use_separate_cut = true;
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				/* we could support this but would require more advanced logic inside 'BM_mesh_intersect'
 | 
						|
				 * for now just separate all */
 | 
						|
				use_separate_all = true;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		default:  /* ISECT_SEPARATE_NONE */
 | 
						|
			break;
 | 
						|
	}
 | 
						|
	ViewLayer *view_layer = CTX_data_view_layer(C);
 | 
						|
	uint objects_len = 0;
 | 
						|
	uint isect_len = 0;
 | 
						|
	Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
 | 
						|
	for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
 | 
						|
		Object *obedit = objects[ob_index];
 | 
						|
		BMEditMesh *em = BKE_editmesh_from_object(obedit);
 | 
						|
 | 
						|
		if (em->bm->totfacesel == 0) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		has_isect = BM_mesh_intersect(
 | 
						|
			em->bm,
 | 
						|
			em->looptris, em->tottri,
 | 
						|
			test_fn, NULL,
 | 
						|
			use_self, use_separate_all, true, true, true,
 | 
						|
			-1,
 | 
						|
			eps);
 | 
						|
 | 
						|
		if (use_separate_cut) {
 | 
						|
			/* detach selected/un-selected faces */
 | 
						|
			BM_mesh_separate_faces(
 | 
						|
				em->bm,
 | 
						|
				BM_elem_cb_check_hflag_enabled_simple(const BMFace *, BM_ELEM_SELECT));
 | 
						|
		}
 | 
						|
 | 
						|
		if (has_isect) {
 | 
						|
			edbm_intersect_select(em);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			isect_len++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	MEM_freeN(objects);
 | 
						|
 | 
						|
	if (isect_len == objects_len) {
 | 
						|
		BKE_report(op->reports, RPT_WARNING, "No intersections found");
 | 
						|
	}
 | 
						|
	return OPERATOR_FINISHED;
 | 
						|
}
 | 
						|
 | 
						|
void MESH_OT_intersect(struct wmOperatorType *ot)
 | 
						|
{
 | 
						|
	static const EnumPropertyItem isect_mode_items[] = {
 | 
						|
		{ISECT_SEL, "SELECT", 0, "Self Intersect",
 | 
						|
		 "Self intersect selected faces"},
 | 
						|
		{ISECT_SEL_UNSEL, "SELECT_UNSELECT", 0, "Selected/Unselected",
 | 
						|
		 "Intersect selected with unselected faces"},
 | 
						|
		{0, NULL, 0, NULL, NULL}
 | 
						|
	};
 | 
						|
 | 
						|
	static const EnumPropertyItem isect_separate_items[] = {
 | 
						|
		{ISECT_SEPARATE_ALL, "ALL", 0, "All",
 | 
						|
		 "Separate all geometry from intersections"},
 | 
						|
		{ISECT_SEPARATE_CUT, "CUT", 0, "Cut",
 | 
						|
		 "Cut into geometry keeping each side separate (Selected/Unselected only)"},
 | 
						|
		{ISECT_SEPARATE_NONE, "NONE", 0, "Merge",
 | 
						|
		 "Merge all geometry from the intersection"},
 | 
						|
		{0, NULL, 0, NULL, NULL}
 | 
						|
	};
 | 
						|
 | 
						|
	/* identifiers */
 | 
						|
	ot->name = "Intersect (Knife)";
 | 
						|
	ot->description = "Cut an intersection into faces";
 | 
						|
	ot->idname = "MESH_OT_intersect";
 | 
						|
 | 
						|
	/* api callbacks */
 | 
						|
	ot->exec = edbm_intersect_exec;
 | 
						|
	ot->poll = ED_operator_editmesh;
 | 
						|
 | 
						|
	/* props */
 | 
						|
	RNA_def_enum(ot->srna, "mode", isect_mode_items, ISECT_SEL_UNSEL, "Source", "");
 | 
						|
	RNA_def_enum(ot->srna, "separate_mode", isect_separate_items, ISECT_SEPARATE_CUT, "Separate Mode", "");
 | 
						|
	RNA_def_float_distance(ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge threshold", "", 0.0, 0.001);
 | 
						|
 | 
						|
	/* flags */
 | 
						|
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | 
						|
}
 | 
						|
 | 
						|
/** \} */
 | 
						|
 | 
						|
 | 
						|
/* -------------------------------------------------------------------- */
 | 
						|
/* Boolean (a kind of intersect) */
 | 
						|
 | 
						|
/** \name Boolean Intersect
 | 
						|
 *
 | 
						|
 * \note internally this is nearly exactly the same as 'MESH_OT_intersect',
 | 
						|
 * however from a user perspective they are quite different, so expose as different tools.
 | 
						|
 *
 | 
						|
 * \{
 | 
						|
 */
 | 
						|
 | 
						|
static int edbm_intersect_boolean_exec(bContext *C, wmOperator *op)
 | 
						|
{
 | 
						|
	const int boolean_operation = RNA_enum_get(op->ptr, "operation");
 | 
						|
	bool use_swap = RNA_boolean_get(op->ptr, "use_swap");
 | 
						|
	const float eps = RNA_float_get(op->ptr, "threshold");
 | 
						|
	int (*test_fn)(BMFace *, void *);
 | 
						|
	bool has_isect;
 | 
						|
 | 
						|
	test_fn = use_swap ? bm_face_isect_pair_swap : bm_face_isect_pair;
 | 
						|
	ViewLayer *view_layer = CTX_data_view_layer(C);
 | 
						|
	uint objects_len = 0;
 | 
						|
	uint isect_len = 0;
 | 
						|
	Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
 | 
						|
	for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
 | 
						|
		Object *obedit = objects[ob_index];
 | 
						|
		BMEditMesh *em = BKE_editmesh_from_object(obedit);
 | 
						|
 | 
						|
		if (em->bm->totfacesel == 0) {
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		has_isect = BM_mesh_intersect(
 | 
						|
			em->bm,
 | 
						|
			em->looptris, em->tottri,
 | 
						|
			test_fn, NULL,
 | 
						|
			false, false, true, true, true,
 | 
						|
			boolean_operation,
 | 
						|
			eps);
 | 
						|
 | 
						|
 | 
						|
		if (has_isect) {
 | 
						|
			edbm_intersect_select(em);
 | 
						|
		}
 | 
						|
		else {
 | 
						|
			isect_len++;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	MEM_freeN(objects);
 | 
						|
 | 
						|
	if (isect_len == objects_len) {
 | 
						|
		BKE_report(op->reports, RPT_WARNING, "No intersections found");
 | 
						|
	}
 | 
						|
	return OPERATOR_FINISHED;
 | 
						|
}
 | 
						|
 | 
						|
void MESH_OT_intersect_boolean(struct wmOperatorType *ot)
 | 
						|
{
 | 
						|
	static const EnumPropertyItem isect_boolean_operation_items[] = {
 | 
						|
		{BMESH_ISECT_BOOLEAN_ISECT, "INTERSECT", 0, "Intersect", ""},
 | 
						|
		{BMESH_ISECT_BOOLEAN_UNION, "UNION", 0, "Union", ""},
 | 
						|
		{BMESH_ISECT_BOOLEAN_DIFFERENCE, "DIFFERENCE", 0, "Difference", ""},
 | 
						|
		{0, NULL, 0, NULL, NULL}
 | 
						|
	};
 | 
						|
 | 
						|
	/* identifiers */
 | 
						|
	ot->name = "Intersect (Boolean)";
 | 
						|
	ot->description = "Cut solid geometry from selected to unselected";
 | 
						|
	ot->idname = "MESH_OT_intersect_boolean";
 | 
						|
 | 
						|
	/* api callbacks */
 | 
						|
	ot->exec = edbm_intersect_boolean_exec;
 | 
						|
	ot->poll = ED_operator_editmesh;
 | 
						|
 | 
						|
	/* props */
 | 
						|
	RNA_def_enum(ot->srna, "operation", isect_boolean_operation_items, BMESH_ISECT_BOOLEAN_DIFFERENCE, "Boolean", "");
 | 
						|
	RNA_def_boolean(ot->srna, "use_swap", false, "Swap", "Use with difference intersection to swap which side is kept");
 | 
						|
	RNA_def_float_distance(ot->srna, "threshold", 0.000001f, 0.0, 0.01, "Merge threshold", "", 0.0, 0.001);
 | 
						|
 | 
						|
	/* flags */
 | 
						|
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | 
						|
}
 | 
						|
 | 
						|
/** \} */
 | 
						|
 | 
						|
/* -------------------------------------------------------------------- */
 | 
						|
/* Face Split by Edges */
 | 
						|
 | 
						|
/** \name Face/Edge Split
 | 
						|
 * \{ */
 | 
						|
 | 
						|
static void bm_face_split_by_edges(
 | 
						|
        BMesh *bm, BMFace *f, const char hflag,
 | 
						|
        /* reusable memory buffer */
 | 
						|
        BLI_Buffer *edge_net_temp_buf)
 | 
						|
{
 | 
						|
	const int f_index = BM_elem_index_get(f);
 | 
						|
 | 
						|
	BMLoop *l_iter;
 | 
						|
	BMLoop *l_first;
 | 
						|
	BMVert *v;
 | 
						|
 | 
						|
	BMFace **face_arr;
 | 
						|
	int face_arr_len;
 | 
						|
 | 
						|
	/* likely this will stay very small
 | 
						|
	 * all verts pushed into this stack _must_ have their previous edges set! */
 | 
						|
	BLI_SMALLSTACK_DECLARE(vert_stack, BMVert *);
 | 
						|
	BLI_SMALLSTACK_DECLARE(vert_stack_next, BMVert *);
 | 
						|
 | 
						|
	BLI_assert(edge_net_temp_buf->count == 0);
 | 
						|
 | 
						|
	/* collect all edges */
 | 
						|
	l_iter = l_first = BM_FACE_FIRST_LOOP(f);
 | 
						|
	do {
 | 
						|
		BMIter iter;
 | 
						|
		BMEdge *e;
 | 
						|
 | 
						|
		BM_ITER_ELEM (e, &iter, l_iter->v, BM_EDGES_OF_VERT) {
 | 
						|
			if (BM_elem_flag_test(e, hflag) &&
 | 
						|
			    (BM_elem_index_get(e) == f_index))
 | 
						|
			{
 | 
						|
				v = BM_edge_other_vert(e, l_iter->v);
 | 
						|
				v->e = e;
 | 
						|
 | 
						|
				BLI_SMALLSTACK_PUSH(vert_stack, v);
 | 
						|
				BLI_buffer_append(edge_net_temp_buf, BMEdge *, e);
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} while ((l_iter = l_iter->next) != l_first);
 | 
						|
 | 
						|
 | 
						|
 | 
						|
	/* now assign all */
 | 
						|
	/* pop free values into the next stack */
 | 
						|
	while ((v = BLI_SMALLSTACK_POP_EX(vert_stack, vert_stack_next))) {
 | 
						|
		BMIter eiter;
 | 
						|
		BMEdge *e_next;
 | 
						|
 | 
						|
		BM_ITER_ELEM (e_next, &eiter, v, BM_EDGES_OF_VERT) {
 | 
						|
			if (BM_elem_flag_test(e_next, hflag) &&
 | 
						|
			    (BM_elem_index_get(e_next) == -1))
 | 
						|
			{
 | 
						|
				BMVert *v_next;
 | 
						|
				v_next = BM_edge_other_vert(e_next, v);
 | 
						|
				BM_elem_index_set(e_next, f_index);
 | 
						|
				BLI_SMALLSTACK_PUSH(vert_stack_next, v_next);
 | 
						|
				BLI_buffer_append(edge_net_temp_buf, BMEdge *, e_next);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		if (BLI_SMALLSTACK_IS_EMPTY(vert_stack)) {
 | 
						|
			BLI_SMALLSTACK_SWAP(vert_stack, vert_stack_next);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	BM_face_split_edgenet(
 | 
						|
	        bm, f, edge_net_temp_buf->data, edge_net_temp_buf->count,
 | 
						|
	        &face_arr, &face_arr_len);
 | 
						|
 | 
						|
	BLI_buffer_clear(edge_net_temp_buf);
 | 
						|
 | 
						|
	if (face_arr_len) {
 | 
						|
		int i;
 | 
						|
		for (i = 0; i < face_arr_len; i++) {
 | 
						|
			BM_face_select_set(bm, face_arr[i], true);
 | 
						|
			BM_elem_flag_disable(face_arr[i], hflag);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (face_arr) {
 | 
						|
		MEM_freeN(face_arr);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Check if a vert is in any of the faces connected to the edge,
 | 
						|
 * \a f_ignore is a face we happen to know isn't shared by the vertex.
 | 
						|
 */
 | 
						|
static bool bm_vert_in_faces_radial(BMVert *v, BMEdge *e_radial, BMFace *f_ignore)
 | 
						|
{
 | 
						|
	BLI_assert(BM_vert_in_face(v, f_ignore) == false);
 | 
						|
	if (e_radial->l) {
 | 
						|
		BMLoop *l_iter = e_radial->l;
 | 
						|
		do {
 | 
						|
			if (l_iter->f != f_ignore) {
 | 
						|
				if (BM_vert_in_face(v, l_iter->f)) {
 | 
						|
					return true;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} while ((l_iter = l_iter->radial_next) != e_radial->l);
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef USE_NET_ISLAND_CONNECT
 | 
						|
 | 
						|
struct LinkBase {
 | 
						|
	LinkNode    *list;
 | 
						|
	unsigned int list_len;
 | 
						|
};
 | 
						|
 | 
						|
static void ghash_insert_face_edge_link(
 | 
						|
        GHash *gh, BMFace *f_key, BMEdge *e_val,
 | 
						|
        MemArena *mem_arena)
 | 
						|
{
 | 
						|
	void           **ls_base_p;
 | 
						|
	struct LinkBase *ls_base;
 | 
						|
	LinkNode *ls;
 | 
						|
 | 
						|
	if (!BLI_ghash_ensure_p(gh, f_key, &ls_base_p)) {
 | 
						|
		ls_base = *ls_base_p = BLI_memarena_alloc(mem_arena, sizeof(*ls_base));
 | 
						|
		ls_base->list     = NULL;
 | 
						|
		ls_base->list_len = 0;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
		ls_base = *ls_base_p;
 | 
						|
	}
 | 
						|
 | 
						|
	ls = BLI_memarena_alloc(mem_arena, sizeof(*ls));
 | 
						|
	ls->next = ls_base->list;
 | 
						|
	ls->link = e_val;
 | 
						|
	ls_base->list = ls;
 | 
						|
	ls_base->list_len += 1;
 | 
						|
}
 | 
						|
 | 
						|
static int bm_edge_sort_length_cb(const void *e_a_v, const void *e_b_v)
 | 
						|
{
 | 
						|
	const float val_a = -BM_edge_calc_length_squared(*((BMEdge **)e_a_v));
 | 
						|
	const float val_b = -BM_edge_calc_length_squared(*((BMEdge **)e_b_v));
 | 
						|
 | 
						|
	if      (val_a > val_b) return  1;
 | 
						|
	else if (val_a < val_b) return -1;
 | 
						|
	else                    return  0;
 | 
						|
}
 | 
						|
 | 
						|
static void bm_face_split_by_edges_island_connect(
 | 
						|
        BMesh *bm, BMFace *f,
 | 
						|
        LinkNode *e_link, const int e_link_len,
 | 
						|
        MemArena *mem_arena_edgenet)
 | 
						|
{
 | 
						|
	BMEdge **edge_arr = BLI_memarena_alloc(mem_arena_edgenet, sizeof(*edge_arr) * e_link_len);
 | 
						|
	int edge_arr_len = 0;
 | 
						|
 | 
						|
	while (e_link) {
 | 
						|
		edge_arr[edge_arr_len++] = e_link->link;
 | 
						|
		e_link = e_link->next;
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		unsigned int edge_arr_holes_len;
 | 
						|
		BMEdge **edge_arr_holes;
 | 
						|
		if (BM_face_split_edgenet_connect_islands(
 | 
						|
		        bm, f,
 | 
						|
		        edge_arr, e_link_len,
 | 
						|
		        true,
 | 
						|
		        mem_arena_edgenet,
 | 
						|
		        &edge_arr_holes, &edge_arr_holes_len))
 | 
						|
		{
 | 
						|
			edge_arr_len = edge_arr_holes_len;
 | 
						|
			edge_arr = edge_arr_holes;  /* owned by the arena */
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	BM_face_split_edgenet(
 | 
						|
	        bm, f, edge_arr, edge_arr_len,
 | 
						|
	        NULL, NULL);
 | 
						|
 | 
						|
	for (int i = e_link_len; i < edge_arr_len; i++) {
 | 
						|
		BM_edge_select_set(bm, edge_arr[i], true);
 | 
						|
	}
 | 
						|
 | 
						|
	if (e_link_len != edge_arr_len) {
 | 
						|
		/* connecting partial islands can add redundant edges
 | 
						|
		 * sort before removal to give deterministic outcome */
 | 
						|
		qsort(edge_arr, edge_arr_len - e_link_len, sizeof(*edge_arr), bm_edge_sort_length_cb);
 | 
						|
		for (int i = e_link_len; i < edge_arr_len; i++) {
 | 
						|
			BMFace *f_pair[2];
 | 
						|
			if (BM_edge_face_pair(edge_arr[i], &f_pair[0], &f_pair[1])) {
 | 
						|
				if (BM_face_share_vert_count(f_pair[0], f_pair[1]) == 2) {
 | 
						|
					BMFace *f_new = BM_faces_join(bm, f_pair, 2, true);
 | 
						|
					if (f_new) {
 | 
						|
						BM_face_select_set(bm, f_new, true);
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Check if \a v_pivot should be spliced into an existing edge.
 | 
						|
 *
 | 
						|
 * Detect one of 3 cases:
 | 
						|
 *
 | 
						|
 * - \a v_pivot is shared by 2+ edges from different faces.
 | 
						|
 *   in this case return the closest edge shared by all faces.
 | 
						|
 *
 | 
						|
 * - \a v_pivot is an end-point of an edge which has no other edges connected.
 | 
						|
 *   in this case return the closest edge in \a f_a to the \a v_pivot.
 | 
						|
 *
 | 
						|
 * - \a v_pivot has only edges from the same face connected,
 | 
						|
 *   in this case return NULL. This is the most common case - no action is needed.
 | 
						|
 *
 | 
						|
 * \return the edge to be split.
 | 
						|
 *
 | 
						|
 * \note Currently we don't snap to verts or split chains by verts on-edges.
 | 
						|
 */
 | 
						|
static BMEdge *bm_face_split_edge_find(
 | 
						|
        BMEdge *e_a, BMFace *f_a, BMVert *v_pivot, BMFace **ftable, const int ftable_len,
 | 
						|
        float r_v_pivot_co[3], float *r_v_pivot_fac)
 | 
						|
{
 | 
						|
	const int f_a_index = BM_elem_index_get(e_a);
 | 
						|
	bool found_other_self = false;
 | 
						|
	int  found_other_face = 0;
 | 
						|
	BLI_SMALLSTACK_DECLARE(face_stack, BMFace *);
 | 
						|
 | 
						|
	/* loop over surrounding edges to check if we're part of a chain or a delimiter vertex */
 | 
						|
	BMEdge *e_b = v_pivot->e;
 | 
						|
	do {
 | 
						|
		if (e_b != e_a) {
 | 
						|
			const int f_b_index = BM_elem_index_get(e_b);
 | 
						|
			if (f_b_index == f_a_index) {
 | 
						|
				/* not an endpoint */
 | 
						|
				found_other_self = true;
 | 
						|
			}
 | 
						|
			else if (f_b_index != -1) {
 | 
						|
				BLI_assert(f_b_index < ftable_len);
 | 
						|
				UNUSED_VARS_NDEBUG(ftable_len);
 | 
						|
 | 
						|
				/* 'v_pivot' spans 2+ faces,
 | 
						|
				 * tag to ensure we pick an edge that includes this face */
 | 
						|
				BMFace *f_b = ftable[f_b_index];
 | 
						|
				if (!BM_elem_flag_test(f_b, BM_ELEM_INTERNAL_TAG)) {
 | 
						|
					BM_elem_flag_enable(f_b, BM_ELEM_INTERNAL_TAG);
 | 
						|
					BLI_SMALLSTACK_PUSH(face_stack, f_b);
 | 
						|
					found_other_face++;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	} while ((e_b = BM_DISK_EDGE_NEXT(e_b, v_pivot)) != v_pivot->e);
 | 
						|
 | 
						|
	BMEdge *e_split = NULL;
 | 
						|
 | 
						|
	/* if we have no others or the other edge is outside this face,
 | 
						|
	 * we're an endpoint to connect to a boundary */
 | 
						|
	if ((found_other_self == false) || found_other_face) {
 | 
						|
 | 
						|
		BMLoop *l_iter, *l_first;
 | 
						|
		l_iter = l_first = BM_FACE_FIRST_LOOP(f_a);
 | 
						|
		float dist_best_sq = FLT_MAX;
 | 
						|
 | 
						|
		do {
 | 
						|
			float v_pivot_co_test[3];
 | 
						|
			float v_pivot_fac = line_point_factor_v3(v_pivot->co, l_iter->e->v1->co, l_iter->e->v2->co);
 | 
						|
			CLAMP(v_pivot_fac, 0.0f, 1.0f);
 | 
						|
			interp_v3_v3v3(v_pivot_co_test, l_iter->e->v1->co, l_iter->e->v2->co, v_pivot_fac);
 | 
						|
 | 
						|
			float dist_test_sq = len_squared_v3v3(v_pivot_co_test, v_pivot->co);
 | 
						|
			if ((dist_test_sq < dist_best_sq) || (e_split == NULL)) {
 | 
						|
				bool ok = true;
 | 
						|
 | 
						|
				if (UNLIKELY(BM_edge_exists(v_pivot, l_iter->e->v1) ||
 | 
						|
				             BM_edge_exists(v_pivot, l_iter->e->v2)))
 | 
						|
				{
 | 
						|
					/* very unlikely but will cause complications splicing the verts together,
 | 
						|
					 * so just skip this case */
 | 
						|
					ok = false;
 | 
						|
				}
 | 
						|
				else if (found_other_face) {
 | 
						|
					/* double check that _all_ the faces used by v_pivot's edges are attached to this edge
 | 
						|
					 * otherwise don't attempt the split since it will give non-deterministic results */
 | 
						|
					BMLoop *l_radial_iter = l_iter->radial_next;
 | 
						|
					int other_face_shared = 0;
 | 
						|
					if (l_radial_iter != l_iter) {
 | 
						|
						do {
 | 
						|
							if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_INTERNAL_TAG)) {
 | 
						|
								other_face_shared++;
 | 
						|
							}
 | 
						|
						} while ((l_radial_iter = l_radial_iter->radial_next) != l_iter);
 | 
						|
					}
 | 
						|
					if (other_face_shared != found_other_face) {
 | 
						|
						ok = false;
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if (ok) {
 | 
						|
					e_split = l_iter->e;
 | 
						|
					dist_best_sq = dist_test_sq;
 | 
						|
					copy_v3_v3(r_v_pivot_co, v_pivot_co_test);
 | 
						|
					*r_v_pivot_fac = v_pivot_fac;
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} while ((l_iter = l_iter->next) != l_first);
 | 
						|
	}
 | 
						|
 | 
						|
	{
 | 
						|
		/* reset the flag, for future use */
 | 
						|
		BMFace *f;
 | 
						|
		while ((f = BLI_SMALLSTACK_POP(face_stack))) {
 | 
						|
			BM_elem_flag_disable(f, BM_ELEM_INTERNAL_TAG);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return e_split;
 | 
						|
}
 | 
						|
 | 
						|
#endif  /* USE_NET_ISLAND_CONNECT */
 | 
						|
 | 
						|
 | 
						|
static int edbm_face_split_by_edges_exec(bContext *C, wmOperator *UNUSED(op))
 | 
						|
{
 | 
						|
	const char hflag = BM_ELEM_TAG;
 | 
						|
 | 
						|
	BMEdge *e;
 | 
						|
	BMIter iter;
 | 
						|
 | 
						|
	ViewLayer *view_layer = CTX_data_view_layer(C);
 | 
						|
	uint objects_len = 0;
 | 
						|
	Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data(view_layer, &objects_len);
 | 
						|
	for (uint ob_index = 0; ob_index < objects_len; ob_index++) {
 | 
						|
		Object *obedit = objects[ob_index];
 | 
						|
		BMEditMesh *em = BKE_editmesh_from_object(obedit);
 | 
						|
		BMesh *bm = em->bm;
 | 
						|
 | 
						|
		if ((bm->totedgesel == 0) ||
 | 
						|
		    (bm->totfacesel == 0))
 | 
						|
		{
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		BLI_SMALLSTACK_DECLARE(loop_stack, BMLoop *);
 | 
						|
 | 
						|
		{
 | 
						|
			BMVert *v;
 | 
						|
			BM_ITER_MESH(v, &iter, bm, BM_VERTS_OF_MESH) {
 | 
						|
				BM_elem_flag_disable(v, hflag);
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		/* edge index is set to -1 then used to assosiate them with faces */
 | 
						|
		BM_ITER_MESH(e, &iter, bm, BM_EDGES_OF_MESH) {
 | 
						|
			if (BM_elem_flag_test(e, BM_ELEM_SELECT) && BM_edge_is_wire(e)) {
 | 
						|
				BM_elem_flag_enable(e, hflag);
 | 
						|
 | 
						|
				BM_elem_flag_enable(e->v1, hflag);
 | 
						|
				BM_elem_flag_enable(e->v2, hflag);
 | 
						|
 | 
						|
			}
 | 
						|
			else {
 | 
						|
				BM_elem_flag_disable(e, hflag);
 | 
						|
			}
 | 
						|
			BM_elem_index_set(e, -1);  /* set_dirty */
 | 
						|
		}
 | 
						|
		bm->elem_index_dirty |= BM_EDGE;
 | 
						|
 | 
						|
		{
 | 
						|
			BMFace *f;
 | 
						|
			int i;
 | 
						|
			BM_ITER_MESH_INDEX(f, &iter, bm, BM_FACES_OF_MESH, i) {
 | 
						|
				if (BM_elem_flag_test(f, BM_ELEM_SELECT)) {
 | 
						|
					BM_elem_flag_enable(f, hflag);
 | 
						|
				}
 | 
						|
				else {
 | 
						|
					BM_elem_flag_disable(f, hflag);
 | 
						|
				}
 | 
						|
				BM_elem_flag_disable(f, BM_ELEM_INTERNAL_TAG);
 | 
						|
				BM_elem_index_set(f, i);  /* set_ok */
 | 
						|
			}
 | 
						|
		}
 | 
						|
		bm->elem_index_dirty &= ~BM_FACE;
 | 
						|
 | 
						|
		BM_ITER_MESH(e, &iter, bm, BM_EDGES_OF_MESH) {
 | 
						|
			if (BM_elem_flag_test(e, hflag)) {
 | 
						|
				BMIter viter;
 | 
						|
				BMVert *v;
 | 
						|
				BM_ITER_ELEM(v, &viter, e, BM_VERTS_OF_EDGE) {
 | 
						|
					BMIter liter;
 | 
						|
					BMLoop *l;
 | 
						|
 | 
						|
					unsigned int loop_stack_len;
 | 
						|
					BMLoop *l_best = NULL;
 | 
						|
 | 
						|
					BLI_assert(BLI_SMALLSTACK_IS_EMPTY(loop_stack));
 | 
						|
					loop_stack_len = 0;
 | 
						|
 | 
						|
					BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) {
 | 
						|
						if (BM_elem_flag_test(l->f, hflag)) {
 | 
						|
							BLI_SMALLSTACK_PUSH(loop_stack, l);
 | 
						|
							loop_stack_len++;
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					if (loop_stack_len == 0) {
 | 
						|
						/* pass */
 | 
						|
					}
 | 
						|
					else if (loop_stack_len == 1) {
 | 
						|
						l_best = BLI_SMALLSTACK_POP(loop_stack);
 | 
						|
					}
 | 
						|
					else {
 | 
						|
						/* complicated case, match the edge with a face-loop */
 | 
						|
 | 
						|
						BMVert *v_other = BM_edge_other_vert(e, v);
 | 
						|
						float e_dir[3];
 | 
						|
 | 
						|
						/* we want closest to zero */
 | 
						|
						float dot_best = FLT_MAX;
 | 
						|
 | 
						|
						sub_v3_v3v3(e_dir, v_other->co, v->co);
 | 
						|
						normalize_v3(e_dir);
 | 
						|
 | 
						|
						while ((l = BLI_SMALLSTACK_POP(loop_stack))) {
 | 
						|
							float dot_test;
 | 
						|
 | 
						|
							/* Check dot first to save on expensive angle-comparison.
 | 
						|
							 * ideal case is 90d difference == 0.0 dot */
 | 
						|
							dot_test = fabsf(dot_v3v3(e_dir, l->f->no));
 | 
						|
							if (dot_test < dot_best) {
 | 
						|
 | 
						|
								/* check we're in the correct corner (works with convex loops too) */
 | 
						|
								if (angle_signed_on_axis_v3v3v3_v3(l->prev->v->co, l->v->co, v_other->co, l->f->no) <
 | 
						|
								    angle_signed_on_axis_v3v3v3_v3(l->prev->v->co, l->v->co, l->next->v->co, l->f->no))
 | 
						|
								{
 | 
						|
									dot_best = dot_test;
 | 
						|
									l_best = l;
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
 | 
						|
					if (l_best) {
 | 
						|
						BM_elem_index_set(e, BM_elem_index_get(l_best->f));  /* set_dirty */
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		{
 | 
						|
			BMFace *f;
 | 
						|
			BLI_buffer_declare_static(BMEdge **, edge_net_temp_buf, 0, 128);
 | 
						|
 | 
						|
			BM_ITER_MESH(f, &iter, bm, BM_FACES_OF_MESH) {
 | 
						|
				if (BM_elem_flag_test(f, hflag)) {
 | 
						|
					bm_face_split_by_edges(bm, f, hflag, &edge_net_temp_buf);
 | 
						|
				}
 | 
						|
			}
 | 
						|
			BLI_buffer_free(&edge_net_temp_buf);
 | 
						|
		}
 | 
						|
 | 
						|
#ifdef USE_NET_ISLAND_CONNECT
 | 
						|
		/* before overwriting edge index values, collect edges left untouched */
 | 
						|
		BLI_Stack *edges_loose = BLI_stack_new(sizeof(BMEdge *), __func__);
 | 
						|
		BM_ITER_MESH(e, &iter, bm, BM_EDGES_OF_MESH) {
 | 
						|
			if (BM_elem_flag_test(e, BM_ELEM_SELECT) && BM_edge_is_wire(e)) {
 | 
						|
				BLI_stack_push(edges_loose, &e);
 | 
						|
			}
 | 
						|
		}
 | 
						|
#endif
 | 
						|
 | 
						|
		EDBM_mesh_normals_update(em);
 | 
						|
		EDBM_update_generic(em, true, true);
 | 
						|
 | 
						|
 | 
						|
#ifdef USE_NET_ISLAND_CONNECT
 | 
						|
		/* we may have remaining isolated regions remaining,
 | 
						|
		 * these will need to have connecting edges created */
 | 
						|
		if (!BLI_stack_is_empty(edges_loose)) {
 | 
						|
			GHash *face_edge_map = BLI_ghash_ptr_new(__func__);
 | 
						|
 | 
						|
			MemArena *mem_arena = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
 | 
						|
 | 
						|
			BM_mesh_elem_index_ensure(bm, BM_FACE);
 | 
						|
 | 
						|
			{
 | 
						|
				BMBVHTree *bmbvh = BKE_bmbvh_new(bm, em->looptris, em->tottri, BMBVH_RESPECT_SELECT, NULL, false);
 | 
						|
 | 
						|
				BM_ITER_MESH(e, &iter, bm, BM_EDGES_OF_MESH) {
 | 
						|
					BM_elem_index_set(e, -1);  /* set_dirty */
 | 
						|
				}
 | 
						|
 | 
						|
				while (!BLI_stack_is_empty(edges_loose)) {
 | 
						|
					BLI_stack_pop(edges_loose, &e);
 | 
						|
					float e_center[3];
 | 
						|
					mid_v3_v3v3(e_center, e->v1->co, e->v2->co);
 | 
						|
 | 
						|
					BMFace *f = BKE_bmbvh_find_face_closest(bmbvh, e_center, FLT_MAX);
 | 
						|
					if (f) {
 | 
						|
						ghash_insert_face_edge_link(face_edge_map, f, e, mem_arena);
 | 
						|
						BM_elem_index_set(e, BM_elem_index_get(f));  /* set_dirty */
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				BKE_bmbvh_free(bmbvh);
 | 
						|
			}
 | 
						|
 | 
						|
			bm->elem_index_dirty |= BM_EDGE;
 | 
						|
 | 
						|
			BM_mesh_elem_table_ensure(bm, BM_FACE);
 | 
						|
 | 
						|
			/* detect edges chains that span faces
 | 
						|
			 * and splice vertices into the closest edges */
 | 
						|
			{
 | 
						|
				GHashIterator gh_iter;
 | 
						|
 | 
						|
				GHASH_ITER(gh_iter, face_edge_map) {
 | 
						|
					BMFace *f = BLI_ghashIterator_getKey(&gh_iter);
 | 
						|
					struct LinkBase *e_ls_base = BLI_ghashIterator_getValue(&gh_iter);
 | 
						|
					LinkNode *e_link = e_ls_base->list;
 | 
						|
 | 
						|
					do {
 | 
						|
						e = e_link->link;
 | 
						|
 | 
						|
						for (int j = 0; j < 2; j++) {
 | 
						|
							BMVert *v_pivot = (&e->v1)[j];
 | 
						|
							/* checking that \a v_pivot isn't in the face
 | 
						|
							 * prevents attempting to splice the same vertex into an edge from multiple faces */
 | 
						|
							if (!BM_vert_in_face(v_pivot, f)) {
 | 
						|
								float v_pivot_co[3];
 | 
						|
								float v_pivot_fac;
 | 
						|
								BMEdge *e_split = bm_face_split_edge_find(
 | 
						|
									e, f, v_pivot, bm->ftable, bm->totface,
 | 
						|
									v_pivot_co, &v_pivot_fac);
 | 
						|
 | 
						|
								if (e_split) {
 | 
						|
									/* for degenerate cases this vertex may be in one of this edges radial faces */
 | 
						|
									if (!bm_vert_in_faces_radial(v_pivot, e_split, f)) {
 | 
						|
										BMEdge *e_new;
 | 
						|
										BMVert *v_new = BM_edge_split(bm, e_split, e_split->v1, &e_new, v_pivot_fac);
 | 
						|
										if (v_new) {
 | 
						|
											/* we _know_ these don't share an edge */
 | 
						|
											BM_vert_splice(bm, v_pivot, v_new);
 | 
						|
											BM_elem_index_set(e_new, BM_elem_index_get(e_split));
 | 
						|
										}
 | 
						|
									}
 | 
						|
								}
 | 
						|
							}
 | 
						|
						}
 | 
						|
 | 
						|
					} while ((e_link = e_link->next));
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
 | 
						|
			{
 | 
						|
				MemArena *mem_arena_edgenet = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
 | 
						|
 | 
						|
				GHashIterator gh_iter;
 | 
						|
 | 
						|
				GHASH_ITER(gh_iter, face_edge_map) {
 | 
						|
					BMFace *f = BLI_ghashIterator_getKey(&gh_iter);
 | 
						|
					struct LinkBase *e_ls_base = BLI_ghashIterator_getValue(&gh_iter);
 | 
						|
 | 
						|
					bm_face_split_by_edges_island_connect(
 | 
						|
						bm, f,
 | 
						|
						e_ls_base->list, e_ls_base->list_len,
 | 
						|
						mem_arena_edgenet);
 | 
						|
 | 
						|
					BLI_memarena_clear(mem_arena_edgenet);
 | 
						|
				}
 | 
						|
 | 
						|
				BLI_memarena_free(mem_arena_edgenet);
 | 
						|
			}
 | 
						|
 | 
						|
			BLI_memarena_free(mem_arena);
 | 
						|
 | 
						|
			BLI_ghash_free(face_edge_map, NULL, NULL);
 | 
						|
 | 
						|
			EDBM_mesh_normals_update(em);
 | 
						|
			EDBM_update_generic(em, true, true);
 | 
						|
		}
 | 
						|
 | 
						|
		BLI_stack_free(edges_loose);
 | 
						|
#endif  /* USE_NET_ISLAND_CONNECT */
 | 
						|
 | 
						|
	}
 | 
						|
	MEM_freeN(objects);
 | 
						|
	return OPERATOR_FINISHED;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
void MESH_OT_face_split_by_edges(struct wmOperatorType *ot)
 | 
						|
{
 | 
						|
	/* identifiers */
 | 
						|
	ot->name = "Weld Edges into Faces";
 | 
						|
	ot->description = "Weld loose edges into faces (splitting them into new faces)";
 | 
						|
	ot->idname = "MESH_OT_face_split_by_edges";
 | 
						|
 | 
						|
	/* api callbacks */
 | 
						|
	ot->exec = edbm_face_split_by_edges_exec;
 | 
						|
	ot->poll = ED_operator_editmesh;
 | 
						|
 | 
						|
	/* flags */
 | 
						|
	ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
 | 
						|
}
 | 
						|
 | 
						|
/** \} */
 |