512 lines
13 KiB
C
512 lines
13 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.
|
|
*
|
|
* Contributor(s): Campbell Barton
|
|
*
|
|
* ***** END GPL LICENSE BLOCK *****
|
|
*/
|
|
|
|
/** \file blender/bmesh/tools/bmesh_edgenet.c
|
|
* \ingroup bmesh
|
|
*
|
|
* Edgenet Fill.
|
|
*
|
|
*/
|
|
|
|
#include <limits.h>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_alloca.h"
|
|
#include "BLI_mempool.h"
|
|
#include "BLI_linklist.h"
|
|
|
|
#include "bmesh.h"
|
|
|
|
#ifdef __GNUC__
|
|
# pragma GCC diagnostic error "-Wsign-conversion"
|
|
# if (__GNUC__ * 100 + __GNUC_MINOR__) >= 406 /* gcc4.6+ only */
|
|
# pragma GCC diagnostic error "-Wsign-compare"
|
|
# pragma GCC diagnostic error "-Wconversion"
|
|
# endif
|
|
#endif
|
|
|
|
/* Data for one end of an edge involved in a bevel */
|
|
typedef struct VertNetInfo {
|
|
BMVert *prev; /* previous vertex */
|
|
int pass; /* path scanning pass value, for internal calculation */
|
|
int face; /* face index connected to the edge between this and the previous vert */
|
|
int flag; /* flag */
|
|
} VertNetInfo;
|
|
|
|
enum {
|
|
VNINFO_FLAG_IS_MIXFACE = (1 << 0),
|
|
};
|
|
|
|
/**
|
|
* Check if this edge can be used in a path.
|
|
*/
|
|
static bool bm_edge_step_ok(BMEdge *e)
|
|
{
|
|
return BM_elem_flag_test(e, BM_ELEM_TAG) && ((e->l == NULL) || (e->l->radial_next == e->l));
|
|
}
|
|
|
|
static int bm_edge_face(BMEdge *e)
|
|
{
|
|
return e->l ? BM_elem_index_get(e->l->f) : -1;
|
|
}
|
|
|
|
/**
|
|
* Get the next available edge we can use to attempt tp calculate a path from.
|
|
*/
|
|
static BMEdge *bm_edgenet_edge_get_next(
|
|
BMesh *bm,
|
|
LinkNode **edge_queue, BLI_mempool *edge_queue_pool)
|
|
{
|
|
BMEdge *e;
|
|
BMIter iter;
|
|
|
|
while (*edge_queue) {
|
|
e = BLI_linklist_pop_pool(edge_queue, edge_queue_pool);
|
|
if (bm_edge_step_ok(e)) {
|
|
return e;
|
|
}
|
|
}
|
|
|
|
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
|
|
if (bm_edge_step_ok(e)) {
|
|
return e;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* Edge loops are built up using links to the 'prev' member.
|
|
* with each side of the loop having its own pass (negated from the other).
|
|
*
|
|
* This function returns half a loop, the caller needs to run twice to get both sides.
|
|
*/
|
|
static unsigned int bm_edgenet_path_from_pass(
|
|
BMVert *v, LinkNode **v_ls,
|
|
VertNetInfo *vnet_info, BLI_mempool *path_pool)
|
|
{
|
|
VertNetInfo *vn = &vnet_info[BM_elem_index_get(v)];
|
|
const int pass = vn->pass;
|
|
unsigned int v_ls_tot = 0;
|
|
|
|
do {
|
|
BLI_linklist_prepend_pool(v_ls, v, path_pool);
|
|
v_ls_tot += 1;
|
|
v = vn->prev;
|
|
vn = &vnet_info[BM_elem_index_get(v)];
|
|
} while (vn->pass == pass);
|
|
|
|
return v_ls_tot;
|
|
}
|
|
|
|
/**
|
|
* Specialized wrapper for #BM_face_exists_overlap_subset
|
|
* that gets the verts from a path before we allocate it in the correct order.
|
|
*/
|
|
static bool bm_edgenet_path_check_overlap(
|
|
BMVert *v1, BMVert *v2,
|
|
VertNetInfo *vnet_info)
|
|
{
|
|
/* vert order doesn't matter */
|
|
unsigned int v_ls_tot = 0;
|
|
LinkNode *v_ls;
|
|
BMVert *v_pair[2] = {v1, v2};
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
BMVert *v = v_pair[i];
|
|
VertNetInfo *vn = &vnet_info[BM_elem_index_get(v)];
|
|
const int pass = vn->pass;
|
|
do {
|
|
BLI_linklist_prepend_alloca(&v_ls, v);
|
|
v_ls_tot += 1;
|
|
v = vn->prev;
|
|
vn = &vnet_info[BM_elem_index_get(v)];
|
|
} while (vn->pass == pass);
|
|
}
|
|
|
|
if (v_ls_tot) {
|
|
BMVert **vert_arr = BLI_array_alloca(vert_arr, v_ls_tot);
|
|
LinkNode *v_lnk;
|
|
for (i = 0, v_lnk = v_ls; i < v_ls_tot; v_lnk = v_lnk->next, i++) {
|
|
vert_arr[i] = v_lnk->link;
|
|
}
|
|
|
|
return BM_face_exists_overlap_subset(vert_arr, (int)v_ls_tot);
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a face from the path.
|
|
*/
|
|
static BMFace *bm_edgenet_face_from_path(
|
|
BMesh *bm, LinkNode *path, const unsigned int path_len)
|
|
{
|
|
BMFace *f;
|
|
LinkNode *v_lnk;
|
|
unsigned int i;
|
|
unsigned int i_prev;
|
|
|
|
BMVert **vert_arr = BLI_array_alloca(vert_arr, path_len);
|
|
BMEdge **edge_arr = BLI_array_alloca(edge_arr, path_len);
|
|
|
|
for (v_lnk = path, i = 0; v_lnk; v_lnk = v_lnk->next, i++) {
|
|
vert_arr[i] = v_lnk->link;
|
|
}
|
|
|
|
i_prev = path_len - 1;
|
|
for (i = 0; i < path_len; i++) {
|
|
edge_arr[i_prev] = BM_edge_exists(vert_arr[i], vert_arr[i_prev]);
|
|
i_prev = i;
|
|
}
|
|
|
|
/* no need for this, we do overlap checks before allowing the path to be used */
|
|
#if 0
|
|
if (BM_face_exists_multi(vert_arr, edge_arr, path_len)) {
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
f = BM_face_create(bm, vert_arr, edge_arr, (int)path_len, 0);
|
|
|
|
return f;
|
|
}
|
|
|
|
/**
|
|
* Step along the path from \a v_curr to any vert not already in the path.
|
|
*
|
|
* \return The connecting edge if the path is found, otherwise NULL.
|
|
*/
|
|
static BMEdge *bm_edgenet_path_step(
|
|
BMVert *v_curr, LinkNode **v_ls,
|
|
VertNetInfo *vnet_info, BLI_mempool *path_pool)
|
|
{
|
|
const VertNetInfo *vn_curr = &vnet_info[BM_elem_index_get(v_curr)];
|
|
|
|
BMEdge *e;
|
|
BMIter iter;
|
|
unsigned int tot = 0;
|
|
unsigned int v_ls_tot = 0;
|
|
|
|
BM_ITER_ELEM (e, &iter, v_curr, BM_EDGES_OF_VERT) {
|
|
BMVert *v_next = BM_edge_other_vert(e, v_curr);
|
|
if (v_next != vn_curr->prev) {
|
|
if (bm_edge_step_ok(e)) {
|
|
VertNetInfo *vn_next = &vnet_info[BM_elem_index_get(v_next)];
|
|
|
|
/* check we're not looping back on ourselves */
|
|
if (vn_curr->pass != vn_next->pass) {
|
|
|
|
if (vn_curr->pass == -vn_next->pass) {
|
|
if ((vn_curr->flag & VNINFO_FLAG_IS_MIXFACE) ||
|
|
(vn_next->flag & VNINFO_FLAG_IS_MIXFACE))
|
|
{
|
|
/* found connecting edge */
|
|
if (bm_edgenet_path_check_overlap(v_curr, v_next, vnet_info) == false) {
|
|
return e;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
vn_next->face = bm_edge_face(e);
|
|
vn_next->pass = vn_curr->pass;
|
|
vn_next->prev = v_curr;
|
|
|
|
/* flush flag down the path */
|
|
vn_next->flag &= ~VNINFO_FLAG_IS_MIXFACE;
|
|
if ((vn_curr->flag & VNINFO_FLAG_IS_MIXFACE) ||
|
|
(vn_next->face == -1) ||
|
|
(vn_next->face != vn_curr->face))
|
|
{
|
|
vn_next->flag |= VNINFO_FLAG_IS_MIXFACE;
|
|
}
|
|
|
|
/* add to the list! */
|
|
BLI_linklist_prepend_pool(v_ls, v_next, path_pool);
|
|
v_ls_tot += 1;
|
|
}
|
|
}
|
|
}
|
|
tot += 1;
|
|
}
|
|
}
|
|
|
|
/* trick to walk along wire-edge paths */
|
|
if (v_ls_tot == 1 && tot == 1) {
|
|
v_curr = BLI_linklist_pop_pool(v_ls, path_pool);
|
|
bm_edgenet_path_step(v_curr, v_ls, vnet_info, path_pool);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Given an edge, find the first path that can form a face.
|
|
*
|
|
* \return A linked list of verts.
|
|
*/
|
|
static LinkNode *bm_edgenet_path_calc(
|
|
BMEdge *e, const int pass_nr, const unsigned int path_cost_max,
|
|
unsigned int *r_path_len, unsigned int *r_path_cost,
|
|
VertNetInfo *vnet_info, BLI_mempool *path_pool)
|
|
{
|
|
VertNetInfo *vn_1, *vn_2;
|
|
const int f_index = bm_edge_face(e);
|
|
bool found;
|
|
|
|
LinkNode *v_ls_prev = NULL;
|
|
LinkNode *v_ls_next = NULL;
|
|
|
|
unsigned int path_cost_accum = 0;
|
|
|
|
BLI_assert(bm_edge_step_ok(e));
|
|
|
|
*r_path_len = 0;
|
|
*r_path_cost = 0;
|
|
|
|
vn_1 = &vnet_info[BM_elem_index_get(e->v1)];
|
|
vn_2 = &vnet_info[BM_elem_index_get(e->v2)];
|
|
|
|
vn_1->pass = pass_nr;
|
|
vn_2->pass = -pass_nr;
|
|
|
|
vn_1->prev = e->v2;
|
|
vn_2->prev = e->v1;
|
|
|
|
vn_1->face =
|
|
vn_2->face = f_index;
|
|
|
|
vn_1->flag =
|
|
vn_2->flag = (f_index == -1) ? VNINFO_FLAG_IS_MIXFACE : 0;
|
|
|
|
/* prime the searchlist */
|
|
BLI_linklist_prepend_pool(&v_ls_prev, e->v1, path_pool);
|
|
BLI_linklist_prepend_pool(&v_ls_prev, e->v2, path_pool);
|
|
|
|
do {
|
|
found = false;
|
|
|
|
/* no point to continue, we're over budget */
|
|
if (path_cost_accum >= path_cost_max) {
|
|
BLI_linklist_free_pool(v_ls_next, NULL, path_pool);
|
|
BLI_linklist_free_pool(v_ls_prev, NULL, path_pool);
|
|
return NULL;
|
|
}
|
|
|
|
while (v_ls_prev) {
|
|
const LinkNode *v_ls_next_old = v_ls_next;
|
|
BMVert *v = BLI_linklist_pop_pool(&v_ls_prev, path_pool);
|
|
BMEdge *e_found = bm_edgenet_path_step(v, &v_ls_next, vnet_info, path_pool);
|
|
|
|
if (e_found) {
|
|
LinkNode *path = NULL;
|
|
unsigned int path_len;
|
|
BLI_linklist_free_pool(v_ls_next, NULL, path_pool);
|
|
BLI_linklist_free_pool(v_ls_prev, NULL, path_pool);
|
|
|
|
// BLI_assert(BLI_mempool_count(path_pool) == 0);
|
|
|
|
path_len = bm_edgenet_path_from_pass(e_found->v1, &path, vnet_info, path_pool);
|
|
BLI_linklist_reverse(&path);
|
|
path_len += bm_edgenet_path_from_pass(e_found->v2, &path, vnet_info, path_pool);
|
|
*r_path_len = path_len;
|
|
*r_path_cost = path_cost_accum;
|
|
return path;
|
|
}
|
|
else {
|
|
/* check if a change was made */
|
|
if (v_ls_next_old != v_ls_next) {
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
}
|
|
BLI_assert(v_ls_prev == NULL);
|
|
|
|
path_cost_accum++;
|
|
|
|
/* swap */
|
|
v_ls_prev = v_ls_next;
|
|
v_ls_next = NULL;
|
|
|
|
} while (found);
|
|
|
|
BLI_assert(v_ls_prev == NULL);
|
|
BLI_assert(v_ls_next == NULL);
|
|
|
|
/* tag not to search again */
|
|
BM_elem_flag_disable(e, BM_ELEM_TAG);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for #bm_edgenet_path_calc which ensures all included edges
|
|
* _don't_ have a better option.
|
|
*/
|
|
static LinkNode *bm_edgenet_path_calc_best(
|
|
BMEdge *e, int *pass_nr, unsigned int path_cost_max,
|
|
unsigned int *r_path_len, unsigned int *r_path_cost,
|
|
VertNetInfo *vnet_info, BLI_mempool *path_pool)
|
|
{
|
|
LinkNode *path;
|
|
unsigned int path_cost;
|
|
|
|
path = bm_edgenet_path_calc(e, *pass_nr, path_cost_max,
|
|
r_path_len, &path_cost,
|
|
vnet_info, path_pool);
|
|
(*pass_nr)++;
|
|
|
|
if (path == NULL) {
|
|
return NULL;
|
|
}
|
|
else if (path_cost <= 1) {
|
|
/* any face that takes 1-2 iterations to find we consider valid */
|
|
return path;
|
|
}
|
|
else {
|
|
/* Check every edge to see if any can give a better path.
|
|
* This avoids very strange/long paths from being created. */
|
|
|
|
const unsigned int path_len = *r_path_len;
|
|
unsigned int i, i_prev;
|
|
BMVert **vert_arr = BLI_array_alloca(vert_arr, path_len);
|
|
LinkNode *v_lnk;
|
|
|
|
for (v_lnk = path, i = 0; v_lnk; v_lnk = v_lnk->next, i++) {
|
|
vert_arr[i] = v_lnk->link;
|
|
}
|
|
|
|
i_prev = path_len - 1;
|
|
for (i = 0; i < path_len; i++) {
|
|
BMEdge *e_other = BM_edge_exists(vert_arr[i], vert_arr[i_prev]);
|
|
if (e_other != e) {
|
|
LinkNode *path_test;
|
|
unsigned int path_len_test;
|
|
unsigned int path_cost_test;
|
|
|
|
path_test = bm_edgenet_path_calc(e_other, *pass_nr, path_cost,
|
|
&path_len_test, &path_cost_test,
|
|
vnet_info, path_pool);
|
|
(*pass_nr)++;
|
|
|
|
if (path_test) {
|
|
BLI_assert(path_cost_test < path_cost);
|
|
|
|
BLI_linklist_free_pool(path, NULL, path_pool);
|
|
path = path_test;
|
|
*r_path_len = path_len_test;
|
|
*r_path_cost = path_cost_test;
|
|
path_cost = path_cost_test;
|
|
}
|
|
}
|
|
|
|
i_prev = i;
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* Fill in faces from an edgenet made up of boundary and wire edges.
|
|
*
|
|
* \note New faces currently don't have their normals calculated and are flipped randomly.
|
|
* The caller needs to flip faces correctly.
|
|
*
|
|
* \param bm The mesh to operate on.
|
|
* \param use_edge_tag Only fill tagged edges.
|
|
* \param face_oflag if nonzero, apply all new faces with this bmo flag.
|
|
*/
|
|
void BM_mesh_edgenet(BMesh *bm, const bool use_edge_tag, const short face_oflag)
|
|
{
|
|
VertNetInfo *vnet_info = MEM_callocN(sizeof(*vnet_info) * (size_t)bm->totvert, __func__);
|
|
BLI_mempool *edge_queue_pool = BLI_mempool_create(sizeof(LinkNode), 1, 512, 0);
|
|
BLI_mempool *path_pool = BLI_mempool_create(sizeof(LinkNode), 1, 512, 0);
|
|
LinkNode *edge_queue = NULL;
|
|
|
|
BMEdge *e;
|
|
BMIter iter;
|
|
|
|
int pass_nr = 1;
|
|
|
|
if (use_edge_tag == false) {
|
|
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
|
|
BM_elem_flag_enable(e, BM_ELEM_TAG);
|
|
BM_elem_flag_set(e, BM_ELEM_TAG, bm_edge_step_ok(e));
|
|
}
|
|
}
|
|
|
|
BM_mesh_elem_index_ensure(bm, BM_VERT | BM_FACE);
|
|
|
|
while (true) {
|
|
LinkNode *path = NULL;
|
|
unsigned int path_len;
|
|
unsigned int path_cost;
|
|
|
|
e = bm_edgenet_edge_get_next(bm, &edge_queue, edge_queue_pool);
|
|
if (e == NULL) {
|
|
break;
|
|
}
|
|
|
|
BLI_assert(bm_edge_step_ok(e) == true);
|
|
|
|
path = bm_edgenet_path_calc_best(e, &pass_nr, UINT_MAX,
|
|
&path_len, &path_cost,
|
|
vnet_info, path_pool);
|
|
|
|
if (path) {
|
|
BMFace *f = bm_edgenet_face_from_path(bm, path, path_len);
|
|
/* queue edges to operate on */
|
|
BMLoop *l_first, *l_iter;
|
|
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
|
|
do {
|
|
if (bm_edge_step_ok(l_iter->e)) {
|
|
BLI_linklist_prepend_pool(&edge_queue, l_iter->e, edge_queue_pool);
|
|
}
|
|
} while ((l_iter = l_iter->next) != l_first);
|
|
|
|
if (face_oflag) {
|
|
BMO_elem_flag_enable(bm, f, face_oflag);
|
|
}
|
|
|
|
/* the face index only needs to be unique, not kept valid */
|
|
BM_elem_index_set(f, bm->totface - 1); /* set_dirty */
|
|
}
|
|
|
|
BLI_linklist_free_pool(path, NULL, path_pool);
|
|
BLI_assert(BLI_mempool_count(path_pool) == 0);
|
|
}
|
|
|
|
bm->elem_index_dirty |= BM_FACE;
|
|
|
|
BLI_mempool_destroy(edge_queue_pool);
|
|
BLI_mempool_destroy(path_pool);
|
|
MEM_freeN(vnet_info);
|
|
}
|