This repository has been archived on 2023-10-09. You can view files and clone it, but cannot push or open issues or pull requests.
Files
blender-archive/source/blender/bmesh/operators/bmo_removedoubles.c
Campbell Barton 55d3277d0c BMesh: use a smaller dist3 threshold
Matches array modifier remove-doubles
2014-08-14 12:49:02 +10:00

652 lines
17 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): Joseph Eagar.
*
* ***** END GPL LICENSE BLOCK *****
*/
/** \file blender/bmesh/operators/bmo_removedoubles.c
* \ingroup bmesh
*
* Welding and merging functionality.
*/
#include "MEM_guardedalloc.h"
#include "BLI_math.h"
#include "BLI_array.h"
#include "BLI_alloca.h"
#include "BLI_stackdefines.h"
#include "BKE_customdata.h"
#include "bmesh.h"
#include "intern/bmesh_operators_private.h"
static void remdoubles_splitface(BMFace *f, BMesh *bm, BMOperator *op, BMOpSlot *slot_targetmap)
{
BMIter liter;
BMLoop *l, *l_tar, *l_double;
bool split = false;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BMVert *v_tar = BMO_slot_map_elem_get(slot_targetmap, l->v);
/* ok: if v_tar is NULL (e.g. not in the map) then it's
* a target vert, otherwise it's a double */
if (v_tar) {
l_tar = BM_face_vert_share_loop(f, v_tar);
if (l_tar && (l_tar != l) && !BM_loop_is_adjacent(l_tar, l)) {
l_double = l;
split = true;
break;
}
}
}
if (split) {
BMLoop *l_new;
BMFace *f_new;
f_new = BM_face_split(bm, f, l_double, l_tar, &l_new, NULL, false);
remdoubles_splitface(f, bm, op, slot_targetmap);
remdoubles_splitface(f_new, bm, op, slot_targetmap);
}
}
#define ELE_DEL 1
#define EDGE_COL 2
#define FACE_MARK 2
/**
* helper function for bmo_weld_verts_exec so we can use stack memory
*/
static void remdoubles_createface(BMesh *bm, BMFace *f, BMOpSlot *slot_targetmap)
{
BMIter liter;
BMFace *f_new;
BMEdge *e_new;
BMLoop *l;
BMEdge **edges = BLI_array_alloca(edges, f->len);
BMLoop **loops = BLI_array_alloca(loops, f->len);
STACK_DECLARE(edges);
STACK_DECLARE(loops);
STACK_INIT(edges, f->len);
STACK_INIT(loops, f->len);
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BMVert *v1 = l->v;
BMVert *v2 = l->next->v;
const bool is_del_v1 = BMO_elem_flag_test_bool(bm, v1, ELE_DEL);
const bool is_del_v2 = BMO_elem_flag_test_bool(bm, v2, ELE_DEL);
/* only search for a new edge if one of the verts is mapped */
if (is_del_v1 || is_del_v2) {
if (is_del_v1)
v1 = BMO_slot_map_elem_get(slot_targetmap, v1);
if (is_del_v2)
v2 = BMO_slot_map_elem_get(slot_targetmap, v2);
e_new = (v1 != v2) ? BM_edge_exists(v1, v2) : NULL;
}
else {
e_new = l->e;
}
if (e_new) {
unsigned int i;
for (i = 0; i < STACK_SIZE(edges); i++) {
if (edges[i] == e_new) {
break;
}
}
if (UNLIKELY(i != STACK_SIZE(edges))) {
continue;
}
STACK_PUSH(edges, e_new);
STACK_PUSH(loops, l);
}
}
if (STACK_SIZE(edges) >= 3) {
BMVert *v1 = loops[0]->v;
BMVert *v2 = loops[1]->v;
if (BMO_elem_flag_test(bm, v1, ELE_DEL)) {
v1 = BMO_slot_map_elem_get(slot_targetmap, v1);
}
if (BMO_elem_flag_test(bm, v2, ELE_DEL)) {
v2 = BMO_slot_map_elem_get(slot_targetmap, v2);
}
f_new = BM_face_create_ngon(bm, v1, v2, edges, STACK_SIZE(edges), f, BM_CREATE_NO_DOUBLE);
BLI_assert(f_new != f);
if (f_new) {
unsigned int i;
BM_ITER_ELEM_INDEX (l, &liter, f_new, BM_LOOPS_OF_FACE, i) {
BM_elem_attrs_copy(bm, bm, loops[i], l);
}
}
}
}
/**
* \note with 'targetmap', multiple 'keys' are currently supported, though no callers should be using.
* (because slot maps currently use GHash without the GHASH_FLAG_ALLOW_DUPES flag set)
*
*/
void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
{
BMIter iter, liter;
BMVert *v1, *v2;
BMEdge *e;
BMLoop *l;
BMFace *f;
BMOpSlot *slot_targetmap = BMO_slot_get(op->slots_in, "targetmap");
/* mark merge verts for deletion */
BM_ITER_MESH (v1, &iter, bm, BM_VERTS_OF_MESH) {
if ((v2 = BMO_slot_map_elem_get(slot_targetmap, v1))) {
BMO_elem_flag_enable(bm, v1, ELE_DEL);
/* merge the vertex flags, else we get randomly selected/unselected verts */
BM_elem_flag_merge(v1, v2);
}
}
/* check if any faces are getting their own corners merged
* together, split face if so */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
remdoubles_splitface(f, bm, op, slot_targetmap);
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
const bool is_del_v1 = BMO_elem_flag_test_bool(bm, (v1 = e->v1), ELE_DEL);
const bool is_del_v2 = BMO_elem_flag_test_bool(bm, (v2 = e->v2), ELE_DEL);
if (is_del_v1 || is_del_v2) {
if (is_del_v1)
v1 = BMO_slot_map_elem_get(slot_targetmap, v1);
if (is_del_v2)
v2 = BMO_slot_map_elem_get(slot_targetmap, v2);
if (v1 == v2) {
BMO_elem_flag_enable(bm, e, EDGE_COL);
}
else if (!BM_edge_exists(v1, v2)) {
BM_edge_create(bm, v1, v2, e, BM_CREATE_NO_DOUBLE);
}
BMO_elem_flag_enable(bm, e, ELE_DEL);
}
}
/* BMESH_TODO, stop abusing face index here */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BM_elem_index_set(f, 0); /* set_dirty! */
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (BMO_elem_flag_test(bm, l->v, ELE_DEL)) {
BMO_elem_flag_enable(bm, f, FACE_MARK | ELE_DEL);
}
if (BMO_elem_flag_test(bm, l->e, EDGE_COL)) {
BM_elem_index_set(f, BM_elem_index_get(f) + 1); /* set_dirty! */
}
}
}
bm->elem_index_dirty |= BM_FACE | BM_LOOP;
/* faces get "modified" by creating new faces here, then at the
* end the old faces are deleted */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (!BMO_elem_flag_test(bm, f, FACE_MARK))
continue;
if (f->len - BM_elem_index_get(f) < 3) {
BMO_elem_flag_enable(bm, f, ELE_DEL);
continue;
}
remdoubles_createface(bm, f, slot_targetmap);
}
BMO_mesh_delete_oflag_context(bm, ELE_DEL, DEL_ONLYTAGGED);
}
static int vergaverco(const void *e1, const void *e2)
{
const BMVert *v1 = *(void **)e1, *v2 = *(void **)e2;
float x1 = v1->co[0] + v1->co[1] + v1->co[2];
float x2 = v2->co[0] + v2->co[1] + v2->co[2];
if (x1 > x2) return 1;
else if (x1 < x2) return -1;
else return 0;
}
// #define VERT_TESTED 1 // UNUSED
#define VERT_DOUBLE 2
#define VERT_TARGET 4
#define VERT_KEEP 8
// #define VERT_MARK 16 // UNUSED
#define VERT_IN 32
#define EDGE_MARK 1
void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v, *vert_snap;
BMLoop *l, *firstl = NULL;
float fac;
int i, tot;
vert_snap = BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_snap"));
tot = BM_vert_face_count(vert_snap);
if (!tot)
return;
fac = 1.0f / tot;
BM_ITER_ELEM (l, &iter, vert_snap, BM_LOOPS_OF_VERT) {
if (!firstl) {
firstl = l;
}
for (i = 0; i < bm->ldata.totlayer; i++) {
if (CustomData_layer_has_math(&bm->ldata, i)) {
int type = bm->ldata.layers[i].type;
void *e1, *e2;
e1 = CustomData_bmesh_get_layer_n(&bm->ldata, firstl->head.data, i);
e2 = CustomData_bmesh_get_layer_n(&bm->ldata, l->head.data, i);
CustomData_data_multiply(type, e2, fac);
if (l != firstl)
CustomData_data_add(type, e1, e2);
}
}
}
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
if (l == firstl) {
continue;
}
CustomData_bmesh_copy_data(&bm->ldata, &bm->ldata, firstl->head.data, &l->head.data);
}
}
}
void bmo_average_vert_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v;
BMLoop *l /* , *firstl = NULL */;
CDBlockBytes min, max;
void *block;
int i, type;
for (i = 0; i < bm->ldata.totlayer; i++) {
if (!CustomData_layer_has_math(&bm->ldata, i))
continue;
type = bm->ldata.layers[i].type;
CustomData_data_initminmax(type, &min, &max);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
block = CustomData_bmesh_get_layer_n(&bm->ldata, l->head.data, i);
CustomData_data_dominmax(type, block, &min, &max);
}
}
CustomData_data_multiply(type, &min, 0.5f);
CustomData_data_multiply(type, &max, 0.5f);
CustomData_data_add(type, &min, &max);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
block = CustomData_bmesh_get_layer_n(&bm->ldata, l->head.data, i);
CustomData_data_copy_value(type, &min, block);
}
}
}
}
void bmo_pointmerge_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOIter siter;
BMVert *v, *vert_snap = NULL;
float vec[3];
BMOpSlot *slot_targetmap;
BMO_slot_vec_get(op->slots_in, "merge_co", vec);
//BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (!vert_snap) {
vert_snap = v;
copy_v3_v3(vert_snap->co, vec);
}
else {
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v, vert_snap);
}
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}
void bmo_collapse_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMWalker walker;
BMIter iter;
BMEdge *e, **edges = NULL;
BLI_array_declare(edges);
float min[3], max[3], center[3];
unsigned int i, tot;
BMOpSlot *slot_targetmap;
BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
BMW_init(&walker, bm, BMW_VERT_SHELL,
BMW_MASK_NOP, EDGE_MARK, BMW_MASK_NOP,
BMW_FLAG_NOP, /* no need to use BMW_FLAG_TEST_HIDDEN, already marked data */
BMW_NIL_LAY);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BMVert *v_tar;
if (!BMO_elem_flag_test(bm, e, EDGE_MARK))
continue;
BLI_array_empty(edges);
INIT_MINMAX(min, max);
for (e = BMW_begin(&walker, e->v1), tot = 0; e; e = BMW_step(&walker), tot++) {
BLI_array_grow_one(edges);
edges[tot] = e;
minmax_v3v3_v3(min, max, e->v1->co);
minmax_v3v3_v3(min, max, e->v2->co);
/* prevent adding to slot_targetmap multiple times */
BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
}
mid_v3_v3v3(center, min, max);
/* snap edges to a point. for initial testing purposes anyway */
v_tar = edges[0]->v1;
for (i = 0; i < tot; i++) {
unsigned int j;
for (j = 0; j < 2; j++) {
BMVert *v_src = *((&edges[i]->v1) + j);
copy_v3_v3(v_src->co, center);
if ((v_src != v_tar) && !BM_elem_flag_test(v_src, BM_ELEM_TAG)) {
BM_elem_flag_enable(v_src, BM_ELEM_TAG);
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_src, v_tar);
}
}
}
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
BMW_end(&walker);
BLI_array_free(edges);
}
/* uv collapse function */
static void bmo_collapsecon_do_layer(BMesh *bm, const int layer, const short oflag)
{
BMIter iter, liter;
BMFace *f;
BMLoop *l, *l2;
BMWalker walker;
void **blocks = NULL;
BLI_array_declare(blocks);
CDBlockBytes min, max;
int i, tot, type = bm->ldata.layers[layer].type;
BMW_init(&walker, bm, BMW_LOOPDATA_ISLAND,
BMW_MASK_NOP, oflag, BMW_MASK_NOP,
BMW_FLAG_NOP, /* no need to use BMW_FLAG_TEST_HIDDEN, already marked data */
layer);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (BMO_elem_flag_test(bm, l->e, oflag)) {
/* walk */
BLI_array_empty(blocks);
CustomData_data_initminmax(type, &min, &max);
for (l2 = BMW_begin(&walker, l), tot = 0; l2; l2 = BMW_step(&walker), tot++) {
BLI_array_grow_one(blocks);
blocks[tot] = CustomData_bmesh_get_layer_n(&bm->ldata, l2->head.data, layer);
CustomData_data_dominmax(type, blocks[tot], &min, &max);
}
if (tot) {
CustomData_data_multiply(type, &min, 0.5f);
CustomData_data_multiply(type, &max, 0.5f);
CustomData_data_add(type, &min, &max);
/* snap CD (uv, vcol) points to their centroid */
for (i = 0; i < tot; i++) {
CustomData_data_copy_value(type, &min, blocks[i]);
}
}
}
}
}
BMW_end(&walker);
BLI_array_free(blocks);
}
void bmo_collapse_uvs_exec(BMesh *bm, BMOperator *op)
{
const short oflag = EDGE_MARK;
int i;
/* check flags dont change once set */
#ifndef NDEBUG
int tot_test;
#endif
if (!CustomData_has_math(&bm->ldata)) {
return;
}
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, oflag);
#ifndef NDEBUG
tot_test = BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, oflag, true);
#endif
for (i = 0; i < bm->ldata.totlayer; i++) {
if (CustomData_layer_has_math(&bm->ldata, i))
bmo_collapsecon_do_layer(bm, i, oflag);
}
#ifndef NDEBUG
BLI_assert(tot_test == BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, EDGE_MARK, true));
#endif
}
static void bmesh_find_doubles_common(BMesh *bm, BMOperator *op,
BMOperator *optarget, BMOpSlot *optarget_slot)
{
BMVert **verts;
int verts_len;
int i, j, keepvert = 0;
const float dist = BMO_slot_float_get(op->slots_in, "dist");
const float dist_sq = dist * dist;
const float dist3 = (M_SQRT3 + 0.00005f) * dist; /* Just above sqrt(3) */
/* Test whether keep_verts arg exists and is non-empty */
if (BMO_slot_exists(op->slots_in, "keep_verts")) {
BMOIter oiter;
keepvert = BMO_iter_new(&oiter, op->slots_in, "keep_verts", BM_VERT) != NULL;
}
/* get the verts as an array we can sort */
verts = BMO_slot_as_arrayN(op->slots_in, "verts", &verts_len);
/* sort by vertex coordinates added together */
qsort(verts, verts_len, sizeof(BMVert *), vergaverco);
/* Flag keep_verts */
if (keepvert) {
BMO_slot_buffer_flag_enable(bm, op->slots_in, "keep_verts", BM_VERT, VERT_KEEP);
}
for (i = 0; i < verts_len; i++) {
BMVert *v_check = verts[i];
if (BMO_elem_flag_test(bm, v_check, VERT_DOUBLE | VERT_TARGET)) {
continue;
}
for (j = i + 1; j < verts_len; j++) {
BMVert *v_other = verts[j];
/* a match has already been found, (we could check which is best, for now don't) */
if (BMO_elem_flag_test(bm, v_other, VERT_DOUBLE | VERT_TARGET)) {
continue;
}
/* Compare sort values of the verts using 3x tolerance (allowing for the tolerance
* on each of the three axes). This avoids the more expensive length comparison
* for most vertex pairs. */
if ((v_other->co[0] + v_other->co[1] + v_other->co[2]) -
(v_check->co[0] + v_check->co[1] + v_check->co[2]) > dist3)
{
break;
}
if (keepvert) {
if (BMO_elem_flag_test(bm, v_other, VERT_KEEP) == BMO_elem_flag_test(bm, v_check, VERT_KEEP))
continue;
}
if (compare_len_squared_v3v3(v_check->co, v_other->co, dist_sq)) {
/* If one vert is marked as keep, make sure it will be the target */
if (BMO_elem_flag_test(bm, v_other, VERT_KEEP)) {
SWAP(BMVert *, v_check, v_other);
}
BMO_elem_flag_enable(bm, v_other, VERT_DOUBLE);
BMO_elem_flag_enable(bm, v_check, VERT_TARGET);
BMO_slot_map_elem_insert(optarget, optarget_slot, v_other, v_check);
}
}
}
MEM_freeN(verts);
}
void bmo_remove_doubles_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOpSlot *slot_targetmap;
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
bmesh_find_doubles_common(bm, op,
&weldop, slot_targetmap);
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}
void bmo_find_doubles_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *slot_targetmap_out;
slot_targetmap_out = BMO_slot_get(op->slots_out, "targetmap.out");
bmesh_find_doubles_common(bm, op,
op, slot_targetmap_out);
}
void bmo_automerge_exec(BMesh *bm, BMOperator *op)
{
BMOperator findop, weldop;
BMIter viter;
BMVert *v;
/* The "verts" input sent to this op is the set of verts that
* can be merged away into any other verts. Mark all other verts
* as VERT_KEEP. */
BMO_slot_buffer_flag_enable(bm, op->slots_in, "verts", BM_VERT, VERT_IN);
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
if (!BMO_elem_flag_test(bm, v, VERT_IN)) {
BMO_elem_flag_enable(bm, v, VERT_KEEP);
}
}
/* Search for doubles among all vertices, but only merge non-VERT_KEEP
* vertices into VERT_KEEP vertices. */
BMO_op_initf(bm, &findop, op->flag, "find_doubles verts=%av keep_verts=%fv", VERT_KEEP);
BMO_slot_copy(op, slots_in, "dist",
&findop, slots_in, "dist");
BMO_op_exec(bm, &findop);
/* weld the vertices */
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
BMO_slot_copy(&findop, slots_out, "targetmap.out",
&weldop, slots_in, "targetmap");
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &findop);
BMO_op_finish(bm, &weldop);
}