From feae12a83a36bbfe61319660d08a7b4b9052c209 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Sat, 1 Oct 2022 22:12:55 -0500 Subject: Cleanup: Move more files using mesh runtime data to C++ In preparation for moving mesh runtime data out of DNA. --- source/blender/blenkernel/CMakeLists.txt | 2 +- .../blender/blenkernel/intern/mball_tessellate.c | 1485 ------ .../blender/blenkernel/intern/mball_tessellate.cc | 1500 ++++++ source/blender/editors/include/ED_mesh.h | 2 +- source/blender/editors/include/ED_view3d.h | 3 +- source/blender/editors/mesh/CMakeLists.txt | 2 +- source/blender/editors/mesh/editmesh_loopcut.c | 2 +- source/blender/editors/mesh/editmesh_select.c | 5344 ------------------- source/blender/editors/mesh/editmesh_select.cc | 5353 ++++++++++++++++++++ source/blender/editors/mesh/mesh_intern.h | 2 +- source/blender/editors/space_view3d/CMakeLists.txt | 2 +- .../editors/space_view3d/view3d_iterators.c | 883 ---- .../editors/space_view3d/view3d_iterators.cc | 886 ++++ 13 files changed, 7747 insertions(+), 7719 deletions(-) delete mode 100644 source/blender/blenkernel/intern/mball_tessellate.c create mode 100644 source/blender/blenkernel/intern/mball_tessellate.cc delete mode 100644 source/blender/editors/mesh/editmesh_select.c create mode 100644 source/blender/editors/mesh/editmesh_select.cc delete mode 100644 source/blender/editors/space_view3d/view3d_iterators.c create mode 100644 source/blender/editors/space_view3d/view3d_iterators.cc (limited to 'source/blender') diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt index 627e34be424..d0e2d945c2f 100644 --- a/source/blender/blenkernel/CMakeLists.txt +++ b/source/blender/blenkernel/CMakeLists.txt @@ -191,7 +191,7 @@ set(SRC intern/mask_rasterize.c intern/material.c intern/mball.cc - intern/mball_tessellate.c + intern/mball_tessellate.cc intern/mesh.cc intern/mesh_boolean_convert.cc intern/mesh_calc_edges.cc diff --git a/source/blender/blenkernel/intern/mball_tessellate.c b/source/blender/blenkernel/intern/mball_tessellate.c deleted file mode 100644 index 1bb50c2c97f..00000000000 --- a/source/blender/blenkernel/intern/mball_tessellate.c +++ /dev/null @@ -1,1485 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ - -/** \file - * \ingroup bke - */ - -#include -#include -#include -#include -#include -#include - -#include "MEM_guardedalloc.h" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_meta_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_memarena.h" -#include "BLI_string_utils.h" -#include "BLI_utildefines.h" - -#include "BKE_displist.h" -#include "BKE_global.h" -#include "BKE_lib_id.h" -#include "BKE_mball_tessellate.h" /* own include */ -#include "BKE_mesh.h" -#include "BKE_object.h" -#include "BKE_scene.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_query.h" - -#include "BLI_strict_flags.h" - -/* experimental (faster) normal calculation */ -// #define USE_ACCUM_NORMAL - -#define MBALL_ARRAY_LEN_INIT 4096 - -/* Data types */ - -typedef struct corner { /* corner of a cube */ - int i, j, k; /* (i, j, k) is index within lattice */ - float co[3], value; /* location and function value */ - struct corner *next; -} CORNER; - -typedef struct cube { /* partitioning cell (cube) */ - int i, j, k; /* lattice location of cube */ - CORNER *corners[8]; /* eight corners */ -} CUBE; - -typedef struct cubes { /* linked list of cubes acting as stack */ - CUBE cube; /* a single cube */ - struct cubes *next; /* remaining elements */ -} CUBES; - -typedef struct centerlist { /* list of cube locations */ - int i, j, k; /* cube location */ - struct centerlist *next; /* remaining elements */ -} CENTERLIST; - -typedef struct edgelist { /* list of edges */ - int i1, j1, k1, i2, j2, k2; /* edge corner ids */ - int vid; /* vertex id */ - struct edgelist *next; /* remaining elements */ -} EDGELIST; - -typedef struct intlist { /* list of integers */ - int i; /* an integer */ - struct intlist *next; /* remaining elements */ -} INTLIST; - -typedef struct intlists { /* list of list of integers */ - INTLIST *list; /* a list of integers */ - struct intlists *next; /* remaining elements */ -} INTLISTS; - -typedef struct Box { /* an AABB with pointer to metalelem */ - float min[3], max[3]; - const MetaElem *ml; -} Box; - -typedef struct MetaballBVHNode { /* BVH node */ - Box bb[2]; /* AABB of children */ - struct MetaballBVHNode *child[2]; -} MetaballBVHNode; - -typedef struct process { /* parameters, storage */ - float thresh, size; /* mball threshold, single cube size */ - float delta; /* small delta for calculating normals */ - uint converge_res; /* converge procedure resolution (more = slower) */ - - MetaElem **mainb; /* array of all metaelems */ - uint totelem, mem; /* number of metaelems */ - - MetaballBVHNode metaball_bvh; /* The simplest bvh */ - Box allbb; /* Bounding box of all metaelems */ - - MetaballBVHNode **bvh_queue; /* Queue used during bvh traversal */ - uint bvh_queue_size; - - CUBES *cubes; /* stack of cubes waiting for polygonization */ - CENTERLIST **centers; /* cube center hash table */ - CORNER **corners; /* corner value hash table */ - EDGELIST **edges; /* edge and vertex id hash table */ - - int (*indices)[4]; /* output indices */ - uint totindex; /* size of memory allocated for indices */ - uint curindex; /* number of currently added indices */ - - float (*co)[3], (*no)[3]; /* surface vertices - positions and normals */ - uint totvertex; /* memory size */ - uint curvertex; /* currently added vertices */ - - /* memory allocation from common pool */ - MemArena *pgn_elements; -} PROCESS; - -/* Forward declarations */ -static int vertid(PROCESS *process, const CORNER *c1, const CORNER *c2); -static void add_cube(PROCESS *process, int i, int j, int k); -static void make_face(PROCESS *process, int i1, int i2, int i3, int i4); -static void converge(PROCESS *process, const CORNER *c1, const CORNER *c2, float r_p[3]); - -/* ******************* SIMPLE BVH ********************* */ - -static void make_box_union(const BoundBox *a, const Box *b, Box *r_out) -{ - r_out->min[0] = min_ff(a->vec[0][0], b->min[0]); - r_out->min[1] = min_ff(a->vec[0][1], b->min[1]); - r_out->min[2] = min_ff(a->vec[0][2], b->min[2]); - - r_out->max[0] = max_ff(a->vec[6][0], b->max[0]); - r_out->max[1] = max_ff(a->vec[6][1], b->max[1]); - r_out->max[2] = max_ff(a->vec[6][2], b->max[2]); -} - -static void make_box_from_metaelem(Box *r, const MetaElem *ml) -{ - copy_v3_v3(r->max, ml->bb->vec[6]); - copy_v3_v3(r->min, ml->bb->vec[0]); - r->ml = ml; -} - -/** - * Partitions part of #process.mainb array [start, end) along axis s. Returns i, - * where centroids of elements in the [start, i) segment lie "on the right side" of div, - * and elements in the [i, end) segment lie "on the left" - */ -static uint partition_mainb(MetaElem **mainb, uint start, uint end, uint s, float div) -{ - uint i = start, j = end - 1; - div *= 2.0f; - - while (1) { - while (i < j && div > (mainb[i]->bb->vec[6][s] + mainb[i]->bb->vec[0][s])) { - i++; - } - while (j > i && div < (mainb[j]->bb->vec[6][s] + mainb[j]->bb->vec[0][s])) { - j--; - } - - if (i >= j) { - break; - } - - SWAP(MetaElem *, mainb[i], mainb[j]); - i++; - j--; - } - - if (i == start) { - i++; - } - - return i; -} - -/** - * Recursively builds a BVH, dividing elements along the middle of the longest axis of allbox. - */ -static void build_bvh_spatial( - PROCESS *process, MetaballBVHNode *node, uint start, uint end, const Box *allbox) -{ - uint part, j, s; - float dim[3], div; - - /* Maximum bvh queue size is number of nodes which are made, equals calls to this function. */ - process->bvh_queue_size++; - - dim[0] = allbox->max[0] - allbox->min[0]; - dim[1] = allbox->max[1] - allbox->min[1]; - dim[2] = allbox->max[2] - allbox->min[2]; - - s = 0; - if (dim[1] > dim[0] && dim[1] > dim[2]) { - s = 1; - } - else if (dim[2] > dim[1] && dim[2] > dim[0]) { - s = 2; - } - - div = allbox->min[s] + (dim[s] / 2.0f); - - part = partition_mainb(process->mainb, start, end, s, div); - - make_box_from_metaelem(&node->bb[0], process->mainb[start]); - node->child[0] = NULL; - - if (part > start + 1) { - for (j = start; j < part; j++) { - make_box_union(process->mainb[j]->bb, &node->bb[0], &node->bb[0]); - } - - node->child[0] = BLI_memarena_alloc(process->pgn_elements, sizeof(MetaballBVHNode)); - build_bvh_spatial(process, node->child[0], start, part, &node->bb[0]); - } - - node->child[1] = NULL; - if (part < end) { - make_box_from_metaelem(&node->bb[1], process->mainb[part]); - - if (part < end - 1) { - for (j = part; j < end; j++) { - make_box_union(process->mainb[j]->bb, &node->bb[1], &node->bb[1]); - } - - node->child[1] = BLI_memarena_alloc(process->pgn_elements, sizeof(MetaballBVHNode)); - build_bvh_spatial(process, node->child[1], part, end, &node->bb[1]); - } - } - else { - INIT_MINMAX(node->bb[1].min, node->bb[1].max); - } -} - -/* ******************** ARITH ************************* */ - -/** - * BASED AT CODE (but mostly rewritten) : - * C code from the article - * "An Implicit Surface Polygonizer" - * by Jules Bloomenthal - * in "Graphics Gems IV", Academic Press, 1994 - * - * Authored by Jules Bloomenthal, Xerox PARC. - * Copyright (c) Xerox Corporation, 1991. All rights reserved. - * Permission is granted to reproduce, use and distribute this code for - * any and all purposes, provided that this notice appears in all copies. - */ - -#define L 0 /* Left direction: -x, -i. */ -#define R 1 /* Right direction: +x, +i. */ -#define B 2 /* Bottom direction: -y, -j. */ -#define T 3 /* Top direction: +y, +j. */ -#define N 4 /* Near direction: -z, -k. */ -#define F 5 /* Far direction: +z, +k. */ -#define LBN 0 /* Left bottom near corner. */ -#define LBF 1 /* Left bottom far corner. */ -#define LTN 2 /* Left top near corner. */ -#define LTF 3 /* Left top far corner. */ -#define RBN 4 /* Right bottom near corner. */ -#define RBF 5 /* Right bottom far corner. */ -#define RTN 6 /* Right top near corner. */ -#define RTF 7 /* Right top far corner. */ - -/** - * the LBN corner of cube (i, j, k), corresponds with location - * (i-0.5)*size, (j-0.5)*size, (k-0.5)*size) - */ - -#define HASHBIT (5) -/** Hash table size (32768). */ -#define HASHSIZE (size_t)(1 << (3 * HASHBIT)) - -#define HASH(i, j, k) ((((((i)&31) << 5) | ((j)&31)) << 5) | ((k)&31)) - -#define MB_BIT(i, bit) (((i) >> (bit)) & 1) -// #define FLIP(i, bit) ((i) ^ 1 << (bit)) /* flip the given bit of i */ - -/* ******************** DENSITY COPMPUTATION ********************* */ - -/** - * Computes density from given metaball at given position. - * Metaball equation is: `(1 - r^2 / R^2)^3 * s` - * - * r = distance from center - * R = metaball radius - * s - metaball stiffness - */ -static float densfunc(const MetaElem *ball, float x, float y, float z) -{ - float dist2; - float dvec[3] = {x, y, z}; - - mul_m4_v3((const float(*)[4])ball->imat, dvec); - - switch (ball->type) { - case MB_BALL: - /* do nothing */ - break; - case MB_CUBE: - if (dvec[2] > ball->expz) { - dvec[2] -= ball->expz; - } - else if (dvec[2] < -ball->expz) { - dvec[2] += ball->expz; - } - else { - dvec[2] = 0.0; - } - ATTR_FALLTHROUGH; - case MB_PLANE: - if (dvec[1] > ball->expy) { - dvec[1] -= ball->expy; - } - else if (dvec[1] < -ball->expy) { - dvec[1] += ball->expy; - } - else { - dvec[1] = 0.0; - } - ATTR_FALLTHROUGH; - case MB_TUBE: - if (dvec[0] > ball->expx) { - dvec[0] -= ball->expx; - } - else if (dvec[0] < -ball->expx) { - dvec[0] += ball->expx; - } - else { - dvec[0] = 0.0; - } - break; - case MB_ELIPSOID: - dvec[0] /= ball->expx; - dvec[1] /= ball->expy; - dvec[2] /= ball->expz; - break; - - /* *** deprecated, could be removed?, do-versioned at least *** */ - case MB_TUBEX: - if (dvec[0] > ball->len) { - dvec[0] -= ball->len; - } - else if (dvec[0] < -ball->len) { - dvec[0] += ball->len; - } - else { - dvec[0] = 0.0; - } - break; - case MB_TUBEY: - if (dvec[1] > ball->len) { - dvec[1] -= ball->len; - } - else if (dvec[1] < -ball->len) { - dvec[1] += ball->len; - } - else { - dvec[1] = 0.0; - } - break; - case MB_TUBEZ: - if (dvec[2] > ball->len) { - dvec[2] -= ball->len; - } - else if (dvec[2] < -ball->len) { - dvec[2] += ball->len; - } - else { - dvec[2] = 0.0; - } - break; - /* *** end deprecated *** */ - } - - /* ball->rad2 is inverse of squared rad */ - dist2 = 1.0f - (len_squared_v3(dvec) * ball->rad2); - - /* ball->s is negative if metaball is negative */ - return (dist2 < 0.0f) ? 0.0f : (ball->s * dist2 * dist2 * dist2); -} - -/** - * Computes density at given position form all meta-balls which contain this point in their box. - * Traverses BVH using a queue. - */ -static float metaball(PROCESS *process, float x, float y, float z) -{ - float dens = 0.0f; - uint front = 0, back = 0; - MetaballBVHNode *node; - - process->bvh_queue[front++] = &process->metaball_bvh; - - while (front != back) { - node = process->bvh_queue[back++]; - - for (int i = 0; i < 2; i++) { - if ((node->bb[i].min[0] <= x) && (node->bb[i].max[0] >= x) && (node->bb[i].min[1] <= y) && - (node->bb[i].max[1] >= y) && (node->bb[i].min[2] <= z) && (node->bb[i].max[2] >= z)) { - if (node->child[i]) { - process->bvh_queue[front++] = node->child[i]; - } - else { - dens += densfunc(node->bb[i].ml, x, y, z); - } - } - } - } - - return process->thresh - dens; -} - -/** - * Adds face to indices, expands memory if needed. - */ -static void make_face(PROCESS *process, int i1, int i2, int i3, int i4) -{ -#ifdef USE_ACCUM_NORMAL - float n[3]; -#endif - - if (UNLIKELY(process->totindex == process->curindex)) { - process->totindex = process->totindex ? (process->totindex * 2) : MBALL_ARRAY_LEN_INIT; - process->indices = MEM_reallocN(process->indices, sizeof(int[4]) * process->totindex); - } - - int *cur = process->indices[process->curindex++]; - - /* Treat triangles as fake quads. */ - cur[0] = i1; - cur[1] = i2; - cur[2] = i3; - cur[3] = i4; - -#ifdef USE_ACCUM_NORMAL - if (i4 == i3) { - normal_tri_v3(n, process->co[i1], process->co[i2], process->co[i3]); - accumulate_vertex_normals_v3(process->no[i1], - process->no[i2], - process->no[i3], - NULL, - n, - process->co[i1], - process->co[i2], - process->co[i3], - NULL); - } - else { - normal_quad_v3(n, process->co[i1], process->co[i2], process->co[i3], process->co[i4]); - accumulate_vertex_normals_v3(process->no[i1], - process->no[i2], - process->no[i3], - process->no[i4], - n, - process->co[i1], - process->co[i2], - process->co[i3], - process->co[i4]); - } -#endif -} - -/* Frees allocated memory */ -static void freepolygonize(PROCESS *process) -{ - if (process->corners) { - MEM_freeN(process->corners); - } - if (process->edges) { - MEM_freeN(process->edges); - } - if (process->centers) { - MEM_freeN(process->centers); - } - if (process->mainb) { - MEM_freeN(process->mainb); - } - if (process->bvh_queue) { - MEM_freeN(process->bvh_queue); - } - if (process->pgn_elements) { - BLI_memarena_free(process->pgn_elements); - } -} - -/* **************** POLYGONIZATION ************************ */ - -/**** Cubical Polygonization (optional) ****/ - -#define LB 0 /* left bottom edge */ -#define LT 1 /* left top edge */ -#define LN 2 /* left near edge */ -#define LF 3 /* left far edge */ -#define RB 4 /* right bottom edge */ -#define RT 5 /* right top edge */ -#define RN 6 /* right near edge */ -#define RF 7 /* right far edge */ -#define BN 8 /* bottom near edge */ -#define BF 9 /* bottom far edge */ -#define TN 10 /* top near edge */ -#define TF 11 /* top far edge */ - -static INTLISTS *cubetable[256]; -static char faces[256]; - -/* edge: LB, LT, LN, LF, RB, RT, RN, RF, BN, BF, TN, TF */ -static int corner1[12] = { - LBN, - LTN, - LBN, - LBF, - RBN, - RTN, - RBN, - RBF, - LBN, - LBF, - LTN, - LTF, -}; -static int corner2[12] = { - LBF, - LTF, - LTN, - LTF, - RBF, - RTF, - RTN, - RTF, - RBN, - RBF, - RTN, - RTF, -}; -static int leftface[12] = { - B, - L, - L, - F, - R, - T, - N, - R, - N, - B, - T, - F, -}; -/* face on left when going corner1 to corner2 */ -static int rightface[12] = { - L, - T, - N, - L, - B, - R, - R, - F, - B, - F, - N, - T, -}; -/* face on right when going corner1 to corner2 */ - -/** - * triangulate the cube directly, without decomposition - */ -static void docube(PROCESS *process, CUBE *cube) -{ - INTLISTS *polys; - CORNER *c1, *c2; - int i, index = 0, count, indexar[8]; - - /* Determine which case cube falls into. */ - for (i = 0; i < 8; i++) { - if (cube->corners[i]->value > 0.0f) { - index += (1 << i); - } - } - - /* Using faces[] table, adds neighboring cube if surface intersects face in this direction. */ - if (MB_BIT(faces[index], 0)) { - add_cube(process, cube->i - 1, cube->j, cube->k); - } - if (MB_BIT(faces[index], 1)) { - add_cube(process, cube->i + 1, cube->j, cube->k); - } - if (MB_BIT(faces[index], 2)) { - add_cube(process, cube->i, cube->j - 1, cube->k); - } - if (MB_BIT(faces[index], 3)) { - add_cube(process, cube->i, cube->j + 1, cube->k); - } - if (MB_BIT(faces[index], 4)) { - add_cube(process, cube->i, cube->j, cube->k - 1); - } - if (MB_BIT(faces[index], 5)) { - add_cube(process, cube->i, cube->j, cube->k + 1); - } - - /* Using cubetable[], determines polygons for output. */ - for (polys = cubetable[index]; polys; polys = polys->next) { - INTLIST *edges; - - count = 0; - /* Sets needed vertex id's lying on the edges. */ - for (edges = polys->list; edges; edges = edges->next) { - c1 = cube->corners[corner1[edges->i]]; - c2 = cube->corners[corner2[edges->i]]; - - indexar[count] = vertid(process, c1, c2); - count++; - } - - /* Adds faces to output. */ - if (count > 2) { - switch (count) { - case 3: - make_face(process, indexar[2], indexar[1], indexar[0], indexar[0]); /* triangle */ - break; - case 4: - make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); - break; - case 5: - make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); - make_face(process, indexar[4], indexar[3], indexar[0], indexar[0]); /* triangle */ - break; - case 6: - make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); - make_face(process, indexar[5], indexar[4], indexar[3], indexar[0]); - break; - case 7: - make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); - make_face(process, indexar[5], indexar[4], indexar[3], indexar[0]); - make_face(process, indexar[6], indexar[5], indexar[0], indexar[0]); /* triangle */ - break; - } - } - } -} - -/** - * return corner with the given lattice location - * set (and cache) its function value - */ -static CORNER *setcorner(PROCESS *process, int i, int j, int k) -{ - /* for speed, do corner value caching here */ - CORNER *c; - int index; - - /* does corner exist? */ - index = HASH(i, j, k); - c = process->corners[index]; - - for (; c != NULL; c = c->next) { - if (c->i == i && c->j == j && c->k == k) { - return c; - } - } - - c = BLI_memarena_alloc(process->pgn_elements, sizeof(CORNER)); - - c->i = i; - c->co[0] = ((float)i - 0.5f) * process->size; - c->j = j; - c->co[1] = ((float)j - 0.5f) * process->size; - c->k = k; - c->co[2] = ((float)k - 0.5f) * process->size; - - c->value = metaball(process, c->co[0], c->co[1], c->co[2]); - - c->next = process->corners[index]; - process->corners[index] = c; - - return c; -} - -/** - * return next clockwise edge from given edge around given face - */ -static int nextcwedge(int edge, int face) -{ - switch (edge) { - case LB: - return (face == L) ? LF : BN; - case LT: - return (face == L) ? LN : TF; - case LN: - return (face == L) ? LB : TN; - case LF: - return (face == L) ? LT : BF; - case RB: - return (face == R) ? RN : BF; - case RT: - return (face == R) ? RF : TN; - case RN: - return (face == R) ? RT : BN; - case RF: - return (face == R) ? RB : TF; - case BN: - return (face == B) ? RB : LN; - case BF: - return (face == B) ? LB : RF; - case TN: - return (face == T) ? LT : RN; - case TF: - return (face == T) ? RT : LF; - } - return 0; -} - -/** - * \return the face adjoining edge that is not the given face - */ -static int otherface(int edge, int face) -{ - int other = leftface[edge]; - return face == other ? rightface[edge] : other; -} - -/** - * create the 256 entry table for cubical polygonization - */ -static void makecubetable(void) -{ - static bool is_done = false; - int i, e, c, done[12], pos[8]; - - if (is_done) { - return; - } - is_done = true; - - for (i = 0; i < 256; i++) { - for (e = 0; e < 12; e++) { - done[e] = 0; - } - for (c = 0; c < 8; c++) { - pos[c] = MB_BIT(i, c); - } - for (e = 0; e < 12; e++) { - if (!done[e] && (pos[corner1[e]] != pos[corner2[e]])) { - INTLIST *ints = NULL; - INTLISTS *lists = MEM_callocN(sizeof(INTLISTS), "mball_intlist"); - int start = e, edge = e; - - /* get face that is to right of edge from pos to neg corner: */ - int face = pos[corner1[e]] ? rightface[e] : leftface[e]; - - while (1) { - edge = nextcwedge(edge, face); - done[edge] = 1; - if (pos[corner1[edge]] != pos[corner2[edge]]) { - INTLIST *tmp = ints; - - ints = MEM_callocN(sizeof(INTLIST), "mball_intlist"); - ints->i = edge; - ints->next = tmp; /* add edge to head of list */ - - if (edge == start) { - break; - } - face = otherface(edge, face); - } - } - lists->list = ints; /* add ints to head of table entry */ - lists->next = cubetable[i]; - cubetable[i] = lists; - } - } - } - - for (i = 0; i < 256; i++) { - INTLISTS *polys; - faces[i] = 0; - for (polys = cubetable[i]; polys; polys = polys->next) { - INTLIST *edges; - - for (edges = polys->list; edges; edges = edges->next) { - if (ELEM(edges->i, LB, LT, LN, LF)) { - faces[i] |= 1 << L; - } - if (ELEM(edges->i, RB, RT, RN, RF)) { - faces[i] |= 1 << R; - } - if (ELEM(edges->i, LB, RB, BN, BF)) { - faces[i] |= 1 << B; - } - if (ELEM(edges->i, LT, RT, TN, TF)) { - faces[i] |= 1 << T; - } - if (ELEM(edges->i, LN, RN, BN, TN)) { - faces[i] |= 1 << N; - } - if (ELEM(edges->i, LF, RF, BF, TF)) { - faces[i] |= 1 << F; - } - } - } - } -} - -void BKE_mball_cubeTable_free(void) -{ - for (int i = 0; i < 256; i++) { - INTLISTS *lists = cubetable[i]; - while (lists) { - INTLISTS *nlists = lists->next; - - INTLIST *ints = lists->list; - while (ints) { - INTLIST *nints = ints->next; - MEM_freeN(ints); - ints = nints; - } - - MEM_freeN(lists); - lists = nlists; - } - cubetable[i] = NULL; - } -} - -/**** Storage ****/ - -/** - * Inserts cube at lattice i, j, k into hash table, marking it as "done" - */ -static int setcenter(PROCESS *process, CENTERLIST *table[], const int i, const int j, const int k) -{ - int index; - CENTERLIST *newc, *l, *q; - - index = HASH(i, j, k); - q = table[index]; - - for (l = q; l != NULL; l = l->next) { - if (l->i == i && l->j == j && l->k == k) { - return 1; - } - } - - newc = BLI_memarena_alloc(process->pgn_elements, sizeof(CENTERLIST)); - newc->i = i; - newc->j = j; - newc->k = k; - newc->next = q; - table[index] = newc; - - return 0; -} - -/** - * Sets vid of vertex lying on given edge. - */ -static void setedge(PROCESS *process, int i1, int j1, int k1, int i2, int j2, int k2, int vid) -{ - int index; - EDGELIST *newe; - - if (i1 > i2 || (i1 == i2 && (j1 > j2 || (j1 == j2 && k1 > k2)))) { - int t = i1; - i1 = i2; - i2 = t; - t = j1; - j1 = j2; - j2 = t; - t = k1; - k1 = k2; - k2 = t; - } - index = HASH(i1, j1, k1) + HASH(i2, j2, k2); - newe = BLI_memarena_alloc(process->pgn_elements, sizeof(EDGELIST)); - - newe->i1 = i1; - newe->j1 = j1; - newe->k1 = k1; - newe->i2 = i2; - newe->j2 = j2; - newe->k2 = k2; - newe->vid = vid; - newe->next = process->edges[index]; - process->edges[index] = newe; -} - -/** - * \return vertex id for edge; return -1 if not set - */ -static int getedge(EDGELIST *table[], int i1, int j1, int k1, int i2, int j2, int k2) -{ - EDGELIST *q; - - if (i1 > i2 || (i1 == i2 && (j1 > j2 || (j1 == j2 && k1 > k2)))) { - int t = i1; - i1 = i2; - i2 = t; - t = j1; - j1 = j2; - j2 = t; - t = k1; - k1 = k2; - k2 = t; - } - q = table[HASH(i1, j1, k1) + HASH(i2, j2, k2)]; - for (; q != NULL; q = q->next) { - if (q->i1 == i1 && q->j1 == j1 && q->k1 == k1 && q->i2 == i2 && q->j2 == j2 && q->k2 == k2) { - return q->vid; - } - } - return -1; -} - -/** - * Adds a vertex, expands memory if needed. - */ -static void addtovertices(PROCESS *process, const float v[3], const float no[3]) -{ - if (UNLIKELY(process->curvertex == process->totvertex)) { - process->totvertex = process->totvertex ? process->totvertex * 2 : MBALL_ARRAY_LEN_INIT; - process->co = MEM_reallocN(process->co, process->totvertex * sizeof(float[3])); - process->no = MEM_reallocN(process->no, process->totvertex * sizeof(float[3])); - } - - copy_v3_v3(process->co[process->curvertex], v); - copy_v3_v3(process->no[process->curvertex], no); - - process->curvertex++; -} - -#ifndef USE_ACCUM_NORMAL -/** - * Computes normal from density field at given point. - * - * \note Doesn't do normalization! - */ -static void vnormal(PROCESS *process, const float point[3], float r_no[3]) -{ - const float delta = process->delta; - const float f = metaball(process, point[0], point[1], point[2]); - - r_no[0] = metaball(process, point[0] + delta, point[1], point[2]) - f; - r_no[1] = metaball(process, point[0], point[1] + delta, point[2]) - f; - r_no[2] = metaball(process, point[0], point[1], point[2] + delta) - f; -} -#endif /* USE_ACCUM_NORMAL */ - -/** - * \return the id of vertex between two corners. - * - * If it wasn't previously computed, does #converge() and adds vertex to process. - */ -static int vertid(PROCESS *process, const CORNER *c1, const CORNER *c2) -{ - float v[3], no[3]; - int vid = getedge(process->edges, c1->i, c1->j, c1->k, c2->i, c2->j, c2->k); - - if (vid != -1) { - return vid; /* previously computed */ - } - - converge(process, c1, c2, v); /* position */ - -#ifdef USE_ACCUM_NORMAL - zero_v3(no); -#else - vnormal(process, v, no); -#endif - - addtovertices(process, v, no); /* save vertex */ - vid = (int)process->curvertex - 1; - setedge(process, c1->i, c1->j, c1->k, c2->i, c2->j, c2->k, vid); - - return vid; -} - -/** - * Given two corners, computes approximation of surface intersection point between them. - * In case of small threshold, do bisection. - */ -static void converge(PROCESS *process, const CORNER *c1, const CORNER *c2, float r_p[3]) -{ - float c1_value, c1_co[3]; - float c2_value, c2_co[3]; - - if (c1->value < c2->value) { - c1_value = c2->value; - copy_v3_v3(c1_co, c2->co); - c2_value = c1->value; - copy_v3_v3(c2_co, c1->co); - } - else { - c1_value = c1->value; - copy_v3_v3(c1_co, c1->co); - c2_value = c2->value; - copy_v3_v3(c2_co, c2->co); - } - - for (uint i = 0; i < process->converge_res; i++) { - interp_v3_v3v3(r_p, c1_co, c2_co, 0.5f); - float dens = metaball(process, r_p[0], r_p[1], r_p[2]); - - if (dens > 0.0f) { - c1_value = dens; - copy_v3_v3(c1_co, r_p); - } - else { - c2_value = dens; - copy_v3_v3(c2_co, r_p); - } - } - - float tmp = -c1_value / (c2_value - c1_value); - interp_v3_v3v3(r_p, c1_co, c2_co, tmp); -} - -/** - * Adds cube at given lattice position to cube stack of process. - */ -static void add_cube(PROCESS *process, int i, int j, int k) -{ - CUBES *ncube; - int n; - - /* test if cube has been found before */ - if (setcenter(process, process->centers, i, j, k) == 0) { - /* push cube on stack: */ - ncube = BLI_memarena_alloc(process->pgn_elements, sizeof(CUBES)); - ncube->next = process->cubes; - process->cubes = ncube; - - ncube->cube.i = i; - ncube->cube.j = j; - ncube->cube.k = k; - - /* set corners of initial cube: */ - for (n = 0; n < 8; n++) { - ncube->cube.corners[n] = setcorner( - process, i + MB_BIT(n, 2), j + MB_BIT(n, 1), k + MB_BIT(n, 0)); - } - } -} - -static void next_lattice(int r[3], const float pos[3], const float size) -{ - r[0] = (int)ceil((pos[0] / size) + 0.5f); - r[1] = (int)ceil((pos[1] / size) + 0.5f); - r[2] = (int)ceil((pos[2] / size) + 0.5f); -} -static void prev_lattice(int r[3], const float pos[3], const float size) -{ - next_lattice(r, pos, size); - r[0]--; - r[1]--; - r[2]--; -} -static void closest_latice(int r[3], const float pos[3], const float size) -{ - r[0] = (int)floorf(pos[0] / size + 1.0f); - r[1] = (int)floorf(pos[1] / size + 1.0f); - r[2] = (int)floorf(pos[2] / size + 1.0f); -} - -/** - * Find at most 26 cubes to start polygonization from. - */ -static void find_first_points(PROCESS *process, const uint em) -{ - const MetaElem *ml; - int center[3], lbn[3], rtf[3], it[3], dir[3], add[3]; - float tmp[3], a, b; - - ml = process->mainb[em]; - - mid_v3_v3v3(tmp, ml->bb->vec[0], ml->bb->vec[6]); - closest_latice(center, tmp, process->size); - prev_lattice(lbn, ml->bb->vec[0], process->size); - next_lattice(rtf, ml->bb->vec[6], process->size); - - for (dir[0] = -1; dir[0] <= 1; dir[0]++) { - for (dir[1] = -1; dir[1] <= 1; dir[1]++) { - for (dir[2] = -1; dir[2] <= 1; dir[2]++) { - if (dir[0] == 0 && dir[1] == 0 && dir[2] == 0) { - continue; - } - - copy_v3_v3_int(it, center); - - b = setcorner(process, it[0], it[1], it[2])->value; - do { - it[0] += dir[0]; - it[1] += dir[1]; - it[2] += dir[2]; - a = b; - b = setcorner(process, it[0], it[1], it[2])->value; - - if (a * b < 0.0f) { - add[0] = it[0] - dir[0]; - add[1] = it[1] - dir[1]; - add[2] = it[2] - dir[2]; - DO_MIN(it, add); - add_cube(process, add[0], add[1], add[2]); - break; - } - } while ((it[0] > lbn[0]) && (it[1] > lbn[1]) && (it[2] > lbn[2]) && (it[0] < rtf[0]) && - (it[1] < rtf[1]) && (it[2] < rtf[2])); - } - } - } -} - -/** - * The main polygonization proc. - * Allocates memory, makes cubetable, - * finds starting surface points - * and processes cubes on the stack until none left. - */ -static void polygonize(PROCESS *process) -{ - CUBE c; - - process->centers = MEM_callocN(HASHSIZE * sizeof(CENTERLIST *), "mbproc->centers"); - process->corners = MEM_callocN(HASHSIZE * sizeof(CORNER *), "mbproc->corners"); - process->edges = MEM_callocN(2 * HASHSIZE * sizeof(EDGELIST *), "mbproc->edges"); - process->bvh_queue = MEM_callocN(sizeof(MetaballBVHNode *) * process->bvh_queue_size, - "Metaball BVH Queue"); - - makecubetable(); - - for (uint i = 0; i < process->totelem; i++) { - find_first_points(process, i); - } - - while (process->cubes != NULL) { - c = process->cubes->cube; - process->cubes = process->cubes->next; - - docube(process, &c); - } -} - -/** - * Iterates over ALL objects in the scene and all of its sets, including - * making all duplis (not only meta-elements). Copies meta-elements to #process.mainb array. - * Computes bounding boxes for building BVH. - */ -static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Object *ob) -{ - Scene *sce_iter = scene; - Base *base; - Object *bob; - MetaBall *mb; - const MetaElem *ml; - float obinv[4][4], obmat[4][4]; - uint i; - int obnr, zero_size = 0; - char obname[MAX_ID_NAME]; - SceneBaseIter iter; - const eEvaluationMode deg_eval_mode = DEG_get_mode(depsgraph); - const short parenting_dupli_transflag = (OB_DUPLIFACES | OB_DUPLIVERTS); - - copy_m4_m4(obmat, ob->obmat); /* to cope with duplicators from BKE_scene_base_iter_next */ - invert_m4_m4(obinv, ob->obmat); - - BLI_split_name_num(obname, &obnr, ob->id.name + 2, '.'); - - /* make main array */ - BKE_scene_base_iter_next(depsgraph, &iter, &sce_iter, 0, NULL, NULL); - while (BKE_scene_base_iter_next(depsgraph, &iter, &sce_iter, 1, &base, &bob)) { - if (bob->type == OB_MBALL) { - zero_size = 0; - ml = NULL; - - /* If this metaball is the original that's used for duplication, only have it visible when - * the instancer is visible too. */ - if ((base->flag_legacy & OB_FROMDUPLI) == 0 && ob->parent != NULL && - (ob->parent->transflag & parenting_dupli_transflag) != 0 && - (BKE_object_visibility(ob->parent, deg_eval_mode) & OB_VISIBLE_SELF) == 0) { - continue; - } - - if (bob == ob && (base->flag_legacy & OB_FROMDUPLI) == 0) { - mb = ob->data; - - if (mb->editelems) { - ml = mb->editelems->first; - } - else { - ml = mb->elems.first; - } - } - else { - char name[MAX_ID_NAME]; - int nr; - - BLI_split_name_num(name, &nr, bob->id.name + 2, '.'); - if (STREQ(obname, name)) { - mb = bob->data; - - if (mb->editelems) { - ml = mb->editelems->first; - } - else { - ml = mb->elems.first; - } - } - } - - /* when metaball object has zero scale, then MetaElem to this MetaBall - * will not be put to mainb array */ - if (has_zero_axis_m4(bob->obmat)) { - zero_size = 1; - } - else if (bob->parent) { - struct Object *pob = bob->parent; - while (pob) { - if (has_zero_axis_m4(pob->obmat)) { - zero_size = 1; - break; - } - pob = pob->parent; - } - } - - if (zero_size) { - while (ml) { - ml = ml->next; - } - } - else { - while (ml) { - if (!(ml->flag & MB_HIDE)) { - float pos[4][4], rot[4][4]; - float expx, expy, expz; - float tempmin[3], tempmax[3]; - - MetaElem *new_ml; - - /* make a copy because of duplicates */ - new_ml = BLI_memarena_alloc(process->pgn_elements, sizeof(MetaElem)); - *(new_ml) = *ml; - new_ml->bb = BLI_memarena_alloc(process->pgn_elements, sizeof(BoundBox)); - new_ml->mat = BLI_memarena_alloc(process->pgn_elements, sizeof(float[4][4])); - new_ml->imat = BLI_memarena_alloc(process->pgn_elements, sizeof(float[4][4])); - - /* too big stiffness seems only ugly due to linear interpolation - * no need to have possibility for too big stiffness */ - if (ml->s > 10.0f) { - new_ml->s = 10.0f; - } - else { - new_ml->s = ml->s; - } - - /* if metaball is negative, set stiffness negative */ - if (new_ml->flag & MB_NEGATIVE) { - new_ml->s = -new_ml->s; - } - - /* Translation of MetaElem */ - unit_m4(pos); - pos[3][0] = ml->x; - pos[3][1] = ml->y; - pos[3][2] = ml->z; - - /* Rotation of MetaElem is stored in quat */ - quat_to_mat4(rot, ml->quat); - - /* Matrix multiply is as follows: - * basis object space -> - * world -> - * ml object space -> - * position -> - * rotation -> - * ml local space - */ - mul_m4_series((float(*)[4])new_ml->mat, obinv, bob->obmat, pos, rot); - /* ml local space -> basis object space */ - invert_m4_m4((float(*)[4])new_ml->imat, (float(*)[4])new_ml->mat); - - /* rad2 is inverse of squared radius */ - new_ml->rad2 = 1 / (ml->rad * ml->rad); - - /* initial dimensions = radius */ - expx = ml->rad; - expy = ml->rad; - expz = ml->rad; - - switch (ml->type) { - case MB_BALL: - break; - case MB_CUBE: /* cube is "expanded" by expz, expy and expx */ - expz += ml->expz; - ATTR_FALLTHROUGH; - case MB_PLANE: /* plane is "expanded" by expy and expx */ - expy += ml->expy; - ATTR_FALLTHROUGH; - case MB_TUBE: /* tube is "expanded" by expx */ - expx += ml->expx; - break; - case MB_ELIPSOID: /* ellipsoid is "stretched" by exp* */ - expx *= ml->expx; - expy *= ml->expy; - expz *= ml->expz; - break; - } - - /* untransformed Bounding Box of MetaElem */ - /* TODO: its possible the elem type has been changed and the exp* - * values can use a fallback. */ - copy_v3_fl3(new_ml->bb->vec[0], -expx, -expy, -expz); /* 0 */ - copy_v3_fl3(new_ml->bb->vec[1], +expx, -expy, -expz); /* 1 */ - copy_v3_fl3(new_ml->bb->vec[2], +expx, +expy, -expz); /* 2 */ - copy_v3_fl3(new_ml->bb->vec[3], -expx, +expy, -expz); /* 3 */ - copy_v3_fl3(new_ml->bb->vec[4], -expx, -expy, +expz); /* 4 */ - copy_v3_fl3(new_ml->bb->vec[5], +expx, -expy, +expz); /* 5 */ - copy_v3_fl3(new_ml->bb->vec[6], +expx, +expy, +expz); /* 6 */ - copy_v3_fl3(new_ml->bb->vec[7], -expx, +expy, +expz); /* 7 */ - - /* transformation of Metalem bb */ - for (i = 0; i < 8; i++) { - mul_m4_v3((float(*)[4])new_ml->mat, new_ml->bb->vec[i]); - } - - /* find max and min of transformed bb */ - INIT_MINMAX(tempmin, tempmax); - for (i = 0; i < 8; i++) { - DO_MINMAX(new_ml->bb->vec[i], tempmin, tempmax); - } - - /* set only point 0 and 6 - AABB of Metaelem */ - copy_v3_v3(new_ml->bb->vec[0], tempmin); - copy_v3_v3(new_ml->bb->vec[6], tempmax); - - /* add new_ml to mainb[] */ - if (UNLIKELY(process->totelem == process->mem)) { - process->mem = process->mem * 2 + 10; - process->mainb = MEM_reallocN(process->mainb, sizeof(MetaElem *) * process->mem); - } - process->mainb[process->totelem++] = new_ml; - } - ml = ml->next; - } - } - } - } - - /* compute AABB of all Metaelems */ - if (process->totelem > 0) { - copy_v3_v3(process->allbb.min, process->mainb[0]->bb->vec[0]); - copy_v3_v3(process->allbb.max, process->mainb[0]->bb->vec[6]); - for (i = 1; i < process->totelem; i++) { - make_box_union(process->mainb[i]->bb, &process->allbb, &process->allbb); - } - } -} - -Mesh *BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob) -{ - PROCESS process = {0}; - const bool is_render = DEG_get_mode(depsgraph) == DAG_EVAL_RENDER; - - MetaBall *mb = ob->data; - - process.thresh = mb->thresh; - - if (process.thresh < 0.001f) { - process.converge_res = 16; - } - else if (process.thresh < 0.01f) { - process.converge_res = 8; - } - else if (process.thresh < 0.1f) { - process.converge_res = 4; - } - else { - process.converge_res = 2; - } - - if (!is_render && (mb->flag == MB_UPDATE_NEVER)) { - return NULL; - } - if ((G.moving & (G_TRANSFORM_OBJ | G_TRANSFORM_EDIT)) && mb->flag == MB_UPDATE_FAST) { - return NULL; - } - - if (is_render) { - process.size = mb->rendersize; - } - else { - process.size = mb->wiresize; - if ((G.moving & (G_TRANSFORM_OBJ | G_TRANSFORM_EDIT)) && mb->flag == MB_UPDATE_HALFRES) { - process.size *= 2.0f; - } - } - - process.delta = process.size * 0.001f; - - process.pgn_elements = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "Metaball memarena"); - - /* initialize all mainb (MetaElems) */ - init_meta(depsgraph, &process, scene, ob); - if (process.totelem == 0) { - freepolygonize(&process); - return NULL; - } - - build_bvh_spatial(&process, &process.metaball_bvh, 0, process.totelem, &process.allbb); - - /* Don't polygonize meta-balls with too high resolution (base mball too small) - * NOTE: Eps was 0.0001f but this was giving problems for blood animation for - * the open movie "Sintel", using 0.00001f. */ - if (ob->scale[0] < 0.00001f * (process.allbb.max[0] - process.allbb.min[0]) || - ob->scale[1] < 0.00001f * (process.allbb.max[1] - process.allbb.min[1]) || - ob->scale[2] < 0.00001f * (process.allbb.max[2] - process.allbb.min[2])) { - freepolygonize(&process); - return NULL; - } - - polygonize(&process); - if (process.curindex == 0) { - freepolygonize(&process); - return NULL; - } - - freepolygonize(&process); - - Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)ob->data)->name + 2); - - mesh->totvert = (int)process.curvertex; - MVert *mvert = CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_CONSTRUCT, NULL, mesh->totvert); - for (int i = 0; i < mesh->totvert; i++) { - copy_v3_v3(mvert[i].co, process.co[i]); - } - MEM_freeN(process.co); - - mesh->totpoly = (int)process.curindex; - MPoly *mpoly = CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_CONSTRUCT, NULL, mesh->totpoly); - MLoop *mloop = CustomData_add_layer( - &mesh->ldata, CD_MLOOP, CD_CONSTRUCT, NULL, mesh->totpoly * 4); - - int loop_offset = 0; - for (int i = 0; i < mesh->totpoly; i++) { - const int *indices = process.indices[i]; - - const int count = indices[2] != indices[3] ? 4 : 3; - mpoly[i].loopstart = loop_offset; - mpoly[i].totloop = count; - mpoly[i].flag = ME_SMOOTH; - - mloop[loop_offset].v = (uint32_t)indices[0]; - mloop[loop_offset + 1].v = (uint32_t)indices[1]; - mloop[loop_offset + 2].v = (uint32_t)indices[2]; - if (count == 4) { - mloop[loop_offset + 3].v = (uint32_t)indices[3]; - } - - loop_offset += count; - } - MEM_freeN(process.indices); - - for (int i = 0; i < mesh->totvert; i++) { - normalize_v3(process.no[i]); - } - mesh->runtime.vert_normals = process.no; - BKE_mesh_vertex_normals_clear_dirty(mesh); - - mesh->totloop = loop_offset; - - BKE_mesh_calc_edges(mesh, false, false); - - return mesh; -} diff --git a/source/blender/blenkernel/intern/mball_tessellate.cc b/source/blender/blenkernel/intern/mball_tessellate.cc new file mode 100644 index 00000000000..5df924c1bbf --- /dev/null +++ b/source/blender/blenkernel/intern/mball_tessellate.cc @@ -0,0 +1,1500 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2001-2002 NaN Holding BV. All rights reserved. */ + +/** \file + * \ingroup bke + */ + +#include +#include +#include +#include +#include +#include + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_meta_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_memarena.h" +#include "BLI_string_utils.h" +#include "BLI_utildefines.h" + +#include "BKE_displist.h" +#include "BKE_global.h" +#include "BKE_lib_id.h" +#include "BKE_mball_tessellate.h" /* own include */ +#include "BKE_mesh.h" +#include "BKE_object.h" +#include "BKE_scene.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "BLI_strict_flags.h" + +/* experimental (faster) normal calculation */ +// #define USE_ACCUM_NORMAL + +#define MBALL_ARRAY_LEN_INIT 4096 + +/* Data types */ + +typedef struct corner { /* corner of a cube */ + int i, j, k; /* (i, j, k) is index within lattice */ + float co[3], value; /* location and function value */ + struct corner *next; +} CORNER; + +typedef struct cube { /* partitioning cell (cube) */ + int i, j, k; /* lattice location of cube */ + CORNER *corners[8]; /* eight corners */ +} CUBE; + +typedef struct cubes { /* linked list of cubes acting as stack */ + CUBE cube; /* a single cube */ + struct cubes *next; /* remaining elements */ +} CUBES; + +typedef struct centerlist { /* list of cube locations */ + int i, j, k; /* cube location */ + struct centerlist *next; /* remaining elements */ +} CENTERLIST; + +typedef struct edgelist { /* list of edges */ + int i1, j1, k1, i2, j2, k2; /* edge corner ids */ + int vid; /* vertex id */ + struct edgelist *next; /* remaining elements */ +} EDGELIST; + +typedef struct intlist { /* list of integers */ + int i; /* an integer */ + struct intlist *next; /* remaining elements */ +} INTLIST; + +typedef struct intlists { /* list of list of integers */ + INTLIST *list; /* a list of integers */ + struct intlists *next; /* remaining elements */ +} INTLISTS; + +typedef struct Box { /* an AABB with pointer to metalelem */ + float min[3], max[3]; + const MetaElem *ml; +} Box; + +typedef struct MetaballBVHNode { /* BVH node */ + Box bb[2]; /* AABB of children */ + struct MetaballBVHNode *child[2]; +} MetaballBVHNode; + +typedef struct process { /* parameters, storage */ + float thresh, size; /* mball threshold, single cube size */ + float delta; /* small delta for calculating normals */ + uint converge_res; /* converge procedure resolution (more = slower) */ + + MetaElem **mainb; /* array of all metaelems */ + uint totelem, mem; /* number of metaelems */ + + MetaballBVHNode metaball_bvh; /* The simplest bvh */ + Box allbb; /* Bounding box of all metaelems */ + + MetaballBVHNode **bvh_queue; /* Queue used during bvh traversal */ + uint bvh_queue_size; + + CUBES *cubes; /* stack of cubes waiting for polygonization */ + CENTERLIST **centers; /* cube center hash table */ + CORNER **corners; /* corner value hash table */ + EDGELIST **edges; /* edge and vertex id hash table */ + + int (*indices)[4]; /* output indices */ + uint totindex; /* size of memory allocated for indices */ + uint curindex; /* number of currently added indices */ + + float (*co)[3], (*no)[3]; /* surface vertices - positions and normals */ + uint totvertex; /* memory size */ + uint curvertex; /* currently added vertices */ + + /* memory allocation from common pool */ + MemArena *pgn_elements; +} PROCESS; + +/* Forward declarations */ +static int vertid(PROCESS *process, const CORNER *c1, const CORNER *c2); +static void add_cube(PROCESS *process, int i, int j, int k); +static void make_face(PROCESS *process, int i1, int i2, int i3, int i4); +static void converge(PROCESS *process, const CORNER *c1, const CORNER *c2, float r_p[3]); + +/* ******************* SIMPLE BVH ********************* */ + +static void make_box_union(const BoundBox *a, const Box *b, Box *r_out) +{ + r_out->min[0] = min_ff(a->vec[0][0], b->min[0]); + r_out->min[1] = min_ff(a->vec[0][1], b->min[1]); + r_out->min[2] = min_ff(a->vec[0][2], b->min[2]); + + r_out->max[0] = max_ff(a->vec[6][0], b->max[0]); + r_out->max[1] = max_ff(a->vec[6][1], b->max[1]); + r_out->max[2] = max_ff(a->vec[6][2], b->max[2]); +} + +static void make_box_from_metaelem(Box *r, const MetaElem *ml) +{ + copy_v3_v3(r->max, ml->bb->vec[6]); + copy_v3_v3(r->min, ml->bb->vec[0]); + r->ml = ml; +} + +/** + * Partitions part of #process.mainb array [start, end) along axis s. Returns i, + * where centroids of elements in the [start, i) segment lie "on the right side" of div, + * and elements in the [i, end) segment lie "on the left" + */ +static uint partition_mainb(MetaElem **mainb, uint start, uint end, uint s, float div) +{ + uint i = start, j = end - 1; + div *= 2.0f; + + while (1) { + while (i < j && div > (mainb[i]->bb->vec[6][s] + mainb[i]->bb->vec[0][s])) { + i++; + } + while (j > i && div < (mainb[j]->bb->vec[6][s] + mainb[j]->bb->vec[0][s])) { + j--; + } + + if (i >= j) { + break; + } + + SWAP(MetaElem *, mainb[i], mainb[j]); + i++; + j--; + } + + if (i == start) { + i++; + } + + return i; +} + +/** + * Recursively builds a BVH, dividing elements along the middle of the longest axis of allbox. + */ +static void build_bvh_spatial( + PROCESS *process, MetaballBVHNode *node, uint start, uint end, const Box *allbox) +{ + uint part, j, s; + float dim[3], div; + + /* Maximum bvh queue size is number of nodes which are made, equals calls to this function. */ + process->bvh_queue_size++; + + dim[0] = allbox->max[0] - allbox->min[0]; + dim[1] = allbox->max[1] - allbox->min[1]; + dim[2] = allbox->max[2] - allbox->min[2]; + + s = 0; + if (dim[1] > dim[0] && dim[1] > dim[2]) { + s = 1; + } + else if (dim[2] > dim[1] && dim[2] > dim[0]) { + s = 2; + } + + div = allbox->min[s] + (dim[s] / 2.0f); + + part = partition_mainb(process->mainb, start, end, s, div); + + make_box_from_metaelem(&node->bb[0], process->mainb[start]); + node->child[0] = NULL; + + if (part > start + 1) { + for (j = start; j < part; j++) { + make_box_union(process->mainb[j]->bb, &node->bb[0], &node->bb[0]); + } + + node->child[0] = static_cast( + BLI_memarena_alloc(process->pgn_elements, sizeof(MetaballBVHNode))); + build_bvh_spatial(process, node->child[0], start, part, &node->bb[0]); + } + + node->child[1] = NULL; + if (part < end) { + make_box_from_metaelem(&node->bb[1], process->mainb[part]); + + if (part < end - 1) { + for (j = part; j < end; j++) { + make_box_union(process->mainb[j]->bb, &node->bb[1], &node->bb[1]); + } + + node->child[1] = static_cast( + BLI_memarena_alloc(process->pgn_elements, sizeof(MetaballBVHNode))); + build_bvh_spatial(process, node->child[1], part, end, &node->bb[1]); + } + } + else { + INIT_MINMAX(node->bb[1].min, node->bb[1].max); + } +} + +/* ******************** ARITH ************************* */ + +/** + * BASED AT CODE (but mostly rewritten) : + * C code from the article + * "An Implicit Surface Polygonizer" + * by Jules Bloomenthal + * in "Graphics Gems IV", Academic Press, 1994 + * + * Authored by Jules Bloomenthal, Xerox PARC. + * Copyright (c) Xerox Corporation, 1991. All rights reserved. + * Permission is granted to reproduce, use and distribute this code for + * any and all purposes, provided that this notice appears in all copies. + */ + +#define L 0 /* Left direction: -x, -i. */ +#define R 1 /* Right direction: +x, +i. */ +#define B 2 /* Bottom direction: -y, -j. */ +#define T 3 /* Top direction: +y, +j. */ +#define N 4 /* Near direction: -z, -k. */ +#define F 5 /* Far direction: +z, +k. */ +#define LBN 0 /* Left bottom near corner. */ +#define LBF 1 /* Left bottom far corner. */ +#define LTN 2 /* Left top near corner. */ +#define LTF 3 /* Left top far corner. */ +#define RBN 4 /* Right bottom near corner. */ +#define RBF 5 /* Right bottom far corner. */ +#define RTN 6 /* Right top near corner. */ +#define RTF 7 /* Right top far corner. */ + +/** + * the LBN corner of cube (i, j, k), corresponds with location + * (i-0.5)*size, (j-0.5)*size, (k-0.5)*size) + */ + +#define HASHBIT (5) +/** Hash table size (32768). */ +#define HASHSIZE (size_t)(1 << (3 * HASHBIT)) + +#define HASH(i, j, k) ((((((i)&31) << 5) | ((j)&31)) << 5) | ((k)&31)) + +#define MB_BIT(i, bit) (((i) >> (bit)) & 1) +// #define FLIP(i, bit) ((i) ^ 1 << (bit)) /* flip the given bit of i */ + +/* ******************** DENSITY COPMPUTATION ********************* */ + +/** + * Computes density from given metaball at given position. + * Metaball equation is: `(1 - r^2 / R^2)^3 * s` + * + * r = distance from center + * R = metaball radius + * s - metaball stiffness + */ +static float densfunc(const MetaElem *ball, float x, float y, float z) +{ + float dist2; + float dvec[3] = {x, y, z}; + + mul_m4_v3((const float(*)[4])ball->imat, dvec); + + switch (ball->type) { + case MB_BALL: + /* do nothing */ + break; + case MB_CUBE: + if (dvec[2] > ball->expz) { + dvec[2] -= ball->expz; + } + else if (dvec[2] < -ball->expz) { + dvec[2] += ball->expz; + } + else { + dvec[2] = 0.0; + } + ATTR_FALLTHROUGH; + case MB_PLANE: + if (dvec[1] > ball->expy) { + dvec[1] -= ball->expy; + } + else if (dvec[1] < -ball->expy) { + dvec[1] += ball->expy; + } + else { + dvec[1] = 0.0; + } + ATTR_FALLTHROUGH; + case MB_TUBE: + if (dvec[0] > ball->expx) { + dvec[0] -= ball->expx; + } + else if (dvec[0] < -ball->expx) { + dvec[0] += ball->expx; + } + else { + dvec[0] = 0.0; + } + break; + case MB_ELIPSOID: + dvec[0] /= ball->expx; + dvec[1] /= ball->expy; + dvec[2] /= ball->expz; + break; + + /* *** deprecated, could be removed?, do-versioned at least *** */ + case MB_TUBEX: + if (dvec[0] > ball->len) { + dvec[0] -= ball->len; + } + else if (dvec[0] < -ball->len) { + dvec[0] += ball->len; + } + else { + dvec[0] = 0.0; + } + break; + case MB_TUBEY: + if (dvec[1] > ball->len) { + dvec[1] -= ball->len; + } + else if (dvec[1] < -ball->len) { + dvec[1] += ball->len; + } + else { + dvec[1] = 0.0; + } + break; + case MB_TUBEZ: + if (dvec[2] > ball->len) { + dvec[2] -= ball->len; + } + else if (dvec[2] < -ball->len) { + dvec[2] += ball->len; + } + else { + dvec[2] = 0.0; + } + break; + /* *** end deprecated *** */ + } + + /* ball->rad2 is inverse of squared rad */ + dist2 = 1.0f - (len_squared_v3(dvec) * ball->rad2); + + /* ball->s is negative if metaball is negative */ + return (dist2 < 0.0f) ? 0.0f : (ball->s * dist2 * dist2 * dist2); +} + +/** + * Computes density at given position form all meta-balls which contain this point in their box. + * Traverses BVH using a queue. + */ +static float metaball(PROCESS *process, float x, float y, float z) +{ + float dens = 0.0f; + uint front = 0, back = 0; + MetaballBVHNode *node; + + process->bvh_queue[front++] = &process->metaball_bvh; + + while (front != back) { + node = process->bvh_queue[back++]; + + for (int i = 0; i < 2; i++) { + if ((node->bb[i].min[0] <= x) && (node->bb[i].max[0] >= x) && (node->bb[i].min[1] <= y) && + (node->bb[i].max[1] >= y) && (node->bb[i].min[2] <= z) && (node->bb[i].max[2] >= z)) { + if (node->child[i]) { + process->bvh_queue[front++] = node->child[i]; + } + else { + dens += densfunc(node->bb[i].ml, x, y, z); + } + } + } + } + + return process->thresh - dens; +} + +/** + * Adds face to indices, expands memory if needed. + */ +static void make_face(PROCESS *process, int i1, int i2, int i3, int i4) +{ +#ifdef USE_ACCUM_NORMAL + float n[3]; +#endif + + if (UNLIKELY(process->totindex == process->curindex)) { + process->totindex = process->totindex ? (process->totindex * 2) : MBALL_ARRAY_LEN_INIT; + process->indices = static_cast( + MEM_reallocN(process->indices, sizeof(int[4]) * process->totindex)); + } + + int *cur = process->indices[process->curindex++]; + + /* Treat triangles as fake quads. */ + cur[0] = i1; + cur[1] = i2; + cur[2] = i3; + cur[3] = i4; + +#ifdef USE_ACCUM_NORMAL + if (i4 == i3) { + normal_tri_v3(n, process->co[i1], process->co[i2], process->co[i3]); + accumulate_vertex_normals_v3(process->no[i1], + process->no[i2], + process->no[i3], + NULL, + n, + process->co[i1], + process->co[i2], + process->co[i3], + NULL); + } + else { + normal_quad_v3(n, process->co[i1], process->co[i2], process->co[i3], process->co[i4]); + accumulate_vertex_normals_v3(process->no[i1], + process->no[i2], + process->no[i3], + process->no[i4], + n, + process->co[i1], + process->co[i2], + process->co[i3], + process->co[i4]); + } +#endif +} + +/* Frees allocated memory */ +static void freepolygonize(PROCESS *process) +{ + if (process->corners) { + MEM_freeN(process->corners); + } + if (process->edges) { + MEM_freeN(process->edges); + } + if (process->centers) { + MEM_freeN(process->centers); + } + if (process->mainb) { + MEM_freeN(process->mainb); + } + if (process->bvh_queue) { + MEM_freeN(process->bvh_queue); + } + if (process->pgn_elements) { + BLI_memarena_free(process->pgn_elements); + } +} + +/* **************** POLYGONIZATION ************************ */ + +/**** Cubical Polygonization (optional) ****/ + +#define LB 0 /* left bottom edge */ +#define LT 1 /* left top edge */ +#define LN 2 /* left near edge */ +#define LF 3 /* left far edge */ +#define RB 4 /* right bottom edge */ +#define RT 5 /* right top edge */ +#define RN 6 /* right near edge */ +#define RF 7 /* right far edge */ +#define BN 8 /* bottom near edge */ +#define BF 9 /* bottom far edge */ +#define TN 10 /* top near edge */ +#define TF 11 /* top far edge */ + +static INTLISTS *cubetable[256]; +static char faces[256]; + +/* edge: LB, LT, LN, LF, RB, RT, RN, RF, BN, BF, TN, TF */ +static int corner1[12] = { + LBN, + LTN, + LBN, + LBF, + RBN, + RTN, + RBN, + RBF, + LBN, + LBF, + LTN, + LTF, +}; +static int corner2[12] = { + LBF, + LTF, + LTN, + LTF, + RBF, + RTF, + RTN, + RTF, + RBN, + RBF, + RTN, + RTF, +}; +static int leftface[12] = { + B, + L, + L, + F, + R, + T, + N, + R, + N, + B, + T, + F, +}; +/* face on left when going corner1 to corner2 */ +static int rightface[12] = { + L, + T, + N, + L, + B, + R, + R, + F, + B, + F, + N, + T, +}; +/* face on right when going corner1 to corner2 */ + +/** + * triangulate the cube directly, without decomposition + */ +static void docube(PROCESS *process, CUBE *cube) +{ + INTLISTS *polys; + CORNER *c1, *c2; + int i, index = 0, count, indexar[8]; + + /* Determine which case cube falls into. */ + for (i = 0; i < 8; i++) { + if (cube->corners[i]->value > 0.0f) { + index += (1 << i); + } + } + + /* Using faces[] table, adds neighboring cube if surface intersects face in this direction. */ + if (MB_BIT(faces[index], 0)) { + add_cube(process, cube->i - 1, cube->j, cube->k); + } + if (MB_BIT(faces[index], 1)) { + add_cube(process, cube->i + 1, cube->j, cube->k); + } + if (MB_BIT(faces[index], 2)) { + add_cube(process, cube->i, cube->j - 1, cube->k); + } + if (MB_BIT(faces[index], 3)) { + add_cube(process, cube->i, cube->j + 1, cube->k); + } + if (MB_BIT(faces[index], 4)) { + add_cube(process, cube->i, cube->j, cube->k - 1); + } + if (MB_BIT(faces[index], 5)) { + add_cube(process, cube->i, cube->j, cube->k + 1); + } + + /* Using cubetable[], determines polygons for output. */ + for (polys = cubetable[index]; polys; polys = polys->next) { + INTLIST *edges; + + count = 0; + /* Sets needed vertex id's lying on the edges. */ + for (edges = polys->list; edges; edges = edges->next) { + c1 = cube->corners[corner1[edges->i]]; + c2 = cube->corners[corner2[edges->i]]; + + indexar[count] = vertid(process, c1, c2); + count++; + } + + /* Adds faces to output. */ + if (count > 2) { + switch (count) { + case 3: + make_face(process, indexar[2], indexar[1], indexar[0], indexar[0]); /* triangle */ + break; + case 4: + make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); + break; + case 5: + make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); + make_face(process, indexar[4], indexar[3], indexar[0], indexar[0]); /* triangle */ + break; + case 6: + make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); + make_face(process, indexar[5], indexar[4], indexar[3], indexar[0]); + break; + case 7: + make_face(process, indexar[3], indexar[2], indexar[1], indexar[0]); + make_face(process, indexar[5], indexar[4], indexar[3], indexar[0]); + make_face(process, indexar[6], indexar[5], indexar[0], indexar[0]); /* triangle */ + break; + } + } + } +} + +/** + * return corner with the given lattice location + * set (and cache) its function value + */ +static CORNER *setcorner(PROCESS *process, int i, int j, int k) +{ + /* for speed, do corner value caching here */ + CORNER *c; + int index; + + /* does corner exist? */ + index = HASH(i, j, k); + c = process->corners[index]; + + for (; c != NULL; c = c->next) { + if (c->i == i && c->j == j && c->k == k) { + return c; + } + } + + c = static_cast(BLI_memarena_alloc(process->pgn_elements, sizeof(CORNER))); + + c->i = i; + c->co[0] = ((float)i - 0.5f) * process->size; + c->j = j; + c->co[1] = ((float)j - 0.5f) * process->size; + c->k = k; + c->co[2] = ((float)k - 0.5f) * process->size; + + c->value = metaball(process, c->co[0], c->co[1], c->co[2]); + + c->next = process->corners[index]; + process->corners[index] = c; + + return c; +} + +/** + * return next clockwise edge from given edge around given face + */ +static int nextcwedge(int edge, int face) +{ + switch (edge) { + case LB: + return (face == L) ? LF : BN; + case LT: + return (face == L) ? LN : TF; + case LN: + return (face == L) ? LB : TN; + case LF: + return (face == L) ? LT : BF; + case RB: + return (face == R) ? RN : BF; + case RT: + return (face == R) ? RF : TN; + case RN: + return (face == R) ? RT : BN; + case RF: + return (face == R) ? RB : TF; + case BN: + return (face == B) ? RB : LN; + case BF: + return (face == B) ? LB : RF; + case TN: + return (face == T) ? LT : RN; + case TF: + return (face == T) ? RT : LF; + } + return 0; +} + +/** + * \return the face adjoining edge that is not the given face + */ +static int otherface(int edge, int face) +{ + int other = leftface[edge]; + return face == other ? rightface[edge] : other; +} + +/** + * create the 256 entry table for cubical polygonization + */ +static void makecubetable(void) +{ + static bool is_done = false; + int i, e, c, done[12], pos[8]; + + if (is_done) { + return; + } + is_done = true; + + for (i = 0; i < 256; i++) { + for (e = 0; e < 12; e++) { + done[e] = 0; + } + for (c = 0; c < 8; c++) { + pos[c] = MB_BIT(i, c); + } + for (e = 0; e < 12; e++) { + if (!done[e] && (pos[corner1[e]] != pos[corner2[e]])) { + INTLIST *ints = NULL; + INTLISTS *lists = static_cast(MEM_callocN(sizeof(INTLISTS), "mball_intlist")); + int start = e, edge = e; + + /* get face that is to right of edge from pos to neg corner: */ + int face = pos[corner1[e]] ? rightface[e] : leftface[e]; + + while (1) { + edge = nextcwedge(edge, face); + done[edge] = 1; + if (pos[corner1[edge]] != pos[corner2[edge]]) { + INTLIST *tmp = ints; + + ints = static_cast(MEM_callocN(sizeof(INTLIST), "mball_intlist")); + ints->i = edge; + ints->next = tmp; /* add edge to head of list */ + + if (edge == start) { + break; + } + face = otherface(edge, face); + } + } + lists->list = ints; /* add ints to head of table entry */ + lists->next = cubetable[i]; + cubetable[i] = lists; + } + } + } + + for (i = 0; i < 256; i++) { + INTLISTS *polys; + faces[i] = 0; + for (polys = cubetable[i]; polys; polys = polys->next) { + INTLIST *edges; + + for (edges = polys->list; edges; edges = edges->next) { + if (ELEM(edges->i, LB, LT, LN, LF)) { + faces[i] |= 1 << L; + } + if (ELEM(edges->i, RB, RT, RN, RF)) { + faces[i] |= 1 << R; + } + if (ELEM(edges->i, LB, RB, BN, BF)) { + faces[i] |= 1 << B; + } + if (ELEM(edges->i, LT, RT, TN, TF)) { + faces[i] |= 1 << T; + } + if (ELEM(edges->i, LN, RN, BN, TN)) { + faces[i] |= 1 << N; + } + if (ELEM(edges->i, LF, RF, BF, TF)) { + faces[i] |= 1 << F; + } + } + } + } +} + +void BKE_mball_cubeTable_free(void) +{ + for (int i = 0; i < 256; i++) { + INTLISTS *lists = cubetable[i]; + while (lists) { + INTLISTS *nlists = lists->next; + + INTLIST *ints = lists->list; + while (ints) { + INTLIST *nints = ints->next; + MEM_freeN(ints); + ints = nints; + } + + MEM_freeN(lists); + lists = nlists; + } + cubetable[i] = NULL; + } +} + +/**** Storage ****/ + +/** + * Inserts cube at lattice i, j, k into hash table, marking it as "done" + */ +static int setcenter(PROCESS *process, CENTERLIST *table[], const int i, const int j, const int k) +{ + int index; + CENTERLIST *newc, *l, *q; + + index = HASH(i, j, k); + q = table[index]; + + for (l = q; l != NULL; l = l->next) { + if (l->i == i && l->j == j && l->k == k) { + return 1; + } + } + + newc = static_cast(BLI_memarena_alloc(process->pgn_elements, sizeof(CENTERLIST))); + newc->i = i; + newc->j = j; + newc->k = k; + newc->next = q; + table[index] = newc; + + return 0; +} + +/** + * Sets vid of vertex lying on given edge. + */ +static void setedge(PROCESS *process, int i1, int j1, int k1, int i2, int j2, int k2, int vid) +{ + int index; + EDGELIST *newe; + + if (i1 > i2 || (i1 == i2 && (j1 > j2 || (j1 == j2 && k1 > k2)))) { + int t = i1; + i1 = i2; + i2 = t; + t = j1; + j1 = j2; + j2 = t; + t = k1; + k1 = k2; + k2 = t; + } + index = HASH(i1, j1, k1) + HASH(i2, j2, k2); + newe = static_cast(BLI_memarena_alloc(process->pgn_elements, sizeof(EDGELIST))); + + newe->i1 = i1; + newe->j1 = j1; + newe->k1 = k1; + newe->i2 = i2; + newe->j2 = j2; + newe->k2 = k2; + newe->vid = vid; + newe->next = process->edges[index]; + process->edges[index] = newe; +} + +/** + * \return vertex id for edge; return -1 if not set + */ +static int getedge(EDGELIST *table[], int i1, int j1, int k1, int i2, int j2, int k2) +{ + EDGELIST *q; + + if (i1 > i2 || (i1 == i2 && (j1 > j2 || (j1 == j2 && k1 > k2)))) { + int t = i1; + i1 = i2; + i2 = t; + t = j1; + j1 = j2; + j2 = t; + t = k1; + k1 = k2; + k2 = t; + } + q = table[HASH(i1, j1, k1) + HASH(i2, j2, k2)]; + for (; q != NULL; q = q->next) { + if (q->i1 == i1 && q->j1 == j1 && q->k1 == k1 && q->i2 == i2 && q->j2 == j2 && q->k2 == k2) { + return q->vid; + } + } + return -1; +} + +/** + * Adds a vertex, expands memory if needed. + */ +static void addtovertices(PROCESS *process, const float v[3], const float no[3]) +{ + if (UNLIKELY(process->curvertex == process->totvertex)) { + process->totvertex = process->totvertex ? process->totvertex * 2 : MBALL_ARRAY_LEN_INIT; + process->co = static_cast( + MEM_reallocN(process->co, process->totvertex * sizeof(float[3]))); + process->no = static_cast( + MEM_reallocN(process->no, process->totvertex * sizeof(float[3]))); + } + + copy_v3_v3(process->co[process->curvertex], v); + copy_v3_v3(process->no[process->curvertex], no); + + process->curvertex++; +} + +#ifndef USE_ACCUM_NORMAL +/** + * Computes normal from density field at given point. + * + * \note Doesn't do normalization! + */ +static void vnormal(PROCESS *process, const float point[3], float r_no[3]) +{ + const float delta = process->delta; + const float f = metaball(process, point[0], point[1], point[2]); + + r_no[0] = metaball(process, point[0] + delta, point[1], point[2]) - f; + r_no[1] = metaball(process, point[0], point[1] + delta, point[2]) - f; + r_no[2] = metaball(process, point[0], point[1], point[2] + delta) - f; +} +#endif /* USE_ACCUM_NORMAL */ + +/** + * \return the id of vertex between two corners. + * + * If it wasn't previously computed, does #converge() and adds vertex to process. + */ +static int vertid(PROCESS *process, const CORNER *c1, const CORNER *c2) +{ + float v[3], no[3]; + int vid = getedge(process->edges, c1->i, c1->j, c1->k, c2->i, c2->j, c2->k); + + if (vid != -1) { + return vid; /* previously computed */ + } + + converge(process, c1, c2, v); /* position */ + +#ifdef USE_ACCUM_NORMAL + zero_v3(no); +#else + vnormal(process, v, no); +#endif + + addtovertices(process, v, no); /* save vertex */ + vid = (int)process->curvertex - 1; + setedge(process, c1->i, c1->j, c1->k, c2->i, c2->j, c2->k, vid); + + return vid; +} + +/** + * Given two corners, computes approximation of surface intersection point between them. + * In case of small threshold, do bisection. + */ +static void converge(PROCESS *process, const CORNER *c1, const CORNER *c2, float r_p[3]) +{ + float c1_value, c1_co[3]; + float c2_value, c2_co[3]; + + if (c1->value < c2->value) { + c1_value = c2->value; + copy_v3_v3(c1_co, c2->co); + c2_value = c1->value; + copy_v3_v3(c2_co, c1->co); + } + else { + c1_value = c1->value; + copy_v3_v3(c1_co, c1->co); + c2_value = c2->value; + copy_v3_v3(c2_co, c2->co); + } + + for (uint i = 0; i < process->converge_res; i++) { + interp_v3_v3v3(r_p, c1_co, c2_co, 0.5f); + float dens = metaball(process, r_p[0], r_p[1], r_p[2]); + + if (dens > 0.0f) { + c1_value = dens; + copy_v3_v3(c1_co, r_p); + } + else { + c2_value = dens; + copy_v3_v3(c2_co, r_p); + } + } + + float tmp = -c1_value / (c2_value - c1_value); + interp_v3_v3v3(r_p, c1_co, c2_co, tmp); +} + +/** + * Adds cube at given lattice position to cube stack of process. + */ +static void add_cube(PROCESS *process, int i, int j, int k) +{ + CUBES *ncube; + int n; + + /* test if cube has been found before */ + if (setcenter(process, process->centers, i, j, k) == 0) { + /* push cube on stack: */ + ncube = static_cast(BLI_memarena_alloc(process->pgn_elements, sizeof(CUBES))); + ncube->next = process->cubes; + process->cubes = ncube; + + ncube->cube.i = i; + ncube->cube.j = j; + ncube->cube.k = k; + + /* set corners of initial cube: */ + for (n = 0; n < 8; n++) { + ncube->cube.corners[n] = setcorner( + process, i + MB_BIT(n, 2), j + MB_BIT(n, 1), k + MB_BIT(n, 0)); + } + } +} + +static void next_lattice(int r[3], const float pos[3], const float size) +{ + r[0] = (int)ceil((pos[0] / size) + 0.5f); + r[1] = (int)ceil((pos[1] / size) + 0.5f); + r[2] = (int)ceil((pos[2] / size) + 0.5f); +} +static void prev_lattice(int r[3], const float pos[3], const float size) +{ + next_lattice(r, pos, size); + r[0]--; + r[1]--; + r[2]--; +} +static void closest_latice(int r[3], const float pos[3], const float size) +{ + r[0] = (int)floorf(pos[0] / size + 1.0f); + r[1] = (int)floorf(pos[1] / size + 1.0f); + r[2] = (int)floorf(pos[2] / size + 1.0f); +} + +/** + * Find at most 26 cubes to start polygonization from. + */ +static void find_first_points(PROCESS *process, const uint em) +{ + const MetaElem *ml; + int center[3], lbn[3], rtf[3], it[3], dir[3], add[3]; + float tmp[3], a, b; + + ml = process->mainb[em]; + + mid_v3_v3v3(tmp, ml->bb->vec[0], ml->bb->vec[6]); + closest_latice(center, tmp, process->size); + prev_lattice(lbn, ml->bb->vec[0], process->size); + next_lattice(rtf, ml->bb->vec[6], process->size); + + for (dir[0] = -1; dir[0] <= 1; dir[0]++) { + for (dir[1] = -1; dir[1] <= 1; dir[1]++) { + for (dir[2] = -1; dir[2] <= 1; dir[2]++) { + if (dir[0] == 0 && dir[1] == 0 && dir[2] == 0) { + continue; + } + + copy_v3_v3_int(it, center); + + b = setcorner(process, it[0], it[1], it[2])->value; + do { + it[0] += dir[0]; + it[1] += dir[1]; + it[2] += dir[2]; + a = b; + b = setcorner(process, it[0], it[1], it[2])->value; + + if (a * b < 0.0f) { + add[0] = it[0] - dir[0]; + add[1] = it[1] - dir[1]; + add[2] = it[2] - dir[2]; + DO_MIN(it, add); + add_cube(process, add[0], add[1], add[2]); + break; + } + } while ((it[0] > lbn[0]) && (it[1] > lbn[1]) && (it[2] > lbn[2]) && (it[0] < rtf[0]) && + (it[1] < rtf[1]) && (it[2] < rtf[2])); + } + } + } +} + +/** + * The main polygonization proc. + * Allocates memory, makes cubetable, + * finds starting surface points + * and processes cubes on the stack until none left. + */ +static void polygonize(PROCESS *process) +{ + CUBE c; + + process->centers = static_cast( + MEM_callocN(HASHSIZE * sizeof(CENTERLIST *), "mbproc->centers")); + process->corners = static_cast( + MEM_callocN(HASHSIZE * sizeof(CORNER *), "mbproc->corners")); + process->edges = static_cast( + MEM_callocN(2 * HASHSIZE * sizeof(EDGELIST *), "mbproc->edges")); + process->bvh_queue = static_cast( + MEM_callocN(sizeof(MetaballBVHNode *) * process->bvh_queue_size, "Metaball BVH Queue")); + + makecubetable(); + + for (uint i = 0; i < process->totelem; i++) { + find_first_points(process, i); + } + + while (process->cubes != NULL) { + c = process->cubes->cube; + process->cubes = process->cubes->next; + + docube(process, &c); + } +} + +/** + * Iterates over ALL objects in the scene and all of its sets, including + * making all duplis (not only meta-elements). Copies meta-elements to #process.mainb array. + * Computes bounding boxes for building BVH. + */ +static void init_meta(Depsgraph *depsgraph, PROCESS *process, Scene *scene, Object *ob) +{ + Scene *sce_iter = scene; + Base *base; + Object *bob; + MetaBall *mb; + const MetaElem *ml; + float obinv[4][4], obmat[4][4]; + uint i; + int obnr, zero_size = 0; + char obname[MAX_ID_NAME]; + SceneBaseIter iter; + const eEvaluationMode deg_eval_mode = DEG_get_mode(depsgraph); + const short parenting_dupli_transflag = (OB_DUPLIFACES | OB_DUPLIVERTS); + + copy_m4_m4(obmat, ob->obmat); /* to cope with duplicators from BKE_scene_base_iter_next */ + invert_m4_m4(obinv, ob->obmat); + + BLI_split_name_num(obname, &obnr, ob->id.name + 2, '.'); + + /* make main array */ + BKE_scene_base_iter_next(depsgraph, &iter, &sce_iter, 0, NULL, NULL); + while (BKE_scene_base_iter_next(depsgraph, &iter, &sce_iter, 1, &base, &bob)) { + if (bob->type == OB_MBALL) { + zero_size = 0; + ml = NULL; + + /* If this metaball is the original that's used for duplication, only have it visible when + * the instancer is visible too. */ + if ((base->flag_legacy & OB_FROMDUPLI) == 0 && ob->parent != NULL && + (ob->parent->transflag & parenting_dupli_transflag) != 0 && + (BKE_object_visibility(ob->parent, deg_eval_mode) & OB_VISIBLE_SELF) == 0) { + continue; + } + + if (bob == ob && (base->flag_legacy & OB_FROMDUPLI) == 0) { + mb = static_cast(ob->data); + + if (mb->editelems) { + ml = static_cast(mb->editelems->first); + } + else { + ml = static_cast(mb->elems.first); + } + } + else { + char name[MAX_ID_NAME]; + int nr; + + BLI_split_name_num(name, &nr, bob->id.name + 2, '.'); + if (STREQ(obname, name)) { + mb = static_cast(bob->data); + + if (mb->editelems) { + ml = static_cast(mb->editelems->first); + } + else { + ml = static_cast(mb->elems.first); + } + } + } + + /* when metaball object has zero scale, then MetaElem to this MetaBall + * will not be put to mainb array */ + if (has_zero_axis_m4(bob->obmat)) { + zero_size = 1; + } + else if (bob->parent) { + struct Object *pob = bob->parent; + while (pob) { + if (has_zero_axis_m4(pob->obmat)) { + zero_size = 1; + break; + } + pob = pob->parent; + } + } + + if (zero_size) { + while (ml) { + ml = ml->next; + } + } + else { + while (ml) { + if (!(ml->flag & MB_HIDE)) { + float pos[4][4], rot[4][4]; + float expx, expy, expz; + float tempmin[3], tempmax[3]; + + MetaElem *new_ml; + + /* make a copy because of duplicates */ + new_ml = static_cast( + BLI_memarena_alloc(process->pgn_elements, sizeof(MetaElem))); + *(new_ml) = *ml; + new_ml->bb = static_cast( + BLI_memarena_alloc(process->pgn_elements, sizeof(BoundBox))); + new_ml->mat = static_cast( + BLI_memarena_alloc(process->pgn_elements, sizeof(float[4][4]))); + new_ml->imat = static_cast( + BLI_memarena_alloc(process->pgn_elements, sizeof(float[4][4]))); + + /* too big stiffness seems only ugly due to linear interpolation + * no need to have possibility for too big stiffness */ + if (ml->s > 10.0f) { + new_ml->s = 10.0f; + } + else { + new_ml->s = ml->s; + } + + /* if metaball is negative, set stiffness negative */ + if (new_ml->flag & MB_NEGATIVE) { + new_ml->s = -new_ml->s; + } + + /* Translation of MetaElem */ + unit_m4(pos); + pos[3][0] = ml->x; + pos[3][1] = ml->y; + pos[3][2] = ml->z; + + /* Rotation of MetaElem is stored in quat */ + quat_to_mat4(rot, ml->quat); + + /* Matrix multiply is as follows: + * basis object space -> + * world -> + * ml object space -> + * position -> + * rotation -> + * ml local space + */ + mul_m4_series((float(*)[4])new_ml->mat, obinv, bob->obmat, pos, rot); + /* ml local space -> basis object space */ + invert_m4_m4((float(*)[4])new_ml->imat, (float(*)[4])new_ml->mat); + + /* rad2 is inverse of squared radius */ + new_ml->rad2 = 1 / (ml->rad * ml->rad); + + /* initial dimensions = radius */ + expx = ml->rad; + expy = ml->rad; + expz = ml->rad; + + switch (ml->type) { + case MB_BALL: + break; + case MB_CUBE: /* cube is "expanded" by expz, expy and expx */ + expz += ml->expz; + ATTR_FALLTHROUGH; + case MB_PLANE: /* plane is "expanded" by expy and expx */ + expy += ml->expy; + ATTR_FALLTHROUGH; + case MB_TUBE: /* tube is "expanded" by expx */ + expx += ml->expx; + break; + case MB_ELIPSOID: /* ellipsoid is "stretched" by exp* */ + expx *= ml->expx; + expy *= ml->expy; + expz *= ml->expz; + break; + } + + /* untransformed Bounding Box of MetaElem */ + /* TODO: its possible the elem type has been changed and the exp* + * values can use a fallback. */ + copy_v3_fl3(new_ml->bb->vec[0], -expx, -expy, -expz); /* 0 */ + copy_v3_fl3(new_ml->bb->vec[1], +expx, -expy, -expz); /* 1 */ + copy_v3_fl3(new_ml->bb->vec[2], +expx, +expy, -expz); /* 2 */ + copy_v3_fl3(new_ml->bb->vec[3], -expx, +expy, -expz); /* 3 */ + copy_v3_fl3(new_ml->bb->vec[4], -expx, -expy, +expz); /* 4 */ + copy_v3_fl3(new_ml->bb->vec[5], +expx, -expy, +expz); /* 5 */ + copy_v3_fl3(new_ml->bb->vec[6], +expx, +expy, +expz); /* 6 */ + copy_v3_fl3(new_ml->bb->vec[7], -expx, +expy, +expz); /* 7 */ + + /* transformation of Metalem bb */ + for (i = 0; i < 8; i++) { + mul_m4_v3((float(*)[4])new_ml->mat, new_ml->bb->vec[i]); + } + + /* find max and min of transformed bb */ + INIT_MINMAX(tempmin, tempmax); + for (i = 0; i < 8; i++) { + DO_MINMAX(new_ml->bb->vec[i], tempmin, tempmax); + } + + /* set only point 0 and 6 - AABB of Metaelem */ + copy_v3_v3(new_ml->bb->vec[0], tempmin); + copy_v3_v3(new_ml->bb->vec[6], tempmax); + + /* add new_ml to mainb[] */ + if (UNLIKELY(process->totelem == process->mem)) { + process->mem = process->mem * 2 + 10; + process->mainb = static_cast( + MEM_reallocN(process->mainb, sizeof(MetaElem *) * process->mem)); + } + process->mainb[process->totelem++] = new_ml; + } + ml = ml->next; + } + } + } + } + + /* compute AABB of all Metaelems */ + if (process->totelem > 0) { + copy_v3_v3(process->allbb.min, process->mainb[0]->bb->vec[0]); + copy_v3_v3(process->allbb.max, process->mainb[0]->bb->vec[6]); + for (i = 1; i < process->totelem; i++) { + make_box_union(process->mainb[i]->bb, &process->allbb, &process->allbb); + } + } +} + +Mesh *BKE_mball_polygonize(Depsgraph *depsgraph, Scene *scene, Object *ob) +{ + PROCESS process = {0}; + const bool is_render = DEG_get_mode(depsgraph) == DAG_EVAL_RENDER; + + MetaBall *mb = static_cast(ob->data); + + process.thresh = mb->thresh; + + if (process.thresh < 0.001f) { + process.converge_res = 16; + } + else if (process.thresh < 0.01f) { + process.converge_res = 8; + } + else if (process.thresh < 0.1f) { + process.converge_res = 4; + } + else { + process.converge_res = 2; + } + + if (!is_render && (mb->flag == MB_UPDATE_NEVER)) { + return NULL; + } + if ((G.moving & (G_TRANSFORM_OBJ | G_TRANSFORM_EDIT)) && mb->flag == MB_UPDATE_FAST) { + return NULL; + } + + if (is_render) { + process.size = mb->rendersize; + } + else { + process.size = mb->wiresize; + if ((G.moving & (G_TRANSFORM_OBJ | G_TRANSFORM_EDIT)) && mb->flag == MB_UPDATE_HALFRES) { + process.size *= 2.0f; + } + } + + process.delta = process.size * 0.001f; + + process.pgn_elements = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, "Metaball memarena"); + + /* initialize all mainb (MetaElems) */ + init_meta(depsgraph, &process, scene, ob); + if (process.totelem == 0) { + freepolygonize(&process); + return NULL; + } + + build_bvh_spatial(&process, &process.metaball_bvh, 0, process.totelem, &process.allbb); + + /* Don't polygonize meta-balls with too high resolution (base mball too small) + * NOTE: Eps was 0.0001f but this was giving problems for blood animation for + * the open movie "Sintel", using 0.00001f. */ + if (ob->scale[0] < 0.00001f * (process.allbb.max[0] - process.allbb.min[0]) || + ob->scale[1] < 0.00001f * (process.allbb.max[1] - process.allbb.min[1]) || + ob->scale[2] < 0.00001f * (process.allbb.max[2] - process.allbb.min[2])) { + freepolygonize(&process); + return NULL; + } + + polygonize(&process); + if (process.curindex == 0) { + freepolygonize(&process); + return NULL; + } + + freepolygonize(&process); + + Mesh *mesh = (Mesh *)BKE_id_new_nomain(ID_ME, ((ID *)ob->data)->name + 2); + + mesh->totvert = (int)process.curvertex; + MVert *mvert = static_cast( + CustomData_add_layer(&mesh->vdata, CD_MVERT, CD_CONSTRUCT, NULL, mesh->totvert)); + for (int i = 0; i < mesh->totvert; i++) { + copy_v3_v3(mvert[i].co, process.co[i]); + } + MEM_freeN(process.co); + + mesh->totpoly = (int)process.curindex; + MPoly *mpoly = static_cast( + CustomData_add_layer(&mesh->pdata, CD_MPOLY, CD_CONSTRUCT, NULL, mesh->totpoly)); + MLoop *mloop = static_cast( + CustomData_add_layer(&mesh->ldata, CD_MLOOP, CD_CONSTRUCT, NULL, mesh->totpoly * 4)); + + int loop_offset = 0; + for (int i = 0; i < mesh->totpoly; i++) { + const int *indices = process.indices[i]; + + const int count = indices[2] != indices[3] ? 4 : 3; + mpoly[i].loopstart = loop_offset; + mpoly[i].totloop = count; + mpoly[i].flag = ME_SMOOTH; + + mloop[loop_offset].v = (uint32_t)indices[0]; + mloop[loop_offset + 1].v = (uint32_t)indices[1]; + mloop[loop_offset + 2].v = (uint32_t)indices[2]; + if (count == 4) { + mloop[loop_offset + 3].v = (uint32_t)indices[3]; + } + + loop_offset += count; + } + MEM_freeN(process.indices); + + for (int i = 0; i < mesh->totvert; i++) { + normalize_v3(process.no[i]); + } + mesh->runtime.vert_normals = process.no; + BKE_mesh_vertex_normals_clear_dirty(mesh); + + mesh->totloop = loop_offset; + + BKE_mesh_calc_edges(mesh, false, false); + + return mesh; +} diff --git a/source/blender/editors/include/ED_mesh.h b/source/blender/editors/include/ED_mesh.h index 26743a2bd08..a47ffe0b99a 100644 --- a/source/blender/editors/include/ED_mesh.h +++ b/source/blender/editors/include/ED_mesh.h @@ -197,7 +197,7 @@ void EDBM_automerge_and_split(struct Object *obedit, /** Export for ED_undo_sys. */ void ED_mesh_undosys_type(struct UndoType *ut); -/* editmesh_select.c */ +/* editmesh_select.cc */ void EDBM_select_mirrored(struct BMEditMesh *em, const struct Mesh *me, diff --git a/source/blender/editors/include/ED_view3d.h b/source/blender/editors/include/ED_view3d.h index 7c7b5771a04..9eceb5260ed 100644 --- a/source/blender/editors/include/ED_view3d.h +++ b/source/blender/editors/include/ED_view3d.h @@ -228,6 +228,7 @@ typedef enum { /** outside range (mainly for short), (can't avoid) */ V3D_PROJ_RET_OVERFLOW = 6, } eV3DProjStatus; +ENUM_OPERATORS(eV3DProjStatus, V3D_PROJ_RET_OVERFLOW); /* some clipping tests are optional */ typedef enum { @@ -350,7 +351,7 @@ void ED_view3d_cursor_snap_draw_util(struct RegionView3D *rv3d, const uchar color_point[4], eSnapMode snap_elem_type); -/* view3d_iterators.c */ +/* view3d_iterators.cc */ /* foreach iterators */ diff --git a/source/blender/editors/mesh/CMakeLists.txt b/source/blender/editors/mesh/CMakeLists.txt index 218564eaf30..c1049e32d4c 100644 --- a/source/blender/editors/mesh/CMakeLists.txt +++ b/source/blender/editors/mesh/CMakeLists.txt @@ -45,7 +45,7 @@ set(SRC editmesh_preselect_elem.c editmesh_rip.c editmesh_rip_edge.c - editmesh_select.c + editmesh_select.cc editmesh_select_similar.c editmesh_tools.c editmesh_undo.c diff --git a/source/blender/editors/mesh/editmesh_loopcut.c b/source/blender/editors/mesh/editmesh_loopcut.c index 591e06be80c..46d6cb4d006 100644 --- a/source/blender/editors/mesh/editmesh_loopcut.c +++ b/source/blender/editors/mesh/editmesh_loopcut.c @@ -226,7 +226,7 @@ static void ringsel_finish(bContext *C, wmOperator *op) } else { /* XXX Is this piece of code ever used now? Simple loop select is now - * in editmesh_select.c (around line 1000)... */ + * in editmesh_select.cc (around line 1000)... */ /* sets as active, useful for other tools */ if (em->selectmode & SCE_SELECT_VERTEX) { /* low priority TODO: get vertrex close to mouse. */ diff --git a/source/blender/editors/mesh/editmesh_select.c b/source/blender/editors/mesh/editmesh_select.c deleted file mode 100644 index 34f7301c4b1..00000000000 --- a/source/blender/editors/mesh/editmesh_select.c +++ /dev/null @@ -1,5344 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later - * Copyright 2004 Blender Foundation. All rights reserved. */ - -/** \file - * \ingroup edmesh - */ - -#include "MEM_guardedalloc.h" - -#include "BLI_array.h" -#include "BLI_bitmap.h" -#include "BLI_heap.h" -#include "BLI_linklist.h" -#include "BLI_linklist_stack.h" -#include "BLI_listbase.h" -#include "BLI_math.h" -#include "BLI_math_bits.h" -#include "BLI_rand.h" -#include "BLI_string.h" -#include "BLI_utildefines_stack.h" - -#include "BKE_context.h" -#include "BKE_customdata.h" -#include "BKE_deform.h" -#include "BKE_editmesh.h" -#include "BKE_layer.h" -#include "BKE_report.h" - -#include "WM_api.h" -#include "WM_types.h" - -#include "RNA_access.h" -#include "RNA_define.h" -#include "RNA_enum_types.h" - -#include "ED_mesh.h" -#include "ED_object.h" -#include "ED_screen.h" -#include "ED_select_utils.h" -#include "ED_transform.h" -#include "ED_view3d.h" - -#include "BLT_translation.h" - -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_object_types.h" - -#include "UI_resources.h" - -#include "bmesh_tools.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_query.h" - -#include "DRW_select_buffer.h" - -#include "mesh_intern.h" /* own include */ - -/* use bmesh operator flags for a few operators */ -#define BMO_ELE_TAG 1 - -/* -------------------------------------------------------------------- */ -/** \name Select Mirror - * \{ */ - -void EDBM_select_mirrored(BMEditMesh *em, - const Mesh *me, - const int axis, - const bool extend, - int *r_totmirr, - int *r_totfail) -{ - BMesh *bm = em->bm; - BMIter iter; - int totmirr = 0; - int totfail = 0; - bool use_topology = me->editflag & ME_EDIT_MIRROR_TOPO; - - *r_totmirr = *r_totfail = 0; - - /* select -> tag */ - if (bm->selectmode & SCE_SELECT_VERTEX) { - BMVert *v; - BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - BM_elem_flag_set(v, BM_ELEM_TAG, BM_elem_flag_test(v, BM_ELEM_SELECT)); - } - } - else if (em->selectmode & SCE_SELECT_EDGE) { - BMEdge *e; - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT)); - } - } - else { - BMFace *f; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - BM_elem_flag_set(f, BM_ELEM_TAG, BM_elem_flag_test(f, BM_ELEM_SELECT)); - } - } - - EDBM_verts_mirror_cache_begin(em, axis, true, true, false, use_topology); - - if (!extend) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - if (bm->selectmode & SCE_SELECT_VERTEX) { - BMVert *v; - BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN) && BM_elem_flag_test(v, BM_ELEM_TAG)) { - BMVert *v_mirr = EDBM_verts_mirror_get(em, v); - if (v_mirr && !BM_elem_flag_test(v_mirr, BM_ELEM_HIDDEN)) { - BM_vert_select_set(bm, v_mirr, true); - totmirr++; - } - else { - totfail++; - } - } - } - } - else if (em->selectmode & SCE_SELECT_EDGE) { - BMEdge *e; - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN) && BM_elem_flag_test(e, BM_ELEM_TAG)) { - BMEdge *e_mirr = EDBM_verts_mirror_get_edge(em, e); - if (e_mirr && !BM_elem_flag_test(e_mirr, BM_ELEM_HIDDEN)) { - BM_edge_select_set(bm, e_mirr, true); - totmirr++; - } - else { - totfail++; - } - } - } - } - else { - BMFace *f; - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN) && BM_elem_flag_test(f, BM_ELEM_TAG)) { - BMFace *f_mirr = EDBM_verts_mirror_get_face(em, f); - if (f_mirr && !BM_elem_flag_test(f_mirr, BM_ELEM_HIDDEN)) { - BM_face_select_set(bm, f_mirr, true); - totmirr++; - } - else { - totfail++; - } - } - } - } - - EDBM_verts_mirror_cache_end(em); - - *r_totmirr = totmirr; - *r_totfail = totfail; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Back-Buffer OpenGL Selection - * \{ */ - -static BMElem *edbm_select_id_bm_elem_get(Base **bases, const uint sel_id, uint *r_base_index) -{ - uint elem_id; - char elem_type = 0; - bool success = DRW_select_buffer_elem_get(sel_id, &elem_id, r_base_index, &elem_type); - - if (success) { - Object *obedit = bases[*r_base_index]->object; - BMEditMesh *em = BKE_editmesh_from_object(obedit); - - switch (elem_type) { - case SCE_SELECT_FACE: - return (BMElem *)BM_face_at_index_find_or_table(em->bm, elem_id); - case SCE_SELECT_EDGE: - return (BMElem *)BM_edge_at_index_find_or_table(em->bm, elem_id); - case SCE_SELECT_VERTEX: - return (BMElem *)BM_vert_at_index_find_or_table(em->bm, elem_id); - default: - BLI_assert(0); - return NULL; - } - } - - return NULL; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Find Nearest Vert/Edge/Face - * - * \note Screen-space manhattan distances are used here, - * since its faster and good enough for the purpose of selection. - * - * \note \a dist_bias is used so we can bias against selected items. - * when choosing between elements of a single type, but return the real distance - * to avoid the bias interfering with distance comparisons when mixing types. - * \{ */ - -#define FIND_NEAR_SELECT_BIAS 5 -#define FIND_NEAR_CYCLE_THRESHOLD_MIN 3 - -struct NearestVertUserData_Hit { - float dist; - float dist_bias; - int index; - BMVert *vert; -}; - -struct NearestVertUserData { - float mval_fl[2]; - bool use_select_bias; - bool use_cycle; - int cycle_index_prev; - - struct NearestVertUserData_Hit hit; - struct NearestVertUserData_Hit hit_cycle; -}; - -static void findnearestvert__doClosest(void *userData, - BMVert *eve, - const float screen_co[2], - int index) -{ - struct NearestVertUserData *data = userData; - float dist_test, dist_test_bias; - - dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); - - if (data->use_select_bias && BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - dist_test_bias += FIND_NEAR_SELECT_BIAS; - } - - if (dist_test_bias < data->hit.dist_bias) { - data->hit.dist_bias = dist_test_bias; - data->hit.dist = dist_test; - data->hit.index = index; - data->hit.vert = eve; - } - - if (data->use_cycle) { - if ((data->hit_cycle.vert == NULL) && (index > data->cycle_index_prev) && - (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { - data->hit_cycle.dist_bias = dist_test_bias; - data->hit_cycle.dist = dist_test; - data->hit_cycle.index = index; - data->hit_cycle.vert = eve; - } - } -} - -BMVert *EDBM_vert_find_nearest_ex(ViewContext *vc, - float *dist_px_manhattan_p, - const bool use_select_bias, - bool use_cycle, - Base **bases, - uint bases_len, - uint *r_base_index) -{ - uint base_index = 0; - - if (!XRAY_FLAG_ENABLED(vc->v3d)) { - uint dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, - *dist_px_manhattan_p); - uint index; - BMVert *eve; - - /* No afterqueue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ - { - DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_VERTEX); - - index = DRW_select_buffer_find_nearest_to_point( - vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); - - if (index) { - eve = (BMVert *)edbm_select_id_bm_elem_get(bases, index, &base_index); - } - else { - eve = NULL; - } - } - - if (eve) { - if (dist_px_manhattan_test < *dist_px_manhattan_p) { - if (r_base_index) { - *r_base_index = base_index; - } - *dist_px_manhattan_p = dist_px_manhattan_test; - return eve; - } - } - return NULL; - } - - struct NearestVertUserData data = {{0}}; - const struct NearestVertUserData_Hit *hit = NULL; - const eV3DProjTest clip_flag = RV3D_CLIPPING_ENABLED(vc->v3d, vc->rv3d) ? - V3D_PROJ_TEST_CLIP_DEFAULT : - V3D_PROJ_TEST_CLIP_DEFAULT & ~V3D_PROJ_TEST_CLIP_BB; - BMesh *prev_select_bm = NULL; - - static struct { - int index; - const BMVert *elem; - const BMesh *bm; - } prev_select = {0}; - - data.mval_fl[0] = vc->mval[0]; - data.mval_fl[1] = vc->mval[1]; - data.use_select_bias = use_select_bias; - data.use_cycle = use_cycle; - - for (; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - ED_view3d_viewcontext_init_object(vc, base_iter->object); - if (use_cycle && prev_select.bm == vc->em->bm && - prev_select.elem == BM_vert_at_index_find_or_table(vc->em->bm, prev_select.index)) { - data.cycle_index_prev = prev_select.index; - /* No need to compare in the rest of the loop. */ - use_cycle = false; - } - else { - data.cycle_index_prev = 0; - } - - data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = - *dist_px_manhattan_p; - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - mesh_foreachScreenVert(vc, findnearestvert__doClosest, &data, clip_flag); - - hit = (data.use_cycle && data.hit_cycle.vert) ? &data.hit_cycle : &data.hit; - - if (hit->dist < *dist_px_manhattan_p) { - if (r_base_index) { - *r_base_index = base_index; - } - *dist_px_manhattan_p = hit->dist; - prev_select_bm = vc->em->bm; - } - } - - if (hit == NULL) { - return NULL; - } - - prev_select.index = hit->index; - prev_select.elem = hit->vert; - prev_select.bm = prev_select_bm; - - return hit->vert; -} - -BMVert *EDBM_vert_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) -{ - BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); - Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); - return EDBM_vert_find_nearest_ex(vc, dist_px_manhattan_p, false, false, &base, 1, NULL); -} - -/* find the distance to the edge we already have */ -struct NearestEdgeUserData_ZBuf { - float mval_fl[2]; - float dist; - const BMEdge *edge_test; -}; - -static void find_nearest_edge_center__doZBuf(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int UNUSED(index)) -{ - struct NearestEdgeUserData_ZBuf *data = userData; - - if (eed == data->edge_test) { - float dist_test; - float screen_co_mid[2]; - - mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); - dist_test = len_manhattan_v2v2(data->mval_fl, screen_co_mid); - - if (dist_test < data->dist) { - data->dist = dist_test; - } - } -} - -struct NearestEdgeUserData_Hit { - float dist; - float dist_bias; - int index; - BMEdge *edge; - - /* edges only, un-biased manhattan distance to which ever edge we pick - * (not used for choosing) */ - float dist_center_px_manhattan; -}; - -struct NearestEdgeUserData { - ViewContext vc; - float mval_fl[2]; - bool use_select_bias; - bool use_cycle; - int cycle_index_prev; - - struct NearestEdgeUserData_Hit hit; - struct NearestEdgeUserData_Hit hit_cycle; -}; - -/* NOTE: uses v3d, so needs active 3d window. */ -static void find_nearest_edge__doClosest( - void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) -{ - struct NearestEdgeUserData *data = userData; - float dist_test, dist_test_bias; - - float fac = line_point_factor_v2(data->mval_fl, screen_co_a, screen_co_b); - float screen_co[2]; - - if (fac <= 0.0f) { - fac = 0.0f; - copy_v2_v2(screen_co, screen_co_a); - } - else if (fac >= 1.0f) { - fac = 1.0f; - copy_v2_v2(screen_co, screen_co_b); - } - else { - interp_v2_v2v2(screen_co, screen_co_a, screen_co_b, fac); - } - - dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); - - if (data->use_select_bias && BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - dist_test_bias += FIND_NEAR_SELECT_BIAS; - } - - if (data->vc.rv3d->rflag & RV3D_CLIPPING) { - float vec[3]; - - interp_v3_v3v3(vec, eed->v1->co, eed->v2->co, fac); - if (ED_view3d_clipping_test(data->vc.rv3d, vec, true)) { - return; - } - } - - if (dist_test_bias < data->hit.dist_bias) { - float screen_co_mid[2]; - - data->hit.dist_bias = dist_test_bias; - data->hit.dist = dist_test; - data->hit.index = index; - data->hit.edge = eed; - - mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); - data->hit.dist_center_px_manhattan = len_manhattan_v2v2(data->mval_fl, screen_co_mid); - } - - if (data->use_cycle) { - if ((data->hit_cycle.edge == NULL) && (index > data->cycle_index_prev) && - (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { - float screen_co_mid[2]; - - data->hit_cycle.dist_bias = dist_test_bias; - data->hit_cycle.dist = dist_test; - data->hit_cycle.index = index; - data->hit_cycle.edge = eed; - - mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); - data->hit_cycle.dist_center_px_manhattan = len_manhattan_v2v2(data->mval_fl, screen_co_mid); - } - } -} - -BMEdge *EDBM_edge_find_nearest_ex(ViewContext *vc, - float *dist_px_manhattan_p, - float *r_dist_center_px_manhattan, - const bool use_select_bias, - bool use_cycle, - BMEdge **r_eed_zbuf, - Base **bases, - uint bases_len, - uint *r_base_index) -{ - uint base_index = 0; - - if (!XRAY_FLAG_ENABLED(vc->v3d)) { - uint dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, - *dist_px_manhattan_p); - uint index; - BMEdge *eed; - - /* No afterqueue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ - { - DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_EDGE); - - index = DRW_select_buffer_find_nearest_to_point( - vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); - - if (index) { - eed = (BMEdge *)edbm_select_id_bm_elem_get(bases, index, &base_index); - } - else { - eed = NULL; - } - } - - if (r_eed_zbuf) { - *r_eed_zbuf = eed; - } - - /* exception for faces (verts don't need this) */ - if (r_dist_center_px_manhattan && eed) { - struct NearestEdgeUserData_ZBuf data; - - data.mval_fl[0] = vc->mval[0]; - data.mval_fl[1] = vc->mval[1]; - data.dist = FLT_MAX; - data.edge_test = eed; - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - mesh_foreachScreenEdge(vc, - find_nearest_edge_center__doZBuf, - &data, - V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - - *r_dist_center_px_manhattan = data.dist; - } - /* end exception */ - - if (eed) { - if (dist_px_manhattan_test < *dist_px_manhattan_p) { - if (r_base_index) { - *r_base_index = base_index; - } - *dist_px_manhattan_p = dist_px_manhattan_test; - return eed; - } - } - return NULL; - } - - struct NearestEdgeUserData data = {{0}}; - const struct NearestEdgeUserData_Hit *hit = NULL; - /* interpolate along the edge before doing a clipping plane test */ - const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT & ~V3D_PROJ_TEST_CLIP_BB; - BMesh *prev_select_bm = NULL; - - static struct { - int index; - const BMEdge *elem; - const BMesh *bm; - } prev_select = {0}; - - data.vc = *vc; - data.mval_fl[0] = vc->mval[0]; - data.mval_fl[1] = vc->mval[1]; - data.use_select_bias = use_select_bias; - data.use_cycle = use_cycle; - - for (; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - ED_view3d_viewcontext_init_object(vc, base_iter->object); - if (use_cycle && prev_select.bm == vc->em->bm && - prev_select.elem == BM_edge_at_index_find_or_table(vc->em->bm, prev_select.index)) { - data.cycle_index_prev = prev_select.index; - /* No need to compare in the rest of the loop. */ - use_cycle = false; - } - else { - data.cycle_index_prev = 0; - } - - data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = - *dist_px_manhattan_p; - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - mesh_foreachScreenEdge( - vc, find_nearest_edge__doClosest, &data, clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); - - hit = (data.use_cycle && data.hit_cycle.edge) ? &data.hit_cycle : &data.hit; - - if (hit->dist < *dist_px_manhattan_p) { - if (r_base_index) { - *r_base_index = base_index; - } - *dist_px_manhattan_p = hit->dist; - prev_select_bm = vc->em->bm; - } - } - - if (hit == NULL) { - return NULL; - } - - if (r_dist_center_px_manhattan) { - *r_dist_center_px_manhattan = hit->dist_center_px_manhattan; - } - - prev_select.index = hit->index; - prev_select.elem = hit->edge; - prev_select.bm = prev_select_bm; - - return hit->edge; -} - -BMEdge *EDBM_edge_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) -{ - BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); - Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); - return EDBM_edge_find_nearest_ex( - vc, dist_px_manhattan_p, NULL, false, false, NULL, &base, 1, NULL); -} - -/* find the distance to the face we already have */ -struct NearestFaceUserData_ZBuf { - float mval_fl[2]; - float dist_px_manhattan; - const BMFace *face_test; -}; - -static void find_nearest_face_center__doZBuf(void *userData, - BMFace *efa, - const float screen_co[2], - int UNUSED(index)) -{ - struct NearestFaceUserData_ZBuf *data = userData; - - if (efa == data->face_test) { - const float dist_test = len_manhattan_v2v2(data->mval_fl, screen_co); - - if (dist_test < data->dist_px_manhattan) { - data->dist_px_manhattan = dist_test; - } - } -} - -struct NearestFaceUserData_Hit { - float dist; - float dist_bias; - int index; - BMFace *face; -}; - -struct NearestFaceUserData { - float mval_fl[2]; - bool use_select_bias; - bool use_cycle; - int cycle_index_prev; - - struct NearestFaceUserData_Hit hit; - struct NearestFaceUserData_Hit hit_cycle; -}; - -static void findnearestface__doClosest(void *userData, - BMFace *efa, - const float screen_co[2], - int index) -{ - struct NearestFaceUserData *data = userData; - float dist_test, dist_test_bias; - - dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); - - if (data->use_select_bias && BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - dist_test_bias += FIND_NEAR_SELECT_BIAS; - } - - if (dist_test_bias < data->hit.dist_bias) { - data->hit.dist_bias = dist_test_bias; - data->hit.dist = dist_test; - data->hit.index = index; - data->hit.face = efa; - } - - if (data->use_cycle) { - if ((data->hit_cycle.face == NULL) && (index > data->cycle_index_prev) && - (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { - data->hit_cycle.dist_bias = dist_test_bias; - data->hit_cycle.dist = dist_test; - data->hit_cycle.index = index; - data->hit_cycle.face = efa; - } - } -} - -BMFace *EDBM_face_find_nearest_ex(ViewContext *vc, - float *dist_px_manhattan_p, - float *r_dist_center, - const bool use_zbuf_single_px, - const bool use_select_bias, - bool use_cycle, - BMFace **r_efa_zbuf, - Base **bases, - uint bases_len, - uint *r_base_index) -{ - uint base_index = 0; - - if (!XRAY_FLAG_ENABLED(vc->v3d)) { - float dist_test; - uint index; - BMFace *efa; - - { - uint dist_px_manhattan_test = 0; - if (*dist_px_manhattan_p != 0.0f && (use_zbuf_single_px == false)) { - dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, - *dist_px_manhattan_p); - } - - DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_FACE); - - if (dist_px_manhattan_test == 0) { - index = DRW_select_buffer_sample_point(vc->depsgraph, vc->region, vc->v3d, vc->mval); - dist_test = 0.0f; - } - else { - index = DRW_select_buffer_find_nearest_to_point( - vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); - dist_test = dist_px_manhattan_test; - } - - if (index) { - efa = (BMFace *)edbm_select_id_bm_elem_get(bases, index, &base_index); - } - else { - efa = NULL; - } - } - - if (r_efa_zbuf) { - *r_efa_zbuf = efa; - } - - /* exception for faces (verts don't need this) */ - if (r_dist_center && efa) { - struct NearestFaceUserData_ZBuf data; - - data.mval_fl[0] = vc->mval[0]; - data.mval_fl[1] = vc->mval[1]; - data.dist_px_manhattan = FLT_MAX; - data.face_test = efa; - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - - mesh_foreachScreenFace( - vc, find_nearest_face_center__doZBuf, &data, V3D_PROJ_TEST_CLIP_DEFAULT); - - *r_dist_center = data.dist_px_manhattan; - } - /* end exception */ - - if (efa) { - if (dist_test < *dist_px_manhattan_p) { - if (r_base_index) { - *r_base_index = base_index; - } - *dist_px_manhattan_p = dist_test; - return efa; - } - } - return NULL; - } - - struct NearestFaceUserData data = {{0}}; - const struct NearestFaceUserData_Hit *hit = NULL; - const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT; - BMesh *prev_select_bm = NULL; - - static struct { - int index; - const BMFace *elem; - const BMesh *bm; - } prev_select = {0}; - - data.mval_fl[0] = vc->mval[0]; - data.mval_fl[1] = vc->mval[1]; - data.use_select_bias = use_select_bias; - data.use_cycle = use_cycle; - - for (; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - ED_view3d_viewcontext_init_object(vc, base_iter->object); - if (use_cycle && prev_select.bm == vc->em->bm && - prev_select.elem == BM_face_at_index_find_or_table(vc->em->bm, prev_select.index)) { - data.cycle_index_prev = prev_select.index; - /* No need to compare in the rest of the loop. */ - use_cycle = false; - } - else { - data.cycle_index_prev = 0; - } - - data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = - *dist_px_manhattan_p; - - ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); - mesh_foreachScreenFace(vc, findnearestface__doClosest, &data, clip_flag); - - hit = (data.use_cycle && data.hit_cycle.face) ? &data.hit_cycle : &data.hit; - - if (hit->dist < *dist_px_manhattan_p) { - if (r_base_index) { - *r_base_index = base_index; - } - *dist_px_manhattan_p = hit->dist; - prev_select_bm = vc->em->bm; - } - } - - if (hit == NULL) { - return NULL; - } - - if (r_dist_center) { - *r_dist_center = hit->dist; - } - - prev_select.index = hit->index; - prev_select.elem = hit->face; - prev_select.bm = prev_select_bm; - - return hit->face; -} - -BMFace *EDBM_face_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) -{ - BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); - Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); - return EDBM_face_find_nearest_ex( - vc, dist_px_manhattan_p, NULL, false, false, false, NULL, &base, 1, NULL); -} - -#undef FIND_NEAR_SELECT_BIAS -#undef FIND_NEAR_CYCLE_THRESHOLD_MIN - -/* best distance based on screen coords. - * use em->selectmode to define how to use - * selected vertices and edges get disadvantage - * return 1 if found one - */ -static bool unified_findnearest(ViewContext *vc, - Base **bases, - const uint bases_len, - int *r_base_index, - BMVert **r_eve, - BMEdge **r_eed, - BMFace **r_efa) -{ - BMEditMesh *em = vc->em; - - const bool use_cycle = !WM_cursor_test_motion_and_update(vc->mval); - const float dist_init = ED_view3d_select_dist_px(); - /* since edges select lines, we give dots advantage of ~20 pix */ - const float dist_margin = (dist_init / 2); - float dist = dist_init; - - struct { - struct { - BMVert *ele; - int base_index; - } v; - struct { - BMEdge *ele; - int base_index; - } e, e_zbuf; - struct { - BMFace *ele; - int base_index; - } f, f_zbuf; - } hit = {{NULL}}; - - /* no afterqueue (yet), so we check it now, otherwise the em_xxxofs indices are bad */ - - if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_FACE)) { - float dist_center = 0.0f; - float *dist_center_p = (em->selectmode & (SCE_SELECT_EDGE | SCE_SELECT_VERTEX)) ? - &dist_center : - NULL; - - uint base_index = 0; - BMFace *efa_zbuf = NULL; - BMFace *efa_test = EDBM_face_find_nearest_ex( - vc, &dist, dist_center_p, true, true, use_cycle, &efa_zbuf, bases, bases_len, &base_index); - - if (efa_test && dist_center_p) { - dist = min_ff(dist_margin, dist_center); - } - if (efa_test) { - hit.f.base_index = base_index; - hit.f.ele = efa_test; - } - if (efa_zbuf) { - hit.f_zbuf.base_index = base_index; - hit.f_zbuf.ele = efa_zbuf; - } - } - - if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_EDGE)) { - float dist_center = 0.0f; - float *dist_center_p = (em->selectmode & SCE_SELECT_VERTEX) ? &dist_center : NULL; - - uint base_index = 0; - BMEdge *eed_zbuf = NULL; - BMEdge *eed_test = EDBM_edge_find_nearest_ex( - vc, &dist, dist_center_p, true, use_cycle, &eed_zbuf, bases, bases_len, &base_index); - - if (eed_test && dist_center_p) { - dist = min_ff(dist_margin, dist_center); - } - if (eed_test) { - hit.e.base_index = base_index; - hit.e.ele = eed_test; - } - if (eed_zbuf) { - hit.e_zbuf.base_index = base_index; - hit.e_zbuf.ele = eed_zbuf; - } - } - - if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_VERTEX)) { - uint base_index = 0; - BMVert *eve_test = EDBM_vert_find_nearest_ex( - vc, &dist, true, use_cycle, bases, bases_len, &base_index); - - if (eve_test) { - hit.v.base_index = base_index; - hit.v.ele = eve_test; - } - } - - /* Return only one of 3 pointers, for front-buffer redraws. */ - if (hit.v.ele) { - hit.f.ele = NULL; - hit.e.ele = NULL; - } - else if (hit.e.ele) { - hit.f.ele = NULL; - } - - /* there may be a face under the cursor, who's center if too far away - * use this if all else fails, it makes sense to select this */ - if ((hit.v.ele || hit.e.ele || hit.f.ele) == 0) { - if (hit.e_zbuf.ele) { - hit.e.base_index = hit.e_zbuf.base_index; - hit.e.ele = hit.e_zbuf.ele; - } - else if (hit.f_zbuf.ele) { - hit.f.base_index = hit.f_zbuf.base_index; - hit.f.ele = hit.f_zbuf.ele; - } - } - - /* Only one element type will be non-null. */ - BLI_assert(((hit.v.ele != NULL) + (hit.e.ele != NULL) + (hit.f.ele != NULL)) <= 1); - - if (hit.v.ele) { - *r_base_index = hit.v.base_index; - } - if (hit.e.ele) { - *r_base_index = hit.e.base_index; - } - if (hit.f.ele) { - *r_base_index = hit.f.base_index; - } - - *r_eve = hit.v.ele; - *r_eed = hit.e.ele; - *r_efa = hit.f.ele; - - return (hit.v.ele || hit.e.ele || hit.f.ele); -} - -#undef FAKE_SELECT_MODE_BEGIN -#undef FAKE_SELECT_MODE_END - -bool EDBM_unified_findnearest(ViewContext *vc, - Base **bases, - const uint bases_len, - int *r_base_index, - BMVert **r_eve, - BMEdge **r_eed, - BMFace **r_efa) -{ - return unified_findnearest(vc, bases, bases_len, r_base_index, r_eve, r_eed, r_efa); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Alternate Find Nearest Vert/Edge (optional boundary) - * - * \note This uses ray-cast method instead of back-buffer, - * currently used for poly-build. - * \{ */ - -bool EDBM_unified_findnearest_from_raycast(ViewContext *vc, - Base **bases, - const uint bases_len, - bool use_boundary_vertices, - bool use_boundary_edges, - int *r_base_index_vert, - int *r_base_index_edge, - int *r_base_index_face, - struct BMVert **r_eve, - struct BMEdge **r_eed, - struct BMFace **r_efa) -{ - - const float mval_fl[2] = {UNPACK2(vc->mval)}; - float ray_origin[3], ray_direction[3]; - - struct { - uint base_index; - BMElem *ele; - } best = {0, NULL}; - /* Currently unused, keep since we may want to pick the best. */ - UNUSED_VARS(best); - - struct { - uint base_index; - BMElem *ele; - } best_vert = {0, NULL}; - - struct { - uint base_index; - BMElem *ele; - } best_edge = {0, NULL}; - - struct { - uint base_index; - BMElem *ele; - } best_face = {0, NULL}; - - if (ED_view3d_win_to_ray_clipped( - vc->depsgraph, vc->region, vc->v3d, mval_fl, ray_origin, ray_direction, true)) { - float dist_sq_best = FLT_MAX; - float dist_sq_best_vert = FLT_MAX; - float dist_sq_best_edge = FLT_MAX; - float dist_sq_best_face = FLT_MAX; - - const bool use_vert = (r_eve != NULL); - const bool use_edge = (r_eed != NULL); - const bool use_face = (r_efa != NULL); - - for (uint base_index = 0; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - Object *obedit = base_iter->object; - - BMEditMesh *em = BKE_editmesh_from_object(obedit); - BMesh *bm = em->bm; - float imat3[3][3]; - - ED_view3d_viewcontext_init_object(vc, obedit); - copy_m3_m4(imat3, obedit->obmat); - invert_m3(imat3); - - const float(*coords)[3] = NULL; - { - Mesh *me_eval = (Mesh *)DEG_get_evaluated_id(vc->depsgraph, obedit->data); - if (me_eval->runtime.edit_data) { - coords = me_eval->runtime.edit_data->vertexCos; - } - } - - if (coords != NULL) { - BM_mesh_elem_index_ensure(bm, BM_VERT); - } - - if ((use_boundary_vertices || use_boundary_edges) && (use_vert || use_edge)) { - BMEdge *e; - BMIter eiter; - BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { - if ((BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false) && (BM_edge_is_boundary(e))) { - if (use_vert && use_boundary_vertices) { - for (uint j = 0; j < 2; j++) { - BMVert *v = *((&e->v1) + j); - float point[3]; - mul_v3_m4v3(point, obedit->obmat, coords ? coords[BM_elem_index_get(v)] : v->co); - const float dist_sq_test = dist_squared_to_ray_v3_normalized( - ray_origin, ray_direction, point); - if (dist_sq_test < dist_sq_best_vert) { - dist_sq_best_vert = dist_sq_test; - best_vert.base_index = base_index; - best_vert.ele = (BMElem *)v; - } - if (dist_sq_test < dist_sq_best) { - dist_sq_best = dist_sq_test; - best.base_index = base_index; - best.ele = (BMElem *)v; - } - } - } - - if (use_edge && use_boundary_edges) { - float point[3]; -#if 0 - const float dist_sq_test = dist_squared_ray_to_seg_v3( - ray_origin, ray_direction, e->v1->co, e->v2->co, point, &depth); -#else - if (coords) { - mid_v3_v3v3( - point, coords[BM_elem_index_get(e->v1)], coords[BM_elem_index_get(e->v2)]); - } - else { - mid_v3_v3v3(point, e->v1->co, e->v2->co); - } - mul_m4_v3(obedit->obmat, point); - const float dist_sq_test = dist_squared_to_ray_v3_normalized( - ray_origin, ray_direction, point); - if (dist_sq_test < dist_sq_best_edge) { - dist_sq_best_edge = dist_sq_test; - best_edge.base_index = base_index; - best_edge.ele = (BMElem *)e; - } - if (dist_sq_test < dist_sq_best) { - dist_sq_best = dist_sq_test; - best.base_index = base_index; - best.ele = (BMElem *)e; - } -#endif - } - } - } - } - /* Non boundary case. */ - if (use_vert && !use_boundary_vertices) { - BMVert *v; - BMIter viter; - BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(v, BM_ELEM_HIDDEN) == false) { - float point[3]; - mul_v3_m4v3(point, obedit->obmat, coords ? coords[BM_elem_index_get(v)] : v->co); - const float dist_sq_test = dist_squared_to_ray_v3_normalized( - ray_origin, ray_direction, point); - if (dist_sq_test < dist_sq_best_vert) { - dist_sq_best_vert = dist_sq_test; - best_vert.base_index = base_index; - best_vert.ele = (BMElem *)v; - } - if (dist_sq_test < dist_sq_best) { - dist_sq_best = dist_sq_test; - best.base_index = base_index; - best.ele = (BMElem *)v; - } - } - } - } - - if (use_edge && !use_boundary_edges) { - BMEdge *e; - BMIter eiter; - BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false) { - float point[3]; - if (coords) { - mid_v3_v3v3( - point, coords[BM_elem_index_get(e->v1)], coords[BM_elem_index_get(e->v2)]); - } - else { - mid_v3_v3v3(point, e->v1->co, e->v2->co); - } - mul_m4_v3(obedit->obmat, point); - const float dist_sq_test = dist_squared_to_ray_v3_normalized( - ray_origin, ray_direction, point); - if (dist_sq_test < dist_sq_best_edge) { - dist_sq_best_edge = dist_sq_test; - best_edge.base_index = base_index; - best_edge.ele = (BMElem *)e; - } - if (dist_sq_test < dist_sq_best) { - dist_sq_best = dist_sq_test; - best.base_index = base_index; - best.ele = (BMElem *)e; - } - } - } - } - - if (use_face) { - BMFace *f; - BMIter fiter; - BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_HIDDEN) == false) { - float point[3]; - if (coords) { - BM_face_calc_center_median_vcos(bm, f, point, coords); - } - else { - BM_face_calc_center_median(f, point); - } - mul_m4_v3(obedit->obmat, point); - const float dist_sq_test = dist_squared_to_ray_v3_normalized( - ray_origin, ray_direction, point); - if (dist_sq_test < dist_sq_best_face) { - dist_sq_best_face = dist_sq_test; - best_face.base_index = base_index; - best_face.ele = (BMElem *)f; - } - if (dist_sq_test < dist_sq_best) { - dist_sq_best = dist_sq_test; - best.base_index = base_index; - best.ele = (BMElem *)f; - } - } - } - } - } - } - - *r_base_index_vert = best_vert.base_index; - *r_base_index_edge = best_edge.base_index; - *r_base_index_face = best_face.base_index; - - if (r_eve) { - *r_eve = NULL; - } - if (r_eed) { - *r_eed = NULL; - } - if (r_efa) { - *r_efa = NULL; - } - - if (best_vert.ele) { - *r_eve = (BMVert *)best_vert.ele; - } - if (best_edge.ele) { - *r_eed = (BMEdge *)best_edge.ele; - } - if (best_face.ele) { - *r_efa = (BMFace *)best_face.ele; - } - - return (best_vert.ele != NULL || best_edge.ele != NULL || best_face.ele != NULL); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Similar Region Operator - * \{ */ - -static int edbm_select_similar_region_exec(bContext *C, wmOperator *op) -{ - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = BKE_editmesh_from_object(obedit); - BMesh *bm = em->bm; - bool changed = false; - - /* group vars */ - int *groups_array; - int(*group_index)[2]; - int group_tot; - int i; - - if (bm->totfacesel < 2) { - BKE_report(op->reports, RPT_ERROR, "No face regions selected"); - return OPERATOR_CANCELLED; - } - - groups_array = MEM_mallocN(sizeof(*groups_array) * bm->totfacesel, __func__); - group_tot = BM_mesh_calc_face_groups( - bm, groups_array, &group_index, NULL, NULL, NULL, BM_ELEM_SELECT, BM_VERT); - - BM_mesh_elem_table_ensure(bm, BM_FACE); - - for (i = 0; i < group_tot; i++) { - ListBase faces_regions; - int tot; - - const int fg_sta = group_index[i][0]; - const int fg_len = group_index[i][1]; - int j; - BMFace **fg = MEM_mallocN(sizeof(*fg) * fg_len, __func__); - - for (j = 0; j < fg_len; j++) { - fg[j] = BM_face_at_index(bm, groups_array[fg_sta + j]); - } - - tot = BM_mesh_region_match(bm, fg, fg_len, &faces_regions); - - MEM_freeN(fg); - - if (tot) { - LinkData *link; - while ((link = BLI_pophead(&faces_regions))) { - BMFace *f, **faces = link->data; - while ((f = *(faces++))) { - BM_face_select_set(bm, f, true); - } - MEM_freeN(link->data); - MEM_freeN(link); - - changed = true; - } - } - } - - MEM_freeN(groups_array); - MEM_freeN(group_index); - - if (changed) { - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - else { - BKE_report(op->reports, RPT_WARNING, "No matching face regions found"); - } - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_similar_region(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Similar Regions"; - ot->idname = "MESH_OT_select_similar_region"; - ot->description = "Select similar face regions to the current selection"; - - /* api callbacks */ - ot->exec = edbm_select_similar_region_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Mode Vert/Edge/Face Operator - * \{ */ - -static int edbm_select_mode_exec(bContext *C, wmOperator *op) -{ - const int type = RNA_enum_get(op->ptr, "type"); - const int action = RNA_enum_get(op->ptr, "action"); - const bool use_extend = RNA_boolean_get(op->ptr, "use_extend"); - const bool use_expand = RNA_boolean_get(op->ptr, "use_expand"); - - if (EDBM_selectmode_toggle_multi(C, type, action, use_extend, use_expand)) { - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -static int edbm_select_mode_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - /* Bypass when in UV non sync-select mode, fall through to keymap that edits. */ - if (CTX_wm_space_image(C)) { - ToolSettings *ts = CTX_data_tool_settings(C); - if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { - return OPERATOR_PASS_THROUGH; - } - /* Bypass when no action is needed. */ - if (!RNA_struct_property_is_set(op->ptr, "type")) { - return OPERATOR_CANCELLED; - } - } - - /* detecting these options based on shift/ctrl here is weak, but it's done - * to make this work when clicking buttons or menus */ - if (!RNA_struct_property_is_set(op->ptr, "use_extend")) { - RNA_boolean_set(op->ptr, "use_extend", event->modifier & KM_SHIFT); - } - if (!RNA_struct_property_is_set(op->ptr, "use_expand")) { - RNA_boolean_set(op->ptr, "use_expand", event->modifier & KM_CTRL); - } - - return edbm_select_mode_exec(C, op); -} - -static char *edbm_select_mode_get_description(struct bContext *UNUSED(C), - struct wmOperatorType *UNUSED(op), - struct PointerRNA *values) -{ - const int type = RNA_enum_get(values, "type"); - - /* Because the special behavior for shift and ctrl click depend on user input, they may be - * incorrect if the operator is used from a script or from a special button. So only return the - * specialized descriptions if only the "type" is set, which conveys that the operator is meant - * to be used with the logic in the `invoke` method. */ - if (RNA_struct_property_is_set(values, "type") && - !RNA_struct_property_is_set(values, "use_extend") && - !RNA_struct_property_is_set(values, "use_expand") && - !RNA_struct_property_is_set(values, "action")) { - switch (type) { - case SCE_SELECT_VERTEX: - return BLI_strdup(TIP_( - "Vertex select - Shift-Click for multiple modes, Ctrl-Click contracts selection")); - case SCE_SELECT_EDGE: - return BLI_strdup( - TIP_("Edge select - Shift-Click for multiple modes, " - "Ctrl-Click expands/contracts selection depending on the current mode")); - case SCE_SELECT_FACE: - return BLI_strdup( - TIP_("Face select - Shift-Click for multiple modes, Ctrl-Click expands selection")); - } - } - - return NULL; -} - -void MESH_OT_select_mode(wmOperatorType *ot) -{ - PropertyRNA *prop; - - static const EnumPropertyItem actions_items[] = { - {0, "DISABLE", 0, "Disable", "Disable selected markers"}, - {1, "ENABLE", 0, "Enable", "Enable selected markers"}, - {2, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"}, - {0, NULL, 0, NULL, NULL}, - }; - - /* identifiers */ - ot->name = "Select Mode"; - ot->idname = "MESH_OT_select_mode"; - ot->description = "Change selection mode"; - - /* api callbacks */ - ot->invoke = edbm_select_mode_invoke; - ot->exec = edbm_select_mode_exec; - ot->poll = ED_operator_editmesh; - ot->get_description = edbm_select_mode_get_description; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - /* Hide all, not to show redo panel. */ - prop = RNA_def_boolean(ot->srna, "use_extend", false, "Extend", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "use_expand", false, "Expand", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - ot->prop = prop = RNA_def_enum(ot->srna, "type", rna_enum_mesh_select_mode_items, 0, "Type", ""); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - - prop = RNA_def_enum( - ot->srna, "action", actions_items, 2, "Action", "Selection action to execute"); - RNA_def_property_flag(prop, PROP_HIDDEN); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Loop (Non Modal) Operator - * \{ */ - -static void walker_select_count(BMEditMesh *em, - int walkercode, - void *start, - int r_count_by_select[2]) -{ - BMesh *bm = em->bm; - BMElem *ele; - BMWalker walker; - - r_count_by_select[0] = r_count_by_select[1] = 0; - - BMW_init(&walker, - bm, - walkercode, - BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - for (ele = BMW_begin(&walker, start); ele; ele = BMW_step(&walker)) { - r_count_by_select[BM_elem_flag_test(ele, BM_ELEM_SELECT) ? 1 : 0] += 1; - - /* Early exit when mixed (could be optional if needed. */ - if (r_count_by_select[0] && r_count_by_select[1]) { - r_count_by_select[0] = r_count_by_select[1] = -1; - break; - } - } - - BMW_end(&walker); -} - -static void walker_select(BMEditMesh *em, int walkercode, void *start, const bool select) -{ - BMesh *bm = em->bm; - BMElem *ele; - BMWalker walker; - - BMW_init(&walker, - bm, - walkercode, - BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - for (ele = BMW_begin(&walker, start); ele; ele = BMW_step(&walker)) { - if (!select) { - BM_select_history_remove(bm, ele); - } - BM_elem_select_set(bm, ele, select); - } - BMW_end(&walker); -} - -static int edbm_loop_multiselect_exec(bContext *C, wmOperator *op) -{ - const bool is_ring = RNA_boolean_get(op->ptr, "ring"); - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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->totedgesel == 0) { - continue; - } - - BMEdge *eed; - BMEdge **edarray; - int edindex; - BMIter iter; - int totedgesel = 0; - - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - totedgesel++; - } - } - - edarray = MEM_mallocN(sizeof(BMEdge *) * totedgesel, "edge array"); - edindex = 0; - - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - edarray[edindex] = eed; - edindex++; - } - } - - if (is_ring) { - for (edindex = 0; edindex < totedgesel; edindex += 1) { - eed = edarray[edindex]; - walker_select(em, BMW_EDGERING, eed, true); - } - EDBM_selectmode_flush(em); - } - else { - for (edindex = 0; edindex < totedgesel; edindex += 1) { - eed = edarray[edindex]; - bool non_manifold = BM_edge_face_count_is_over(eed, 2); - if (non_manifold) { - walker_select(em, BMW_EDGELOOP_NONMANIFOLD, eed, true); - } - else { - walker_select(em, BMW_EDGELOOP, eed, true); - } - } - EDBM_selectmode_flush(em); - } - MEM_freeN(edarray); - // if (EM_texFaceCheck()) - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_loop_multi_select(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Multi Select Loops"; - ot->idname = "MESH_OT_loop_multi_select"; - ot->description = "Select a loop of connected edges by connection type"; - - /* api callbacks */ - ot->exec = edbm_loop_multiselect_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - RNA_def_boolean(ot->srna, "ring", 0, "Ring", ""); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Loop (Cursor Pick) Operator - * \{ */ - -static void mouse_mesh_loop_face(BMEditMesh *em, BMEdge *eed, bool select, bool select_clear) -{ - if (select_clear) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - walker_select(em, BMW_FACELOOP, eed, select); -} - -static void mouse_mesh_loop_edge_ring(BMEditMesh *em, BMEdge *eed, bool select, bool select_clear) -{ - if (select_clear) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - walker_select(em, BMW_EDGERING, eed, select); -} - -static void mouse_mesh_loop_edge( - BMEditMesh *em, BMEdge *eed, bool select, bool select_clear, bool select_cycle) -{ - bool edge_boundary = false; - bool non_manifold = BM_edge_face_count_is_over(eed, 2); - - /* Cycle between BMW_EDGELOOP / BMW_EDGEBOUNDARY. */ - if (select_cycle && BM_edge_is_boundary(eed)) { - int count_by_select[2]; - - /* If the loops selected toggle the boundaries. */ - walker_select_count(em, BMW_EDGELOOP, eed, count_by_select); - if (count_by_select[!select] == 0) { - edge_boundary = true; - - /* If the boundaries selected, toggle back to the loop. */ - walker_select_count(em, BMW_EDGEBOUNDARY, eed, count_by_select); - if (count_by_select[!select] == 0) { - edge_boundary = false; - } - } - } - - if (select_clear) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - if (edge_boundary) { - walker_select(em, BMW_EDGEBOUNDARY, eed, select); - } - else if (non_manifold) { - walker_select(em, BMW_EDGELOOP_NONMANIFOLD, eed, select); - } - else { - walker_select(em, BMW_EDGELOOP, eed, select); - } -} - -static bool mouse_mesh_loop( - bContext *C, const int mval[2], bool extend, bool deselect, bool toggle, bool ring) -{ - Base *basact = NULL; - BMVert *eve = NULL; - BMEdge *eed = NULL; - BMFace *efa = NULL; - - ViewContext vc; - BMEditMesh *em; - bool select = true; - bool select_clear = false; - bool select_cycle = true; - float mvalf[2]; - - em_setup_viewcontext(C, &vc); - mvalf[0] = (float)(vc.mval[0] = mval[0]); - mvalf[1] = (float)(vc.mval[1] = mval[1]); - - BMEditMesh *em_original = vc.em; - const short selectmode = em_original->selectmode; - em_original->selectmode = SCE_SELECT_EDGE; - - uint bases_len; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( - vc.scene, vc.view_layer, vc.v3d, &bases_len); - - { - int base_index = -1; - if (EDBM_unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa)) { - basact = bases[base_index]; - ED_view3d_viewcontext_init_object(&vc, basact->object); - em = vc.em; - } - else { - em = NULL; - } - } - - em_original->selectmode = selectmode; - - if (em == NULL || eed == NULL) { - MEM_freeN(bases); - return false; - } - - if (extend == false && deselect == false && toggle == false) { - select_clear = true; - } - - if (extend) { - select = true; - } - else if (deselect) { - select = false; - } - else if (select_clear || (BM_elem_flag_test(eed, BM_ELEM_SELECT) == 0)) { - select = true; - } - else if (toggle) { - select = false; - select_cycle = false; - } - - if (select_clear) { - for (uint base_index = 0; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - Object *ob_iter = base_iter->object; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - - if (em_iter->bm->totvertsel == 0) { - continue; - } - - if (em_iter == em) { - continue; - } - - EDBM_flag_disable_all(em_iter, BM_ELEM_SELECT); - DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT); - } - } - - if (em->selectmode & SCE_SELECT_FACE) { - mouse_mesh_loop_face(em, eed, select, select_clear); - } - else { - if (ring) { - mouse_mesh_loop_edge_ring(em, eed, select, select_clear); - } - else { - mouse_mesh_loop_edge(em, eed, select, select_clear, select_cycle); - } - } - - EDBM_selectmode_flush(em); - - /* sets as active, useful for other tools */ - if (select) { - if (em->selectmode & SCE_SELECT_VERTEX) { - /* Find nearest vert from mouse - * (initialize to large values in case only one vertex can be projected) */ - float v1_co[2], v2_co[2]; - float length_1 = FLT_MAX; - float length_2 = FLT_MAX; - - /* We can't be sure this has already been set... */ - ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); - - if (ED_view3d_project_float_object(vc.region, eed->v1->co, v1_co, V3D_PROJ_TEST_CLIP_NEAR) == - V3D_PROJ_RET_OK) { - length_1 = len_squared_v2v2(mvalf, v1_co); - } - - if (ED_view3d_project_float_object(vc.region, eed->v2->co, v2_co, V3D_PROJ_TEST_CLIP_NEAR) == - V3D_PROJ_RET_OK) { - length_2 = len_squared_v2v2(mvalf, v2_co); - } -#if 0 - printf("mouse to v1: %f\nmouse to v2: %f\n", - len_squared_v2v2(mvalf, v1_co), - len_squared_v2v2(mvalf, v2_co)); -#endif - BM_select_history_store(em->bm, (length_1 < length_2) ? eed->v1 : eed->v2); - } - else if (em->selectmode & SCE_SELECT_EDGE) { - BM_select_history_store(em->bm, eed); - } - else if (em->selectmode & SCE_SELECT_FACE) { - /* Select the face of eed which is the nearest of mouse. */ - BMFace *f; - BMIter iterf; - float best_dist = FLT_MAX; - efa = NULL; - - /* We can't be sure this has already been set... */ - ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); - - BM_ITER_ELEM (f, &iterf, eed, BM_FACES_OF_EDGE) { - if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { - float cent[3]; - float co[2], tdist; - - BM_face_calc_center_median(f, cent); - if (ED_view3d_project_float_object(vc.region, cent, co, V3D_PROJ_TEST_CLIP_NEAR) == - V3D_PROJ_RET_OK) { - tdist = len_squared_v2v2(mvalf, co); - if (tdist < best_dist) { - // printf("Best face: %p (%f)\n", f, tdist); - best_dist = tdist; - efa = f; - } - } - } - } - if (efa) { - BM_mesh_active_face_set(em->bm, efa); - BM_select_history_store(em->bm, efa); - } - } - } - - MEM_freeN(bases); - - DEG_id_tag_update(vc.obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); - - return true; -} - -static int edbm_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - - view3d_operator_needs_opengl(C); - - if (mouse_mesh_loop(C, - event->mval, - RNA_boolean_get(op->ptr, "extend"), - RNA_boolean_get(op->ptr, "deselect"), - RNA_boolean_get(op->ptr, "toggle"), - RNA_boolean_get(op->ptr, "ring"))) { - return OPERATOR_FINISHED; - } - return OPERATOR_CANCELLED; -} - -void MESH_OT_loop_select(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Loop Select"; - ot->idname = "MESH_OT_loop_select"; - ot->description = "Select a loop of connected edges"; - - /* api callbacks */ - ot->invoke = edbm_select_loop_invoke; - ot->poll = ED_operator_editmesh_region_view3d; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* properties */ - PropertyRNA *prop; - - prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend Select", "Extend the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "ring", 0, "Select Ring", "Select ring"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -void MESH_OT_edgering_select(wmOperatorType *ot) -{ - /* description */ - ot->name = "Edge Ring Select"; - ot->idname = "MESH_OT_edgering_select"; - ot->description = "Select an edge ring"; - - /* callbacks */ - ot->invoke = edbm_select_loop_invoke; - ot->poll = ED_operator_editmesh_region_view3d; - - /* flags */ - ot->flag = OPTYPE_UNDO; - - /* Properties. */ - PropertyRNA *prop; - prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); - prop = RNA_def_boolean(ot->srna, "ring", 1, "Select Ring", "Select ring"); - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name (De)Select All Operator - * \{ */ - -static int edbm_select_all_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - int action = RNA_enum_get(op->ptr, "action"); - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &objects_len); - - if (action == SEL_TOGGLE) { - action = SEL_SELECT; - 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->totvertsel || em->bm->totedgesel || em->bm->totfacesel) { - action = SEL_DESELECT; - break; - } - } - } - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *obedit = objects[ob_index]; - BMEditMesh *em = BKE_editmesh_from_object(obedit); - switch (action) { - case SEL_SELECT: - EDBM_flag_enable_all(em, BM_ELEM_SELECT); - break; - case SEL_DESELECT: - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - break; - case SEL_INVERT: - EDBM_select_swap(em); - EDBM_selectmode_flush(em); - break; - } - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_all(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "(De)select All"; - ot->idname = "MESH_OT_select_all"; - ot->description = "(De)select all vertices, edges or faces"; - - /* api callbacks */ - ot->exec = edbm_select_all_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - WM_operator_properties_select_all(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Interior Faces Operator - * \{ */ - -static int edbm_faces_select_interior_exec(bContext *C, wmOperator *UNUSED(op)) -{ - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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 (!EDBM_select_interior_faces(em)) { - continue; - } - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_interior_faces(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Interior Faces"; - ot->idname = "MESH_OT_select_interior_faces"; - ot->description = "Select faces where all edges have more than 2 face users"; - - /* api callbacks */ - ot->exec = edbm_faces_select_interior_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Picking API - * - * Here actual select happens, - * Gets called via generic mouse select operator. - * \{ */ - -bool EDBM_select_pick(bContext *C, const int mval[2], const struct SelectPick_Params *params) -{ - ViewContext vc; - - int base_index_active = -1; - BMVert *eve = NULL; - BMEdge *eed = NULL; - BMFace *efa = NULL; - - /* setup view context for argument to callbacks */ - em_setup_viewcontext(C, &vc); - vc.mval[0] = mval[0]; - vc.mval[1] = mval[1]; - - uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( - vc.scene, vc.view_layer, vc.v3d, &bases_len); - - bool changed = false; - bool found = unified_findnearest(&vc, bases, bases_len, &base_index_active, &eve, &eed, &efa); - - if (params->sel_op == SEL_OP_SET) { - BMElem *ele = efa ? (BMElem *)efa : (eed ? (BMElem *)eed : (BMElem *)eve); - if ((found && params->select_passthrough) && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { - found = false; - } - else if (found || params->deselect_all) { - /* Deselect everything. */ - for (uint base_index = 0; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - Object *ob_iter = base_iter->object; - EDBM_flag_disable_all(BKE_editmesh_from_object(ob_iter), BM_ELEM_SELECT); - DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); - } - changed = true; - } - } - - if (found) { - Base *basact = bases[base_index_active]; - ED_view3d_viewcontext_init_object(&vc, basact->object); - - if (efa) { - switch (params->sel_op) { - case SEL_OP_ADD: { - BM_mesh_active_face_set(vc.em->bm, efa); - - /* Work-around: deselect first, so we can guarantee it will - * be active even if it was already selected. */ - BM_select_history_remove(vc.em->bm, efa); - BM_face_select_set(vc.em->bm, efa, false); - BM_select_history_store(vc.em->bm, efa); - BM_face_select_set(vc.em->bm, efa, true); - break; - } - case SEL_OP_SUB: { - BM_select_history_remove(vc.em->bm, efa); - BM_face_select_set(vc.em->bm, efa, false); - break; - } - case SEL_OP_XOR: { - BM_mesh_active_face_set(vc.em->bm, efa); - if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - BM_select_history_store(vc.em->bm, efa); - BM_face_select_set(vc.em->bm, efa, true); - } - else { - BM_select_history_remove(vc.em->bm, efa); - BM_face_select_set(vc.em->bm, efa, false); - } - break; - } - case SEL_OP_SET: { - BM_mesh_active_face_set(vc.em->bm, efa); - if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - BM_select_history_store(vc.em->bm, efa); - BM_face_select_set(vc.em->bm, efa, true); - } - break; - } - case SEL_OP_AND: { - BLI_assert_unreachable(); /* Doesn't make sense for picking. */ - break; - } - } - } - else if (eed) { - - switch (params->sel_op) { - case SEL_OP_ADD: { - /* Work-around: deselect first, so we can guarantee it will - * be active even if it was already selected. */ - BM_select_history_remove(vc.em->bm, eed); - BM_edge_select_set(vc.em->bm, eed, false); - BM_select_history_store(vc.em->bm, eed); - BM_edge_select_set(vc.em->bm, eed, true); - break; - } - case SEL_OP_SUB: { - BM_select_history_remove(vc.em->bm, eed); - BM_edge_select_set(vc.em->bm, eed, false); - break; - } - case SEL_OP_XOR: { - if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - BM_select_history_store(vc.em->bm, eed); - BM_edge_select_set(vc.em->bm, eed, true); - } - else { - BM_select_history_remove(vc.em->bm, eed); - BM_edge_select_set(vc.em->bm, eed, false); - } - break; - } - case SEL_OP_SET: { - if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - BM_select_history_store(vc.em->bm, eed); - BM_edge_select_set(vc.em->bm, eed, true); - } - break; - } - case SEL_OP_AND: { - BLI_assert_unreachable(); /* Doesn't make sense for picking. */ - break; - } - } - } - else if (eve) { - switch (params->sel_op) { - case SEL_OP_ADD: { - /* Work-around: deselect first, so we can guarantee it will - * be active even if it was already selected. */ - BM_select_history_remove(vc.em->bm, eve); - BM_vert_select_set(vc.em->bm, eve, false); - BM_select_history_store(vc.em->bm, eve); - BM_vert_select_set(vc.em->bm, eve, true); - break; - } - case SEL_OP_SUB: { - BM_select_history_remove(vc.em->bm, eve); - BM_vert_select_set(vc.em->bm, eve, false); - break; - } - case SEL_OP_XOR: { - if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - BM_select_history_store(vc.em->bm, eve); - BM_vert_select_set(vc.em->bm, eve, true); - } - else { - BM_select_history_remove(vc.em->bm, eve); - BM_vert_select_set(vc.em->bm, eve, false); - } - break; - } - case SEL_OP_SET: { - if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { - BM_select_history_store(vc.em->bm, eve); - BM_vert_select_set(vc.em->bm, eve, true); - } - break; - } - case SEL_OP_AND: { - BLI_assert_unreachable(); /* Doesn't make sense for picking. */ - break; - } - } - } - - EDBM_selectmode_flush(vc.em); - - if (efa) { - /* Change active material on object. */ - if (efa->mat_nr != vc.obedit->actcol - 1) { - vc.obedit->actcol = efa->mat_nr + 1; - vc.em->mat_nr = efa->mat_nr; - WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, NULL); - } - - /* Change active face-map on object. */ - if (!BLI_listbase_is_empty(&vc.obedit->fmaps)) { - const int cd_fmap_offset = CustomData_get_offset(&vc.em->bm->pdata, CD_FACEMAP); - if (cd_fmap_offset != -1) { - int map = *((int *)BM_ELEM_CD_GET_VOID_P(efa, cd_fmap_offset)); - if ((map < -1) || (map > BLI_listbase_count_at_most(&vc.obedit->fmaps, map))) { - map = -1; - } - map += 1; - if (map != vc.obedit->actfmap) { - /* We may want to add notifiers later, - * currently select update handles redraw. */ - vc.obedit->actfmap = map; - } - } - } - } - - /* Changing active object is handy since it allows us to - * switch UV layers, vgroups for eg. */ - BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); - if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { - ED_object_base_activate(C, basact); - } - - DEG_id_tag_update(vc.obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); - - changed = true; - } - - MEM_freeN(bases); - - return changed; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Mode Utilities - * \{ */ - -static void edbm_strip_selections(BMEditMesh *em) -{ - BMEditSelection *ese, *nextese; - - if (!(em->selectmode & SCE_SELECT_VERTEX)) { - ese = em->bm->selected.first; - while (ese) { - nextese = ese->next; - if (ese->htype == BM_VERT) { - BLI_freelinkN(&(em->bm->selected), ese); - } - ese = nextese; - } - } - if (!(em->selectmode & SCE_SELECT_EDGE)) { - ese = em->bm->selected.first; - while (ese) { - nextese = ese->next; - if (ese->htype == BM_EDGE) { - BLI_freelinkN(&(em->bm->selected), ese); - } - ese = nextese; - } - } - if (!(em->selectmode & SCE_SELECT_FACE)) { - ese = em->bm->selected.first; - while (ese) { - nextese = ese->next; - if (ese->htype == BM_FACE) { - BLI_freelinkN(&(em->bm->selected), ese); - } - ese = nextese; - } - } -} - -void EDBM_selectmode_set(BMEditMesh *em) -{ - BMVert *eve; - BMEdge *eed; - BMFace *efa; - BMIter iter; - - em->bm->selectmode = em->selectmode; - - /* strip BMEditSelections from em->selected that are not relevant to new mode */ - edbm_strip_selections(em); - - if (em->bm->totvertsel == 0 && em->bm->totedgesel == 0 && em->bm->totfacesel == 0) { - return; - } - - if (em->selectmode & SCE_SELECT_VERTEX) { - if (em->bm->totvertsel) { - EDBM_select_flush(em); - } - } - else if (em->selectmode & SCE_SELECT_EDGE) { - /* deselect vertices, and select again based on edge select */ - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - BM_vert_select_set(em->bm, eve, false); - } - - if (em->bm->totedgesel) { - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { - BM_edge_select_set(em->bm, eed, true); - } - } - - /* selects faces based on edge status */ - EDBM_selectmode_flush(em); - } - } - else if (em->selectmode & SCE_SELECT_FACE) { - /* Deselect edges, and select again based on face select. */ - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - BM_edge_select_set(em->bm, eed, false); - } - - if (em->bm->totfacesel) { - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) { - BM_face_select_set(em->bm, efa, true); - } - } - } - } -} - -void EDBM_selectmode_convert(BMEditMesh *em, - const short selectmode_old, - const short selectmode_new) -{ - BMesh *bm = em->bm; - - BMVert *eve; - BMEdge *eed; - BMFace *efa; - BMIter iter; - - /* first tag-to-select, then select --- this avoids a feedback loop */ - - /* Have to find out what the selection-mode was previously. */ - if (selectmode_old == SCE_SELECT_VERTEX) { - if (bm->totvertsel == 0) { - /* pass */ - } - else if (selectmode_new == SCE_SELECT_EDGE) { - /* flush up (vert -> edge) */ - - /* select all edges associated with every selected vert */ - BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { - BM_elem_flag_set(eed, BM_ELEM_TAG, BM_edge_is_any_vert_flag_test(eed, BM_ELEM_SELECT)); - } - - BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(eed, BM_ELEM_TAG)) { - BM_edge_select_set(bm, eed, true); - } - } - } - else if (selectmode_new == SCE_SELECT_FACE) { - /* flush up (vert -> face) */ - - /* select all faces associated with every selected vert */ - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - BM_elem_flag_set(efa, BM_ELEM_TAG, BM_face_is_any_vert_flag_test(efa, BM_ELEM_SELECT)); - } - - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { - BM_face_select_set(bm, efa, true); - } - } - } - } - else if (selectmode_old == SCE_SELECT_EDGE) { - if (bm->totedgesel == 0) { - /* pass */ - } - else if (selectmode_new == SCE_SELECT_FACE) { - /* flush up (edge -> face) */ - - /* select all faces associated with every selected edge */ - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - BM_elem_flag_set(efa, BM_ELEM_TAG, BM_face_is_any_edge_flag_test(efa, BM_ELEM_SELECT)); - } - - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { - BM_face_select_set(bm, efa, true); - } - } - } - else if (selectmode_new == SCE_SELECT_VERTEX) { - /* flush down (edge -> vert) */ - - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - if (!BM_vert_is_all_edge_flag_test(eve, BM_ELEM_SELECT, true)) { - BM_vert_select_set(bm, eve, false); - } - } - /* deselect edges without both verts selected */ - BM_mesh_deselect_flush(bm); - } - } - else if (selectmode_old == SCE_SELECT_FACE) { - if (bm->totfacesel == 0) { - /* pass */ - } - else if (selectmode_new == SCE_SELECT_EDGE) { - /* flush down (face -> edge) */ - - BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { - if (!BM_edge_is_all_face_flag_test(eed, BM_ELEM_SELECT, true)) { - BM_edge_select_set(bm, eed, false); - } - } - /* Deselect faces without edges selected. */ - BM_mesh_deselect_flush(bm); - } - else if (selectmode_new == SCE_SELECT_VERTEX) { - /* flush down (face -> vert) */ - - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - if (!BM_vert_is_all_face_flag_test(eve, BM_ELEM_SELECT, true)) { - BM_vert_select_set(bm, eve, false); - } - } - /* deselect faces without verts selected */ - BM_mesh_deselect_flush(bm); - } - } -} - -bool EDBM_selectmode_toggle_multi(bContext *C, - const short selectmode_new, - const int action, - const bool use_extend, - const bool use_expand) -{ - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - ToolSettings *ts = CTX_data_tool_settings(C); - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = NULL; - bool ret = false; - - if (obedit && obedit->type == OB_MESH) { - em = BKE_editmesh_from_object(obedit); - } - - if (em == NULL) { - return ret; - } - - bool only_update = false; - switch (action) { - case -1: - /* already set */ - break; - case 0: /* disable */ - /* check we have something to do */ - if ((em->selectmode & selectmode_new) == 0) { - only_update = true; - break; - } - em->selectmode &= ~selectmode_new; - break; - case 1: /* enable */ - /* check we have something to do */ - if ((em->selectmode & selectmode_new) != 0) { - only_update = true; - break; - } - em->selectmode |= selectmode_new; - break; - case 2: /* toggle */ - /* can't disable this flag if its the only one set */ - if (em->selectmode == selectmode_new) { - only_update = true; - break; - } - em->selectmode ^= selectmode_new; - break; - default: - BLI_assert(0); - break; - } - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &objects_len); - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob_iter = objects[ob_index]; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - if (em_iter != em) { - em_iter->selectmode = em->selectmode; - } - } - - if (only_update) { - MEM_freeN(objects); - return false; - } - - if (use_extend == 0 || em->selectmode == 0) { - if (use_expand) { - const short selmode_max = highest_order_bit_s(ts->selectmode); - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob_iter = objects[ob_index]; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - EDBM_selectmode_convert(em_iter, selmode_max, selectmode_new); - } - } - } - - switch (selectmode_new) { - case SCE_SELECT_VERTEX: - if (use_extend == 0 || em->selectmode == 0) { - em->selectmode = SCE_SELECT_VERTEX; - } - ret = true; - break; - case SCE_SELECT_EDGE: - if (use_extend == 0 || em->selectmode == 0) { - em->selectmode = SCE_SELECT_EDGE; - } - ret = true; - break; - case SCE_SELECT_FACE: - if (use_extend == 0 || em->selectmode == 0) { - em->selectmode = SCE_SELECT_FACE; - } - ret = true; - break; - default: - BLI_assert(0); - break; - } - - if (ret == true) { - ts->selectmode = em->selectmode; - em = NULL; - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob_iter = objects[ob_index]; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - em_iter->selectmode = ts->selectmode; - EDBM_selectmode_set(em_iter); - DEG_id_tag_update(ob_iter->data, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); - } - WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); - DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); - } - - MEM_freeN(objects); - return ret; -} - -bool EDBM_selectmode_set_multi(bContext *C, const short selectmode) -{ - BLI_assert(selectmode != 0); - bool changed = false; - - { - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = NULL; - if (obedit && obedit->type == OB_MESH) { - em = BKE_editmesh_from_object(obedit); - } - if (em == NULL) { - return changed; - } - } - - ViewLayer *view_layer = CTX_data_view_layer(C); - Scene *scene = CTX_data_scene(C); - ToolSettings *ts = scene->toolsettings; - - if (ts->selectmode != selectmode) { - ts->selectmode = selectmode; - changed = true; - } - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &objects_len); - - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *ob_iter = objects[ob_index]; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - if (em_iter->selectmode != ts->selectmode) { - em_iter->selectmode = ts->selectmode; - EDBM_selectmode_set(em_iter); - DEG_id_tag_update(ob_iter->data, ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); - changed = true; - } - } - MEM_freeN(objects); - - if (changed) { - WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, NULL); - DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); - } - return changed; -} - -bool EDBM_selectmode_disable(Scene *scene, - BMEditMesh *em, - const short selectmode_disable, - const short selectmode_fallback) -{ - /* note essential, but switch out of vertex mode since the - * selected regions won't be nicely isolated after flushing */ - if (em->selectmode & selectmode_disable) { - if (em->selectmode == selectmode_disable) { - em->selectmode = selectmode_fallback; - } - else { - em->selectmode &= ~selectmode_disable; - } - scene->toolsettings->selectmode = em->selectmode; - EDBM_selectmode_set(em); - - WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, scene); - - return true; - } - return false; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Toggle - * \{ */ - -bool EDBM_deselect_by_material(BMEditMesh *em, const short index, const bool select) -{ - BMIter iter; - BMFace *efa; - bool changed = false; - - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { - continue; - } - if (efa->mat_nr == index) { - changed = true; - BM_face_select_set(em->bm, efa, select); - } - } - return changed; -} - -void EDBM_select_toggle_all(BMEditMesh *em) /* exported for UV */ -{ - if (em->bm->totvertsel || em->bm->totedgesel || em->bm->totfacesel) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - else { - EDBM_flag_enable_all(em, BM_ELEM_SELECT); - } -} - -void EDBM_select_swap(BMEditMesh *em) /* exported for UV */ -{ - BMIter iter; - BMVert *eve; - BMEdge *eed; - BMFace *efa; - - if (em->bm->selectmode & SCE_SELECT_VERTEX) { - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - continue; - } - BM_vert_select_set(em->bm, eve, !BM_elem_flag_test(eve, BM_ELEM_SELECT)); - } - } - else if (em->selectmode & SCE_SELECT_EDGE) { - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { - continue; - } - BM_edge_select_set(em->bm, eed, !BM_elem_flag_test(eed, BM_ELEM_SELECT)); - } - } - else { - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { - continue; - } - BM_face_select_set(em->bm, efa, !BM_elem_flag_test(efa, BM_ELEM_SELECT)); - } - } -} - -bool EDBM_mesh_deselect_all_multi_ex(struct Base **bases, const uint bases_len) -{ - bool changed_multi = false; - for (uint base_index = 0; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - Object *ob_iter = base_iter->object; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - - if (em_iter->bm->totvertsel == 0) { - continue; - } - - EDBM_flag_disable_all(em_iter, BM_ELEM_SELECT); - DEG_id_tag_update(ob_iter->data, ID_RECALC_SELECT); - changed_multi = true; - } - return changed_multi; -} - -bool EDBM_mesh_deselect_all_multi(struct bContext *C) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ViewContext vc; - ED_view3d_viewcontext_init(C, &vc, depsgraph); - uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.scene, vc.view_layer, vc.v3d, &bases_len); - bool changed_multi = EDBM_mesh_deselect_all_multi_ex(bases, bases_len); - MEM_freeN(bases); - return changed_multi; -} - -bool EDBM_selectmode_disable_multi_ex(Scene *scene, - struct Base **bases, - const uint bases_len, - const short selectmode_disable, - const short selectmode_fallback) -{ - bool changed_multi = false; - for (uint base_index = 0; base_index < bases_len; base_index++) { - Base *base_iter = bases[base_index]; - Object *ob_iter = base_iter->object; - BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); - - if (EDBM_selectmode_disable(scene, em_iter, selectmode_disable, selectmode_fallback)) { - changed_multi = true; - } - } - return changed_multi; -} - -bool EDBM_selectmode_disable_multi(struct bContext *C, - const short selectmode_disable, - const short selectmode_fallback) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - Scene *scene = CTX_data_scene(C); - ViewContext vc; - ED_view3d_viewcontext_init(C, &vc, depsgraph); - uint bases_len = 0; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( - vc.scene, vc.view_layer, NULL, &bases_len); - bool changed_multi = EDBM_selectmode_disable_multi_ex( - scene, bases, bases_len, selectmode_disable, selectmode_fallback); - MEM_freeN(bases); - return changed_multi; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Interior Faces - * - * Overview of the algorithm: - * - Groups faces surrounded by edges with 3+ faces using them. - * - Calculates a cost of each face group comparing its angle with the faces - * connected to its non-manifold edges. - * - Mark the face group as interior, and mark connected face groups for recalculation. - * - Continue to remove the face groups with the highest 'cost'. - * - * \{ */ - -struct BMFaceLink { - struct BMFaceLink *next, *prev; - BMFace *face; - float area; -}; - -static bool bm_interior_loop_filter_fn(const BMLoop *l, void *UNUSED(user_data)) -{ - if (BM_elem_flag_test(l->e, BM_ELEM_TAG)) { - return false; - } - return true; -} -static bool bm_interior_edge_is_manifold_except_face_index(BMEdge *e, - int face_index, - BMLoop *r_l_pair[2]) -{ - - BMLoop *l_iter = e->l; - int loop_index = 0; - do { - BMFace *f = l_iter->f; - int i = BM_elem_index_get(f); - if (!ELEM(i, -1, face_index)) { - if (loop_index == 2) { - return false; - } - r_l_pair[loop_index++] = l_iter; - } - } while ((l_iter = l_iter->radial_next) != e->l); - return (loop_index == 2); -} - -/** - * Calculate the cost of the face group. - * A higher value means it's more likely to remove first. - */ -static float bm_interior_face_group_calc_cost(ListBase *ls, const float *edge_lengths) -{ - /* Dividing by the area is important so larger face groups (which will become the outer shell) - * aren't detected as having a high cost. */ - float area = 0.0f; - float cost = 0.0f; - bool found = false; - LISTBASE_FOREACH (struct BMFaceLink *, f_link, ls) { - BMFace *f = f_link->face; - area += f_link->area; - int i = BM_elem_index_get(f); - BLI_assert(i != -1); - BMLoop *l_iter, *l_first; - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - if (BM_elem_flag_test(l_iter->e, BM_ELEM_TAG)) { - float cost_test = 0.0f; - int cost_count = 0; - /* All other faces. */ - BMLoop *l_radial_iter = l_iter; - do { - int i_other = BM_elem_index_get(l_radial_iter->f); - if (!ELEM(i_other, -1, i)) { - float angle = angle_normalized_v3v3(f->no, l_radial_iter->f->no); - /* Ignore face direction since in the case on non-manifold faces connecting edges, - * the face flipping may not be meaningful. */ - if (angle > DEG2RADF(90)) { - angle = DEG2RADF(180) - angle; - } - /* Avoid calculating it inline, pass in pre-calculated edge lengths. */ -#if 0 - cost_test += BM_edge_calc_length(l_iter->e) * angle; -#else - BLI_assert(edge_lengths[BM_elem_index_get(l_iter->e)] != -1.0f); - cost_test += edge_lengths[BM_elem_index_get(l_iter->e)] * angle; -#endif - cost_count += 1; - } - } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); - - if (cost_count >= 2) { - cost += cost_test; - found = true; - } - } - } while ((l_iter = l_iter->next) != l_first); - } - return found ? cost / area : FLT_MAX; -} - -bool EDBM_select_interior_faces(BMEditMesh *em) -{ - BMesh *bm = em->bm; - BMIter iter; - bool changed = false; - - float *edge_lengths = MEM_mallocN(sizeof(*edge_lengths) * bm->totedge, __func__); - - { - bool has_nonmanifold = false; - BMEdge *e; - int i; - BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) { - const bool is_over = BM_edge_face_count_is_over(e, 2); - if (is_over) { - BM_elem_flag_enable(e, BM_ELEM_TAG); - has_nonmanifold = true; - edge_lengths[i] = BM_edge_calc_length(e); - } - else { - BM_elem_flag_disable(e, BM_ELEM_TAG); - edge_lengths[i] = -1.0; - } - - BM_elem_index_set(e, i); /* set_inline */ - } - bm->elem_index_dirty &= ~BM_EDGE; - - if (has_nonmanifold == false) { - MEM_freeN(edge_lengths); - return false; - } - } - - /* group vars */ - int *fgroup_array; - int(*fgroup_index)[2]; - int fgroup_len; - - fgroup_array = MEM_mallocN(sizeof(*fgroup_array) * bm->totface, __func__); - fgroup_len = BM_mesh_calc_face_groups( - bm, fgroup_array, &fgroup_index, bm_interior_loop_filter_fn, NULL, NULL, 0, BM_EDGE); - - int *fgroup_recalc_stack = MEM_mallocN(sizeof(*fgroup_recalc_stack) * fgroup_len, __func__); - STACK_DECLARE(fgroup_recalc_stack); - STACK_INIT(fgroup_recalc_stack, fgroup_len); - - BM_mesh_elem_table_ensure(bm, BM_FACE); - - { - BMFace *f; - BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { - BM_elem_index_set(f, -1); /* set_dirty! */ - } - } - bm->elem_index_dirty |= BM_FACE; - - ListBase *fgroup_listbase = MEM_callocN(sizeof(*fgroup_listbase) * fgroup_len, __func__); - struct BMFaceLink *f_link_array = MEM_callocN(sizeof(*f_link_array) * bm->totface, __func__); - - for (int i = 0; i < fgroup_len; i++) { - const int fg_sta = fgroup_index[i][0]; - const int fg_len = fgroup_index[i][1]; - for (int j = 0; j < fg_len; j++) { - const int face_index = fgroup_array[fg_sta + j]; - BMFace *f = BM_face_at_index(bm, face_index); - BM_elem_index_set(f, i); - - struct BMFaceLink *f_link = &f_link_array[face_index]; - f_link->face = f; - f_link->area = BM_face_calc_area(f); - BLI_addtail(&fgroup_listbase[i], f_link); - } - } - - MEM_freeN(fgroup_array); - MEM_freeN(fgroup_index); - - Heap *fgroup_heap = BLI_heap_new_ex(fgroup_len); - HeapNode **fgroup_table = MEM_mallocN(sizeof(*fgroup_table) * fgroup_len, __func__); - bool *fgroup_dirty = MEM_callocN(sizeof(*fgroup_dirty) * fgroup_len, __func__); - - for (int i = 0; i < fgroup_len; i++) { - const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); - if (cost != FLT_MAX) { - fgroup_table[i] = BLI_heap_insert(fgroup_heap, -cost, POINTER_FROM_INT(i)); - } - else { - fgroup_table[i] = NULL; - } - } - - /* Avoid re-running cost calculations for large face-groups which will end up forming the - * outer shell and not be considered interior. - * As these face groups become increasingly bigger - their chance of being considered - * interior reduces as does the time to calculate their cost. - * - * This delays recalculating them until they are considered can dates to remove - * which becomes less and less likely as they increase in area. */ - -#define USE_DELAY_FACE_GROUP_COST_CALC - - while (true) { - -#if defined(USE_DELAY_FACE_GROUP_COST_CALC) - while (!BLI_heap_is_empty(fgroup_heap)) { - HeapNode *node_min = BLI_heap_top(fgroup_heap); - const int i = POINTER_AS_INT(BLI_heap_node_ptr(node_min)); - if (fgroup_dirty[i]) { - const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); - if (cost != FLT_MAX) { - /* The cost may have improves (we may be able to skip this), - * however the cost should _never_ make this a choice. */ - BLI_assert(-BLI_heap_node_value(node_min) >= cost); - BLI_heap_node_value_update(fgroup_heap, fgroup_table[i], -cost); - } - else { - BLI_heap_remove(fgroup_heap, fgroup_table[i]); - fgroup_table[i] = NULL; - } - fgroup_dirty[i] = false; - } - else { - break; - } - } -#endif - - if (BLI_heap_is_empty(fgroup_heap)) { - break; - } - - const int i_min = POINTER_AS_INT(BLI_heap_pop_min(fgroup_heap)); - BLI_assert(fgroup_table[i_min] != NULL); - BLI_assert(fgroup_dirty[i_min] == false); - fgroup_table[i_min] = NULL; - changed = true; - - struct BMFaceLink *f_link; - while ((f_link = BLI_pophead(&fgroup_listbase[i_min]))) { - BMFace *f = f_link->face; - BM_face_select_set(bm, f, true); - BM_elem_index_set(f, -1); /* set-dirty */ - - BMLoop *l_iter, *l_first; - - /* Loop over edges face edges, merging groups which are no longer separated - * by non-manifold edges (when manifold check ignores faces from this group). */ - l_iter = l_first = BM_FACE_FIRST_LOOP(f); - do { - BMLoop *l_pair[2]; - if (bm_interior_edge_is_manifold_except_face_index(l_iter->e, i_min, l_pair)) { - BM_elem_flag_disable(l_iter->e, BM_ELEM_TAG); - - int i_a = BM_elem_index_get(l_pair[0]->f); - int i_b = BM_elem_index_get(l_pair[1]->f); - if (i_a != i_b) { - /* Only for predictable results that don't depend on the order of radial loops, - * not essential. */ - if (i_a > i_b) { - SWAP(int, i_a, i_b); - } - - /* Merge the groups. */ - LISTBASE_FOREACH (LinkData *, n, &fgroup_listbase[i_b]) { - BMFace *f_iter = n->data; - BM_elem_index_set(f_iter, i_a); - } - BLI_movelisttolist(&fgroup_listbase[i_a], &fgroup_listbase[i_b]); - - /* This may have been added to 'fgroup_recalc_stack', instead of removing it, - * just check the heap node isn't NULL before recalculating. */ - BLI_heap_remove(fgroup_heap, fgroup_table[i_b]); - fgroup_table[i_b] = NULL; - /* Keep the dirty flag as-is for 'i_b', because it may be in the 'fgroup_recalc_stack' - * and we don't want to add it again. - * Instead rely on the 'fgroup_table[i_b]' being NULL as a secondary check. */ - - if (fgroup_dirty[i_a] == false) { - BLI_assert(fgroup_table[i_a] != NULL); - STACK_PUSH(fgroup_recalc_stack, i_a); - fgroup_dirty[i_a] = true; - } - } - } - - /* Mark all connected groups for re-calculation. */ - BMLoop *l_radial_iter = l_iter->radial_next; - if (l_radial_iter != l_iter) { - do { - int i_other = BM_elem_index_get(l_radial_iter->f); - if (!ELEM(i_other, -1, i_min)) { - if ((fgroup_table[i_other] != NULL) && (fgroup_dirty[i_other] == false)) { -#if !defined(USE_DELAY_FACE_GROUP_COST_CALC) - STACK_PUSH(fgroup_recalc_stack, i_other); -#endif - fgroup_dirty[i_other] = true; - } - } - } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); - } - - } while ((l_iter = l_iter->next) != l_first); - } - - for (int index = 0; index < STACK_SIZE(fgroup_recalc_stack); index++) { - const int i = fgroup_recalc_stack[index]; - if (fgroup_table[i] != NULL && fgroup_dirty[i] == true) { - /* First update edge tags. */ - const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); - if (cost != FLT_MAX) { - BLI_heap_node_value_update(fgroup_heap, fgroup_table[i], -cost); - } - else { - BLI_heap_remove(fgroup_heap, fgroup_table[i]); - fgroup_table[i] = NULL; - } - } - fgroup_dirty[i] = false; - } - STACK_CLEAR(fgroup_recalc_stack); - } - - MEM_freeN(edge_lengths); - MEM_freeN(f_link_array); - MEM_freeN(fgroup_listbase); - MEM_freeN(fgroup_recalc_stack); - MEM_freeN(fgroup_table); - MEM_freeN(fgroup_dirty); - - BLI_heap_free(fgroup_heap, NULL); - - return changed; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Linked Operator - * - * Support delimiting on different edge properties. - * \{ */ - -/* so we can have last-used default depend on selection mode (rare exception!) */ -#define USE_LINKED_SELECT_DEFAULT_HACK - -struct DelimitData { - int cd_loop_type; - int cd_loop_offset; -}; - -static bool select_linked_delimit_test(BMEdge *e, - int delimit, - const struct DelimitData *delimit_data) -{ - BLI_assert(delimit); - - if (delimit & BMO_DELIM_SEAM) { - if (BM_elem_flag_test(e, BM_ELEM_SEAM)) { - return true; - } - } - - if (delimit & BMO_DELIM_SHARP) { - if (BM_elem_flag_test(e, BM_ELEM_SMOOTH) == 0) { - return true; - } - } - - if (delimit & BMO_DELIM_NORMAL) { - if (!BM_edge_is_contiguous(e)) { - return true; - } - } - - if (delimit & BMO_DELIM_MATERIAL) { - if (e->l && e->l->radial_next != e->l) { - const short mat_nr = e->l->f->mat_nr; - BMLoop *l_iter = e->l->radial_next; - do { - if (l_iter->f->mat_nr != mat_nr) { - return true; - } - } while ((l_iter = l_iter->radial_next) != e->l); - } - } - - if (delimit & BMO_DELIM_UV) { - if (BM_edge_is_contiguous_loop_cd( - e, delimit_data->cd_loop_type, delimit_data->cd_loop_offset) == 0) { - return true; - } - } - - return false; -} - -#ifdef USE_LINKED_SELECT_DEFAULT_HACK -/** - * Gets the default from the operator fallback to own last-used value - * (selected based on mode) - */ -static int select_linked_delimit_default_from_op(wmOperator *op, const int select_mode) -{ - static char delimit_last_store[2] = {0, BMO_DELIM_SEAM}; - int delimit_last_index = (select_mode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0; - char *delimit_last = &delimit_last_store[delimit_last_index]; - PropertyRNA *prop_delimit = RNA_struct_find_property(op->ptr, "delimit"); - int delimit; - - if (RNA_property_is_set(op->ptr, prop_delimit)) { - delimit = RNA_property_enum_get(op->ptr, prop_delimit); - *delimit_last = delimit; - } - else { - delimit = *delimit_last; - RNA_property_enum_set(op->ptr, prop_delimit, delimit); - } - return delimit; -} -#endif - -static void select_linked_delimit_validate(BMesh *bm, int *delimit) -{ - if ((*delimit) & BMO_DELIM_UV) { - if (!CustomData_has_layer(&bm->ldata, CD_MLOOPUV)) { - (*delimit) &= ~BMO_DELIM_UV; - } - } -} - -static void select_linked_delimit_begin(BMesh *bm, int delimit) -{ - struct DelimitData delimit_data = {0}; - - if (delimit & BMO_DELIM_UV) { - delimit_data.cd_loop_type = CD_MLOOPUV; - delimit_data.cd_loop_offset = CustomData_get_offset(&bm->ldata, delimit_data.cd_loop_type); - if (delimit_data.cd_loop_offset == -1) { - delimit &= ~BMO_DELIM_UV; - } - } - - /* grr, shouldn't need to alloc BMO flags here */ - BM_mesh_elem_toolflags_ensure(bm); - - { - BMIter iter; - BMEdge *e; - - BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { - const bool is_walk_ok = (select_linked_delimit_test(e, delimit, &delimit_data) == false); - - BMO_edge_flag_set(bm, e, BMO_ELE_TAG, is_walk_ok); - } - } -} - -static void select_linked_delimit_end(BMEditMesh *em) -{ - BMesh *bm = em->bm; - - BM_mesh_elem_toolflags_clear(bm); -} - -static int edbm_select_linked_exec(bContext *C, wmOperator *op) -{ - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - -#ifdef USE_LINKED_SELECT_DEFAULT_HACK - const int delimit_init = select_linked_delimit_default_from_op(op, - scene->toolsettings->selectmode); -#else - const int delimit_init = RNA_enum_get(op->ptr, "delimit"); -#endif - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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; - BMIter iter; - BMWalker walker; - - int delimit = delimit_init; - - select_linked_delimit_validate(bm, &delimit); - - if (delimit) { - select_linked_delimit_begin(em->bm, delimit); - } - - if (em->selectmode & SCE_SELECT_VERTEX) { - BMVert *v; - - BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { - BM_elem_flag_set(v, BM_ELEM_TAG, BM_elem_flag_test(v, BM_ELEM_SELECT)); - } - - /* exclude all delimited verts */ - if (delimit) { - BMEdge *e; - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (!BMO_edge_flag_test(bm, e, BMO_ELE_TAG)) { - /* Check the edge for selected faces, - * this supports stepping off isolated vertices which would otherwise be ignored. */ - if (BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)) { - BM_elem_flag_disable(e->v1, BM_ELEM_TAG); - BM_elem_flag_disable(e->v2, BM_ELEM_TAG); - } - } - } - } - - BMW_init(&walker, - em->bm, - delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, - BMW_MASK_NOP, - delimit ? BMO_ELE_TAG : BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - if (delimit) { - BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(v, BM_ELEM_TAG)) { - BMElem *ele_walk; - BMW_ITER (ele_walk, &walker, v) { - if (ele_walk->head.htype == BM_LOOP) { - BMVert *v_step = ((BMLoop *)ele_walk)->v; - BM_vert_select_set(em->bm, v_step, true); - BM_elem_flag_disable(v_step, BM_ELEM_TAG); - } - else { - BMEdge *e_step = (BMEdge *)ele_walk; - BLI_assert(ele_walk->head.htype == BM_EDGE); - BM_edge_select_set(em->bm, e_step, true); - BM_elem_flag_disable(e_step->v1, BM_ELEM_TAG); - BM_elem_flag_disable(e_step->v2, BM_ELEM_TAG); - } - } - } - } - } - else { - BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(v, BM_ELEM_TAG)) { - BMEdge *e_walk; - BMW_ITER (e_walk, &walker, v) { - BM_edge_select_set(em->bm, e_walk, true); - BM_elem_flag_disable(e_walk, BM_ELEM_TAG); - } - } - } - } - - BMW_end(&walker); - - EDBM_selectmode_flush(em); - } - else if (em->selectmode & SCE_SELECT_EDGE) { - BMEdge *e; - - if (delimit) { - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - /* Check the edge for selected faces, - * this supports stepping off isolated edges which would otherwise be ignored. */ - BM_elem_flag_set(e, - BM_ELEM_TAG, - (BM_elem_flag_test(e, BM_ELEM_SELECT) && - (BMO_edge_flag_test(bm, e, BMO_ELE_TAG) || - !BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)))); - } - } - else { - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT)); - } - } - - BMW_init(&walker, - em->bm, - delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, - BMW_MASK_NOP, - delimit ? BMO_ELE_TAG : BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - if (delimit) { - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_TAG)) { - BMElem *ele_walk; - BMW_ITER (ele_walk, &walker, e) { - if (ele_walk->head.htype == BM_LOOP) { - BMLoop *l_step = (BMLoop *)ele_walk; - BM_edge_select_set(em->bm, l_step->e, true); - BM_edge_select_set(em->bm, l_step->prev->e, true); - BM_elem_flag_disable(l_step->e, BM_ELEM_TAG); - } - else { - BMEdge *e_step = (BMEdge *)ele_walk; - BLI_assert(ele_walk->head.htype == BM_EDGE); - BM_edge_select_set(em->bm, e_step, true); - BM_elem_flag_disable(e_step, BM_ELEM_TAG); - } - } - } - } - } - else { - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_TAG)) { - BMEdge *e_walk; - BMW_ITER (e_walk, &walker, e) { - BM_edge_select_set(em->bm, e_walk, true); - BM_elem_flag_disable(e_walk, BM_ELEM_TAG); - } - } - } - } - - BMW_end(&walker); - - EDBM_selectmode_flush(em); - } - else { - BMFace *f; - - BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { - BM_elem_flag_set(f, BM_ELEM_TAG, BM_elem_flag_test(f, BM_ELEM_SELECT)); - } - - BMW_init(&walker, - bm, - BMW_ISLAND, - BMW_MASK_NOP, - delimit ? BMO_ELE_TAG : BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_TAG)) { - BMFace *f_walk; - BMW_ITER (f_walk, &walker, f) { - BM_face_select_set(bm, f_walk, true); - BM_elem_flag_disable(f_walk, BM_ELEM_TAG); - } - } - } - - BMW_end(&walker); - } - - if (delimit) { - select_linked_delimit_end(em); - } - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_linked(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select Linked All"; - ot->idname = "MESH_OT_select_linked"; - ot->description = "Select all vertices connected to the current selection"; - - /* api callbacks */ - ot->exec = edbm_select_linked_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - prop = RNA_def_enum_flag(ot->srna, - "delimit", - rna_enum_mesh_delimit_mode_items, - BMO_DELIM_SEAM, - "Delimit", - "Delimit selected region"); -#ifdef USE_LINKED_SELECT_DEFAULT_HACK - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -#else - UNUSED_VARS(prop); -#endif -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Linked (Cursor Pick) Operator - * \{ */ - -static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op); - -static void edbm_select_linked_pick_ex(BMEditMesh *em, BMElem *ele, bool sel, int delimit) -{ - BMesh *bm = em->bm; - BMWalker walker; - - select_linked_delimit_validate(bm, &delimit); - - if (delimit) { - select_linked_delimit_begin(bm, delimit); - } - - /* NOTE: logic closely matches #edbm_select_linked_exec, keep in sync. */ - - if (ele->head.htype == BM_VERT) { - BMVert *eve = (BMVert *)ele; - - BMW_init(&walker, - bm, - delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, - BMW_MASK_NOP, - delimit ? BMO_ELE_TAG : BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - if (delimit) { - BMElem *ele_walk; - BMW_ITER (ele_walk, &walker, eve) { - if (ele_walk->head.htype == BM_LOOP) { - BMVert *v_step = ((BMLoop *)ele_walk)->v; - BM_vert_select_set(bm, v_step, sel); - } - else { - BMEdge *e_step = (BMEdge *)ele_walk; - BLI_assert(ele_walk->head.htype == BM_EDGE); - BM_edge_select_set(bm, e_step, sel); - } - } - } - else { - BMEdge *e_walk; - BMW_ITER (e_walk, &walker, eve) { - BM_edge_select_set(bm, e_walk, sel); - } - } - - BMW_end(&walker); - - EDBM_selectmode_flush(em); - } - else if (ele->head.htype == BM_EDGE) { - BMEdge *eed = (BMEdge *)ele; - - BMW_init(&walker, - bm, - delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, - BMW_MASK_NOP, - delimit ? BMO_ELE_TAG : BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - if (delimit) { - BMElem *ele_walk; - BMW_ITER (ele_walk, &walker, eed) { - if (ele_walk->head.htype == BM_LOOP) { - BMEdge *e_step = ((BMLoop *)ele_walk)->e; - BM_edge_select_set(bm, e_step, sel); - } - else { - BMEdge *e_step = (BMEdge *)ele_walk; - BLI_assert(ele_walk->head.htype == BM_EDGE); - BM_edge_select_set(bm, e_step, sel); - } - } - } - else { - BMEdge *e_walk; - BMW_ITER (e_walk, &walker, eed) { - BM_edge_select_set(bm, e_walk, sel); - } - } - - BMW_end(&walker); - - EDBM_selectmode_flush(em); - } - else if (ele->head.htype == BM_FACE) { - BMFace *efa = (BMFace *)ele; - - BMW_init(&walker, - bm, - BMW_ISLAND, - BMW_MASK_NOP, - delimit ? BMO_ELE_TAG : BMW_MASK_NOP, - BMW_MASK_NOP, - BMW_FLAG_TEST_HIDDEN, - BMW_NIL_LAY); - - { - BMFace *f_walk; - BMW_ITER (f_walk, &walker, efa) { - BM_face_select_set(bm, f_walk, sel); - BM_elem_flag_disable(f_walk, BM_ELEM_TAG); - } - } - - BMW_end(&walker); - } - - if (delimit) { - select_linked_delimit_end(em); - } -} - -static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) -{ - ViewContext vc; - Base *basact = NULL; - BMVert *eve; - BMEdge *eed; - BMFace *efa; - const bool sel = !RNA_boolean_get(op->ptr, "deselect"); - int index; - - if (RNA_struct_property_is_set(op->ptr, "index")) { - return edbm_select_linked_pick_exec(C, op); - } - - /* #unified_findnearest needs OpenGL. */ - view3d_operator_needs_opengl(C); - - /* setup view context for argument to callbacks */ - em_setup_viewcontext(C, &vc); - - uint bases_len; - Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( - vc.scene, vc.view_layer, vc.v3d, &bases_len); - - { - bool has_edges = false; - for (uint base_index = 0; base_index < bases_len; base_index++) { - Object *ob_iter = bases[base_index]->object; - ED_view3d_viewcontext_init_object(&vc, ob_iter); - if (vc.em->bm->totedge) { - has_edges = true; - } - } - if (has_edges == false) { - MEM_freeN(bases); - return OPERATOR_CANCELLED; - } - } - - vc.mval[0] = event->mval[0]; - vc.mval[1] = event->mval[1]; - - /* return warning! */ - { - int base_index = -1; - const bool ok = unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa); - if (!ok) { - MEM_freeN(bases); - return OPERATOR_CANCELLED; - } - basact = bases[base_index]; - } - - ED_view3d_viewcontext_init_object(&vc, basact->object); - BMEditMesh *em = vc.em; - BMesh *bm = em->bm; - -#ifdef USE_LINKED_SELECT_DEFAULT_HACK - int delimit = select_linked_delimit_default_from_op(op, vc.scene->toolsettings->selectmode); -#else - int delimit = RNA_enum_get(op->ptr, "delimit"); -#endif - - BMElem *ele = EDBM_elem_from_selectmode(em, eve, eed, efa); - - edbm_select_linked_pick_ex(em, ele, sel, delimit); - - /* To support redo. */ - { - /* Note that the `base_index` can't be used as the index depends on the 3D Viewport - * which might not be available on redo. */ - BM_mesh_elem_index_ensure(bm, ele->head.htype); - int object_index; - index = EDBM_elem_to_index_any_multi(vc.scene, vc.view_layer, em, ele, &object_index); - BLI_assert(object_index >= 0); - RNA_int_set(op->ptr, "object_index", object_index); - RNA_int_set(op->ptr, "index", index); - } - - DEG_id_tag_update(basact->object->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, basact->object->data); - - MEM_freeN(bases); - return OPERATOR_FINISHED; -} - -static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op) -{ - Object *obedit = NULL; - BMElem *ele; - - { - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - /* Intentionally wrap negative values so the lookup fails. */ - const uint object_index = (uint)RNA_int_get(op->ptr, "object_index"); - const uint index = (uint)RNA_int_get(op->ptr, "index"); - ele = EDBM_elem_from_index_any_multi(scene, view_layer, object_index, index, &obedit); - } - - if (ele == NULL) { - return OPERATOR_CANCELLED; - } - - BMEditMesh *em = BKE_editmesh_from_object(obedit); - const bool sel = !RNA_boolean_get(op->ptr, "deselect"); - -#ifdef USE_LINKED_SELECT_DEFAULT_HACK - int delimit = select_linked_delimit_default_from_op(op, em->selectmode); -#else - int delimit = RNA_enum_get(op->ptr, "delimit"); -#endif - - edbm_select_linked_pick_ex(em, ele, sel, delimit); - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_linked_pick(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select Linked"; - ot->idname = "MESH_OT_select_linked_pick"; - ot->description = "(De)select all vertices linked to the edge under the mouse cursor"; - - /* api callbacks */ - ot->invoke = edbm_select_linked_pick_invoke; - ot->exec = edbm_select_linked_pick_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", ""); - prop = RNA_def_enum_flag(ot->srna, - "delimit", - rna_enum_mesh_delimit_mode_items, - BMO_DELIM_SEAM, - "Delimit", - "Delimit selected region"); -#ifdef USE_LINKED_SELECT_DEFAULT_HACK - RNA_def_property_flag(prop, PROP_SKIP_SAVE); -#endif - - /* use for redo */ - prop = RNA_def_int(ot->srna, "object_index", -1, -1, INT_MAX, "", "", 0, INT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); - prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX); - RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Face by Sides Operator - * \{ */ - -static int edbm_select_face_by_sides_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - uint objects_len = 0; - const bool extend = RNA_boolean_get(op->ptr, "extend"); - const int numverts = RNA_int_get(op->ptr, "number"); - const int type = RNA_enum_get(op->ptr, "type"); - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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); - BMFace *efa; - BMIter iter; - - if (!extend) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - bool select; - - switch (type) { - case 0: - select = (efa->len < numverts); - break; - case 1: - select = (efa->len == numverts); - break; - case 2: - select = (efa->len > numverts); - break; - case 3: - select = (efa->len != numverts); - break; - default: - BLI_assert(0); - select = false; - break; - } - - if (select) { - BM_face_select_set(em->bm, efa, true); - } - } - - EDBM_selectmode_flush(em); - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - return OPERATOR_FINISHED; -} - -void MESH_OT_select_face_by_sides(wmOperatorType *ot) -{ - static const EnumPropertyItem type_items[] = { - {0, "LESS", 0, "Less Than", ""}, - {1, "EQUAL", 0, "Equal To", ""}, - {2, "GREATER", 0, "Greater Than", ""}, - {3, "NOTEQUAL", 0, "Not Equal To", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - /* identifiers */ - ot->name = "Select Faces by Sides"; - ot->description = "Select vertices or faces by the number of polygon sides"; - ot->idname = "MESH_OT_select_face_by_sides"; - - /* api callbacks */ - ot->exec = edbm_select_face_by_sides_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - RNA_def_int(ot->srna, "number", 4, 3, INT_MAX, "Number of Vertices", "", 3, INT_MAX); - RNA_def_enum(ot->srna, "type", type_items, 1, "Type", "Type of comparison to make"); - RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend the selection"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Loose Operator - * \{ */ - -static int edbm_select_loose_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - const bool extend = RNA_boolean_get(op->ptr, "extend"); - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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; - BMIter iter; - - if (!extend) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - if (em->selectmode & SCE_SELECT_VERTEX) { - BMVert *eve; - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - if (!eve->e) { - BM_vert_select_set(bm, eve, true); - } - } - } - - if (em->selectmode & SCE_SELECT_EDGE) { - BMEdge *eed; - BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { - if (BM_edge_is_wire(eed)) { - BM_edge_select_set(bm, eed, true); - } - } - } - - if (em->selectmode & SCE_SELECT_FACE) { - BMFace *efa; - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - BMIter liter; - BMLoop *l; - bool is_loose = true; - BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { - if (!BM_edge_is_boundary(l->e)) { - is_loose = false; - break; - } - } - if (is_loose) { - BM_face_select_set(bm, efa, true); - } - } - } - - EDBM_selectmode_flush(em); - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_loose(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Loose Geometry"; - ot->description = "Select loose geometry based on the selection mode"; - ot->idname = "MESH_OT_select_loose"; - - /* api callbacks */ - ot->exec = edbm_select_loose_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* props */ - RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Mirror Operator - * \{ */ - -static int edbm_select_mirror_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - const int axis_flag = RNA_enum_get(op->ptr, "axis"); - const bool extend = RNA_boolean_get(op->ptr, "extend"); - Object *obedit_active = CTX_data_edit_object(C); - BMEditMesh *em_active = BKE_editmesh_from_object(obedit_active); - const int select_mode = em_active->bm->selectmode; - int tot_mirr = 0, tot_fail = 0; - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) { - continue; - } - - int tot_mirr_iter = 0, tot_fail_iter = 0; - - for (int axis = 0; axis < 3; axis++) { - if ((1 << axis) & axis_flag) { - EDBM_select_mirrored(em, obedit->data, axis, extend, &tot_mirr_iter, &tot_fail_iter); - } - } - - if (tot_mirr_iter) { - EDBM_selectmode_flush(em); - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - tot_fail += tot_fail_iter; - tot_mirr += tot_mirr_iter; - } - MEM_freeN(objects); - - if (tot_mirr || tot_fail) { - ED_mesh_report_mirror_ex(op, tot_mirr, tot_fail, select_mode); - } - return OPERATOR_FINISHED; -} - -void MESH_OT_select_mirror(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Mirror"; - ot->description = "Select mesh items at mirrored locations"; - ot->idname = "MESH_OT_select_mirror"; - - /* api callbacks */ - ot->exec = edbm_select_mirror_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* props */ - RNA_def_enum_flag(ot->srna, "axis", rna_enum_axis_flag_xyz_items, (1 << 0), "Axis", ""); - - RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the existing selection"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select More Operator - * \{ */ - -static int edbm_select_more_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) && (bm->totedgesel == 0) && (bm->totfacesel == 0)) { - continue; - } - - EDBM_select_more(em, use_face_step); - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - return OPERATOR_FINISHED; -} - -void MESH_OT_select_more(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select More"; - ot->idname = "MESH_OT_select_more"; - ot->description = "Select more vertices, edges or faces connected to initial selection"; - - /* api callbacks */ - ot->exec = edbm_select_more_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_boolean( - ot->srna, "use_face_step", true, "Face Step", "Connected faces (instead of edges)"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select More Operator - * \{ */ - -static int edbm_select_less_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) && (bm->totedgesel == 0) && (bm->totfacesel == 0)) { - continue; - } - - EDBM_select_less(em, use_face_step); - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - return OPERATOR_FINISHED; -} - -void MESH_OT_select_less(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Less"; - ot->idname = "MESH_OT_select_less"; - ot->description = "Deselect vertices, edges or faces at the boundary of each selection region"; - - /* api callbacks */ - ot->exec = edbm_select_less_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_boolean( - ot->srna, "use_face_step", true, "Face Step", "Connected faces (instead of edges)"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select N'th Operator - * \{ */ - -/** - * Check if we're connected to another selected edge. - */ -static bool bm_edge_is_select_isolated(BMEdge *e) -{ - BMIter viter; - BMVert *v; - - BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) { - BMIter eiter; - BMEdge *e_other; - - BM_ITER_ELEM (e_other, &eiter, v, BM_EDGES_OF_VERT) { - if ((e_other != e) && BM_elem_flag_test(e_other, BM_ELEM_SELECT)) { - return false; - } - } - } - return true; -} - -/* Walk all reachable elements of the same type as h_act in breadth-first - * order, starting from h_act. Deselects elements if the depth when they - * are reached is not a multiple of "nth". */ -static void walker_deselect_nth(BMEditMesh *em, - const struct CheckerIntervalParams *op_params, - BMHeader *h_act) -{ - BMElem *ele; - BMesh *bm = em->bm; - BMWalker walker; - BMIter iter; - int walktype = 0, itertype = 0, flushtype = 0; - short mask_vert = 0, mask_edge = 0, mask_face = 0; - - /* No active element from which to start - nothing to do */ - if (h_act == NULL) { - return; - } - - /* Determine which type of iter, walker, and select flush to use - * based on type of the elements being deselected */ - switch (h_act->htype) { - case BM_VERT: - itertype = BM_VERTS_OF_MESH; - walktype = BMW_CONNECTED_VERTEX; - flushtype = SCE_SELECT_VERTEX; - mask_vert = BMO_ELE_TAG; - break; - case BM_EDGE: - /* When an edge has no connected-selected edges, - * use face-stepping (supports edge-rings) */ - itertype = BM_EDGES_OF_MESH; - walktype = bm_edge_is_select_isolated((BMEdge *)h_act) ? BMW_FACE_SHELL : BMW_VERT_SHELL; - flushtype = SCE_SELECT_EDGE; - mask_edge = BMO_ELE_TAG; - break; - case BM_FACE: - itertype = BM_FACES_OF_MESH; - walktype = BMW_ISLAND; - flushtype = SCE_SELECT_FACE; - mask_face = BMO_ELE_TAG; - break; - } - - /* grr, shouldn't need to alloc BMO flags here */ - BM_mesh_elem_toolflags_ensure(bm); - - /* Walker restrictions uses BMO flags, not header flags, - * so transfer BM_ELEM_SELECT from HFlags onto a BMO flag layer. */ - BMO_push(bm, NULL); - BM_ITER_MESH (ele, &iter, bm, itertype) { - if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) { - BMO_elem_flag_enable(bm, (BMElemF *)ele, BMO_ELE_TAG); - } - } - - /* Walk over selected elements starting at active */ - BMW_init(&walker, - bm, - walktype, - mask_vert, - mask_edge, - mask_face, - BMW_FLAG_NOP, /* don't use BMW_FLAG_TEST_HIDDEN here since we want to desel all */ - BMW_NIL_LAY); - - /* use tag to avoid touching the same verts twice */ - BM_ITER_MESH (ele, &iter, bm, itertype) { - BM_elem_flag_disable(ele, BM_ELEM_TAG); - } - - BLI_assert(walker.order == BMW_BREADTH_FIRST); - for (ele = BMW_begin(&walker, h_act); ele != NULL; ele = BMW_step(&walker)) { - if (!BM_elem_flag_test(ele, BM_ELEM_TAG)) { - /* Deselect elements that aren't at "nth" depth from active */ - const int depth = BMW_current_depth(&walker) - 1; - if (!WM_operator_properties_checker_interval_test(op_params, depth)) { - BM_elem_select_set(bm, ele, false); - } - BM_elem_flag_enable(ele, BM_ELEM_TAG); - } - } - BMW_end(&walker); - - BMO_pop(bm); - - /* Flush selection up */ - EDBM_selectmode_flush_ex(em, flushtype); -} - -static void deselect_nth_active(BMEditMesh *em, BMVert **r_eve, BMEdge **r_eed, BMFace **r_efa) -{ - BMIter iter; - BMElem *ele; - - *r_eve = NULL; - *r_eed = NULL; - *r_efa = NULL; - - EDBM_selectmode_flush(em); - ele = BM_mesh_active_elem_get(em->bm); - - if (ele && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { - switch (ele->head.htype) { - case BM_VERT: - *r_eve = (BMVert *)ele; - return; - case BM_EDGE: - *r_eed = (BMEdge *)ele; - return; - case BM_FACE: - *r_efa = (BMFace *)ele; - return; - } - } - - if (em->selectmode & SCE_SELECT_VERTEX) { - BMVert *v; - BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { - if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { - *r_eve = v; - return; - } - } - } - else if (em->selectmode & SCE_SELECT_EDGE) { - BMEdge *e; - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { - *r_eed = e; - return; - } - } - } - else if (em->selectmode & SCE_SELECT_FACE) { - BMFace *f = BM_mesh_active_face_get(em->bm, true, false); - if (f && BM_elem_flag_test(f, BM_ELEM_SELECT)) { - *r_efa = f; - return; - } - } -} - -static bool edbm_deselect_nth(BMEditMesh *em, const struct CheckerIntervalParams *op_params) -{ - BMVert *v; - BMEdge *e; - BMFace *f; - - deselect_nth_active(em, &v, &e, &f); - - if (v) { - walker_deselect_nth(em, op_params, &v->head); - return true; - } - if (e) { - walker_deselect_nth(em, op_params, &e->head); - return true; - } - if (f) { - walker_deselect_nth(em, op_params, &f->head); - return true; - } - - return false; -} - -static int edbm_select_nth_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - struct CheckerIntervalParams op_params; - WM_operator_properties_checker_interval_from_op(op, &op_params); - bool found_active_elt = false; - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( - scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totfacesel == 0)) { - continue; - } - - if (edbm_deselect_nth(em, &op_params) == true) { - found_active_elt = true; - EDBM_update(obedit->data, - &(const struct EDBMUpdate_Params){ - .calc_looptri = false, - .calc_normals = false, - .is_destructive = false, - }); - } - } - MEM_freeN(objects); - - if (!found_active_elt) { - BKE_report(op->reports, RPT_ERROR, "Mesh object(s) have no active vertex/edge/face"); - return OPERATOR_CANCELLED; - } - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_nth(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Checker Deselect"; - ot->idname = "MESH_OT_select_nth"; - ot->description = "Deselect every Nth element starting from the active vertex, edge or face"; - - /* api callbacks */ - ot->exec = edbm_select_nth_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - WM_operator_properties_checker_interval(ot, false); -} - -void em_setup_viewcontext(bContext *C, ViewContext *vc) -{ - Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); - ED_view3d_viewcontext_init(C, vc, depsgraph); - - if (vc->obedit) { - vc->em = BKE_editmesh_from_object(vc->obedit); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Sharp Edges Operator - * \{ */ - -static int edbm_select_sharp_edges_exec(bContext *C, wmOperator *op) -{ - /* Find edges that have exactly two neighboring faces, - * check the angle between those faces, and if angle is - * small enough, select the edge - */ - const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); - - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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); - BMIter iter; - BMEdge *e; - BMLoop *l1, *l2; - - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false && BM_edge_loop_pair(e, &l1, &l2)) { - /* edge has exactly two neighboring faces, check angle */ - const float angle_cos = dot_v3v3(l1->f->no, l2->f->no); - - if (angle_cos < angle_limit_cos) { - BM_edge_select_set(em->bm, e, true); - } - } - } - - if ((em->bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0) { - /* Since we can't select individual edges, select faces connected to them. */ - EDBM_selectmode_convert(em, SCE_SELECT_EDGE, SCE_SELECT_FACE); - } - else { - EDBM_selectmode_flush(em); - } - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_edges_select_sharp(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select Sharp Edges"; - ot->description = "Select all sharp enough edges"; - ot->idname = "MESH_OT_edges_select_sharp"; - - /* api callbacks */ - ot->exec = edbm_select_sharp_edges_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* props */ - prop = RNA_def_float_rotation(ot->srna, - "sharpness", - 0, - NULL, - DEG2RADF(0.01f), - DEG2RADF(180.0f), - "Sharpness", - "", - DEG2RADF(1.0f), - DEG2RADF(180.0f)); - RNA_def_property_float_default(prop, DEG2RADF(30.0f)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Linked Flat Faces Operator - * \{ */ - -static int edbm_select_linked_flat_faces_exec(bContext *C, wmOperator *op) -{ - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &objects_len); - const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); - - 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->totfacesel == 0) { - continue; - } - - BLI_LINKSTACK_DECLARE(stack, BMFace *); - - BMIter iter, liter, liter2; - BMFace *f; - BMLoop *l, *l2; - - BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); - - BLI_LINKSTACK_INIT(stack); - - BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { - if ((BM_elem_flag_test(f, BM_ELEM_HIDDEN) != 0) || - (BM_elem_flag_test(f, BM_ELEM_TAG) != 0) || - (BM_elem_flag_test(f, BM_ELEM_SELECT) == 0)) { - continue; - } - - BLI_assert(BLI_LINKSTACK_SIZE(stack) == 0); - - do { - BM_face_select_set(bm, f, true); - - BM_elem_flag_enable(f, BM_ELEM_TAG); - - BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { - BM_ITER_ELEM (l2, &liter2, l, BM_LOOPS_OF_LOOP) { - float angle_cos; - - if (BM_elem_flag_test(l2->f, BM_ELEM_TAG) || - BM_elem_flag_test(l2->f, BM_ELEM_HIDDEN)) { - continue; - } - - angle_cos = dot_v3v3(f->no, l2->f->no); - - if (angle_cos > angle_limit_cos) { - BLI_LINKSTACK_PUSH(stack, l2->f); - } - } - } - } while ((f = BLI_LINKSTACK_POP(stack))); - } - - BLI_LINKSTACK_FREE(stack); - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_faces_select_linked_flat(wmOperatorType *ot) -{ - PropertyRNA *prop; - - /* identifiers */ - ot->name = "Select Linked Flat Faces"; - ot->description = "Select linked faces by angle"; - ot->idname = "MESH_OT_faces_select_linked_flat"; - - /* api callbacks */ - ot->exec = edbm_select_linked_flat_faces_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* props */ - prop = RNA_def_float_rotation(ot->srna, - "sharpness", - 0, - NULL, - DEG2RADF(0.01f), - DEG2RADF(180.0f), - "Sharpness", - "", - DEG2RADF(1.0f), - DEG2RADF(180.0f)); - RNA_def_property_float_default(prop, DEG2RADF(1.0f)); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Non-Manifold Operator - * \{ */ - -static int edbm_select_non_manifold_exec(bContext *C, wmOperator *op) -{ - const bool use_extend = RNA_boolean_get(op->ptr, "extend"); - const bool use_wire = RNA_boolean_get(op->ptr, "use_wire"); - const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary"); - const bool use_multi_face = RNA_boolean_get(op->ptr, "use_multi_face"); - const bool use_non_contiguous = RNA_boolean_get(op->ptr, "use_non_contiguous"); - const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); - - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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); - BMVert *v; - BMEdge *e; - BMIter iter; - - if (!use_extend) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - } - - /* Selects isolated verts, and edges that do not have 2 neighboring - * faces - */ - - if (em->selectmode == SCE_SELECT_FACE) { - BKE_report(op->reports, RPT_ERROR, "Does not work in face selection mode"); - MEM_freeN(objects); - return OPERATOR_CANCELLED; - } - - if (use_verts) { - BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { - if (!BM_vert_is_manifold(v)) { - BM_vert_select_set(em->bm, v, true); - } - } - } - } - - if (use_wire || use_boundary || use_multi_face || use_non_contiguous) { - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { - if ((use_wire && BM_edge_is_wire(e)) || (use_boundary && BM_edge_is_boundary(e)) || - (use_non_contiguous && (BM_edge_is_manifold(e) && !BM_edge_is_contiguous(e))) || - (use_multi_face && (BM_edge_face_count_is_over(e, 2)))) { - /* check we never select perfect edge (in test above) */ - BLI_assert(!(BM_edge_is_manifold(e) && BM_edge_is_contiguous(e))); - - BM_edge_select_set(em->bm, e, true); - } - } - } - } - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - - EDBM_selectmode_flush(em); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_select_non_manifold(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Non-Manifold"; - ot->description = "Select all non-manifold vertices or edges"; - ot->idname = "MESH_OT_select_non_manifold"; - - /* api callbacks */ - ot->exec = edbm_select_non_manifold_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* props */ - RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend the selection"); - /* edges */ - RNA_def_boolean(ot->srna, "use_wire", true, "Wire", "Wire edges"); - RNA_def_boolean(ot->srna, "use_boundary", true, "Boundaries", "Boundary edges"); - RNA_def_boolean( - ot->srna, "use_multi_face", true, "Multiple Faces", "Edges shared by more than two faces"); - RNA_def_boolean(ot->srna, - "use_non_contiguous", - true, - "Non Contiguous", - "Edges between faces pointing in alternate directions"); - /* verts */ - RNA_def_boolean( - ot->srna, "use_verts", true, "Vertices", "Vertices connecting multiple face regions"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Random Operator - * \{ */ - -static int edbm_select_random_exec(bContext *C, wmOperator *op) -{ - const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT); - const float randfac = RNA_float_get(op->ptr, "ratio"); - const int seed = WM_operator_properties_select_random_seed_increment_get(op); - - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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); - BMIter iter; - int seed_iter = seed; - - /* This gives a consistent result regardless of object order. */ - if (ob_index) { - seed_iter += BLI_ghashutil_strhash_p(obedit->id.name); - } - - if (em->selectmode & SCE_SELECT_VERTEX) { - int elem_map_len = 0; - BMVert **elem_map = MEM_mallocN(sizeof(*elem_map) * em->bm->totvert, __func__); - BMVert *eve; - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - elem_map[elem_map_len++] = eve; - } - } - - BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); - const int count_select = elem_map_len * randfac; - for (int i = 0; i < count_select; i++) { - BM_vert_select_set(em->bm, elem_map[i], select); - } - MEM_freeN(elem_map); - } - else if (em->selectmode & SCE_SELECT_EDGE) { - int elem_map_len = 0; - BMEdge **elem_map = MEM_mallocN(sizeof(*elem_map) * em->bm->totedge, __func__); - BMEdge *eed; - BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { - if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { - elem_map[elem_map_len++] = eed; - } - } - BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); - const int count_select = elem_map_len * randfac; - for (int i = 0; i < count_select; i++) { - BM_edge_select_set(em->bm, elem_map[i], select); - } - MEM_freeN(elem_map); - } - else { - int elem_map_len = 0; - BMFace **elem_map = MEM_mallocN(sizeof(*elem_map) * em->bm->totface, __func__); - BMFace *efa; - BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { - if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { - elem_map[elem_map_len++] = efa; - } - } - BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); - const int count_select = elem_map_len * randfac; - for (int i = 0; i < count_select; i++) { - BM_face_select_set(em->bm, elem_map[i], select); - } - MEM_freeN(elem_map); - } - - if (select) { - /* was EDBM_select_flush, but it over select in edge/face mode */ - EDBM_selectmode_flush(em); - } - else { - EDBM_deselect_flush(em); - } - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - - MEM_freeN(objects); - return OPERATOR_FINISHED; -} - -void MESH_OT_select_random(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Random"; - ot->description = "Randomly select vertices"; - ot->idname = "MESH_OT_select_random"; - - /* api callbacks */ - ot->exec = edbm_select_random_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* props */ - WM_operator_properties_select_random(ot); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Ungrouped Operator - * \{ */ - -static bool edbm_select_ungrouped_poll(bContext *C) -{ - if (ED_operator_editmesh(C)) { - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = BKE_editmesh_from_object(obedit); - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - - const ListBase *defbase = BKE_object_defgroup_list(obedit); - if ((em->selectmode & SCE_SELECT_VERTEX) == 0) { - CTX_wm_operator_poll_msg_set(C, "Must be in vertex selection mode"); - } - else if (BLI_listbase_is_empty(defbase) || cd_dvert_offset == -1) { - CTX_wm_operator_poll_msg_set(C, "No weights/vertex groups on object"); - } - else { - return true; - } - } - return false; -} - -static int edbm_select_ungrouped_exec(bContext *C, wmOperator *op) -{ - const bool extend = RNA_boolean_get(op->ptr, "extend"); - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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); - - const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); - - if (cd_dvert_offset == -1) { - continue; - } - - BMVert *eve; - BMIter iter; - - bool changed = false; - - if (!extend) { - if (em->bm->totvertsel) { - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - changed = true; - } - } - - BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { - MDeformVert *dv = BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset); - /* no dv or dv set with no weight */ - if (ELEM(NULL, dv, dv->dw)) { - BM_vert_select_set(em->bm, eve, true); - changed = true; - } - } - } - - if (changed) { - EDBM_selectmode_flush(em); - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - } - MEM_freeN(objects); - return OPERATOR_FINISHED; -} - -void MESH_OT_select_ungrouped(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Ungrouped"; - ot->idname = "MESH_OT_select_ungrouped"; - ot->description = "Select vertices without a group"; - - /* api callbacks */ - ot->exec = edbm_select_ungrouped_exec; - ot->poll = edbm_select_ungrouped_poll; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection"); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Axis Operator - * \{ */ - -enum { - SELECT_AXIS_POS = 0, - SELECT_AXIS_NEG = 1, - SELECT_AXIS_ALIGN = 2, -}; - -static int edbm_select_axis_exec(bContext *C, wmOperator *op) -{ - Scene *scene = CTX_data_scene(C); - ViewLayer *view_layer = CTX_data_view_layer(C); - Object *obedit = CTX_data_edit_object(C); - BMEditMesh *em = BKE_editmesh_from_object(obedit); - BMVert *v_act = BM_mesh_active_vert_get(em->bm); - const int orientation = RNA_enum_get(op->ptr, "orientation"); - const int axis = RNA_enum_get(op->ptr, "axis"); - const int sign = RNA_enum_get(op->ptr, "sign"); - - if (v_act == NULL) { - BKE_report( - op->reports, RPT_WARNING, "This operator requires an active vertex (last selected)"); - return OPERATOR_CANCELLED; - } - - const float limit = RNA_float_get(op->ptr, "threshold"); - - float value; - float axis_mat[3][3]; - - /* 3D view variables may be NULL, (no need to check in poll function). */ - ED_transform_calc_orientation_from_type_ex(scene, - view_layer, - CTX_wm_view3d(C), - CTX_wm_region_view3d(C), - obedit, - obedit, - orientation, - V3D_AROUND_ACTIVE, - axis_mat); - - const float *axis_vector = axis_mat[axis]; - - { - float vertex_world[3]; - mul_v3_m4v3(vertex_world, obedit->obmat, v_act->co); - value = dot_v3v3(axis_vector, vertex_world); - } - - if (sign == SELECT_AXIS_NEG) { - value += limit; - } - else if (sign == SELECT_AXIS_POS) { - value -= limit; - } - - uint objects_len = 0; - Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( - scene, view_layer, CTX_wm_view3d(C), &objects_len); - for (uint ob_index = 0; ob_index < objects_len; ob_index++) { - Object *obedit_iter = objects[ob_index]; - BMEditMesh *em_iter = BKE_editmesh_from_object(obedit_iter); - BMesh *bm = em_iter->bm; - - if (bm->totvert == bm->totvertsel) { - continue; - } - - BMIter iter; - BMVert *v; - bool changed = false; - - BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { - if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN | BM_ELEM_SELECT)) { - float v_iter_world[3]; - mul_v3_m4v3(v_iter_world, obedit_iter->obmat, v->co); - const float value_iter = dot_v3v3(axis_vector, v_iter_world); - switch (sign) { - case SELECT_AXIS_ALIGN: - if (fabsf(value_iter - value) < limit) { - BM_vert_select_set(bm, v, true); - changed = true; - } - break; - case SELECT_AXIS_NEG: - if (value_iter < value) { - BM_vert_select_set(bm, v, true); - changed = true; - } - break; - case SELECT_AXIS_POS: - if (value_iter > value) { - BM_vert_select_set(bm, v, true); - changed = true; - } - break; - } - } - } - if (changed) { - EDBM_selectmode_flush(em_iter); - WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit_iter->data); - DEG_id_tag_update(obedit_iter->data, ID_RECALC_SELECT); - } - } - MEM_freeN(objects); - return OPERATOR_FINISHED; -} - -void MESH_OT_select_axis(wmOperatorType *ot) -{ - static const EnumPropertyItem axis_sign_items[] = { - {SELECT_AXIS_POS, "POS", 0, "Positive Axis", ""}, - {SELECT_AXIS_NEG, "NEG", 0, "Negative Axis", ""}, - {SELECT_AXIS_ALIGN, "ALIGN", 0, "Aligned Axis", ""}, - {0, NULL, 0, NULL, NULL}, - }; - - /* identifiers */ - ot->name = "Select Axis"; - ot->description = "Select all data in the mesh on a single axis"; - ot->idname = "MESH_OT_select_axis"; - - /* api callbacks */ - ot->exec = edbm_select_axis_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - /* properties */ - RNA_def_enum(ot->srna, - "orientation", - rna_enum_transform_orientation_items, - V3D_ORIENT_LOCAL, - "Axis Mode", - "Axis orientation"); - RNA_def_enum(ot->srna, "sign", axis_sign_items, SELECT_AXIS_POS, "Axis Sign", "Side to select"); - RNA_def_enum(ot->srna, - "axis", - rna_enum_axis_xyz_items, - 0, - "Axis", - "Select the axis to compare each vertex on"); - RNA_def_float( - ot->srna, "threshold", 0.0001f, 0.000001f, 50.0f, "Threshold", "", 0.00001f, 10.0f); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Region to Loop Operator - * \{ */ - -static int edbm_region_to_loop_exec(bContext *C, wmOperator *UNUSED(op)) -{ - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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; - } - BMFace *f; - BMEdge *e; - BMIter iter; - - BM_mesh_elem_hflag_disable_all(em->bm, BM_EDGE, BM_ELEM_TAG, false); - - BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { - BMLoop *l1, *l2; - BMIter liter1, liter2; - - BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) { - int tot = 0, totsel = 0; - - BM_ITER_ELEM (l2, &liter2, l1->e, BM_LOOPS_OF_EDGE) { - tot++; - totsel += BM_elem_flag_test(l2->f, BM_ELEM_SELECT) != 0; - } - - if ((tot != totsel && totsel > 0) || (totsel == 1 && tot == 1)) { - BM_elem_flag_enable(l1->e, BM_ELEM_TAG); - } - } - } - - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - - 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); - } - } - - /* If in face-only select mode, switch to edge select mode so that - * an edge-only selection is not inconsistent state */ - if (em->selectmode == SCE_SELECT_FACE) { - em->selectmode = SCE_SELECT_EDGE; - EDBM_selectmode_set(em); - EDBM_selectmode_to_scene(C); - } - - DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_region_to_loop(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Boundary Loop"; - ot->idname = "MESH_OT_region_to_loop"; - ot->description = "Select boundary edges around the selected faces"; - - /* api callbacks */ - ot->exec = edbm_region_to_loop_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Select Loop to Region Operator - * \{ */ - -static int loop_find_region(BMLoop *l, int flag, GSet *visit_face_set, BMFace ***region_out) -{ - BMFace **region = NULL; - BMFace **stack = NULL; - BLI_array_declare(region); - BLI_array_declare(stack); - BMFace *f; - - BLI_array_append(stack, l->f); - BLI_gset_insert(visit_face_set, l->f); - - while (BLI_array_len(stack) > 0) { - BMIter liter1, liter2; - BMLoop *l1, *l2; - - f = BLI_array_pop(stack); - BLI_array_append(region, f); - - BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) { - if (BM_elem_flag_test(l1->e, flag)) { - continue; - } - - BM_ITER_ELEM (l2, &liter2, l1->e, BM_LOOPS_OF_EDGE) { - /* avoids finding same region twice - * (otherwise) the logic works fine without */ - if (BM_elem_flag_test(l2->f, BM_ELEM_TAG)) { - continue; - } - - if (BLI_gset_add(visit_face_set, l2->f)) { - BLI_array_append(stack, l2->f); - } - } - } - } - - BLI_array_free(stack); - - *region_out = region; - return BLI_array_len(region); -} - -static int verg_radial(const void *va, const void *vb) -{ - const BMEdge *e_a = *((const BMEdge **)va); - const BMEdge *e_b = *((const BMEdge **)vb); - - const int a = BM_edge_face_count(e_a); - const int b = BM_edge_face_count(e_b); - - if (a > b) { - return -1; - } - if (a < b) { - return 1; - } - return 0; -} - -/** - * This function leaves faces tagged which are a part of the new region. - * - * \note faces already tagged are ignored, to avoid finding the same regions twice: - * important when we have regions with equal face counts, see: T40309 - */ -static int loop_find_regions(BMEditMesh *em, const bool selbigger) -{ - GSet *visit_face_set; - BMIter iter; - const int edges_len = em->bm->totedgesel; - BMEdge *e, **edges; - int count = 0, i; - - visit_face_set = BLI_gset_ptr_new_ex(__func__, edges_len); - edges = MEM_mallocN(sizeof(*edges) * edges_len, __func__); - - i = 0; - BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { - if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { - edges[i++] = e; - BM_elem_flag_enable(e, BM_ELEM_TAG); - } - else { - BM_elem_flag_disable(e, BM_ELEM_TAG); - } - } - - /* sort edges by radial cycle length */ - qsort(edges, edges_len, sizeof(*edges), verg_radial); - - for (i = 0; i < edges_len; i++) { - BMIter liter; - BMLoop *l; - BMFace **region = NULL, **region_out; - int c, tot = 0; - - e = edges[i]; - - if (!BM_elem_flag_test(e, BM_ELEM_TAG)) { - continue; - } - - BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) { - if (BLI_gset_haskey(visit_face_set, l->f)) { - continue; - } - - c = loop_find_region(l, BM_ELEM_SELECT, visit_face_set, ®ion_out); - - if (!region || (selbigger ? c >= tot : c < tot)) { - /* this region is the best seen so far */ - tot = c; - if (region) { - /* free the previous best */ - MEM_freeN(region); - } - /* track the current region as the new best */ - region = region_out; - } - else { - /* this region is not as good as best so far, just free it */ - MEM_freeN(region_out); - } - } - - if (region) { - int j; - - for (j = 0; j < tot; j++) { - BM_elem_flag_enable(region[j], BM_ELEM_TAG); - BM_ITER_ELEM (l, &liter, region[j], BM_LOOPS_OF_FACE) { - BM_elem_flag_disable(l->e, BM_ELEM_TAG); - } - } - - count += tot; - - MEM_freeN(region); - } - } - - MEM_freeN(edges); - BLI_gset_free(visit_face_set, NULL); - - return count; -} - -static int edbm_loop_to_region_exec(bContext *C, wmOperator *op) -{ - const bool select_bigger = RNA_boolean_get(op->ptr, "select_bigger"); - - const Scene *scene = CTX_data_scene(C); - 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( - scene, view_layer, CTX_wm_view3d(C), &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->totedgesel == 0) { - continue; - } - - BMIter iter; - BMFace *f; - - /* find the set of regions with smallest number of total faces */ - BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); - const int a = loop_find_regions(em, select_bigger); - const int b = loop_find_regions(em, !select_bigger); - - BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); - loop_find_regions(em, ((a <= b) != select_bigger) ? select_bigger : !select_bigger); - - EDBM_flag_disable_all(em, BM_ELEM_SELECT); - - BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { - if (BM_elem_flag_test(f, BM_ELEM_TAG) && !BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { - BM_face_select_set(em->bm, f, true); - } - } - - EDBM_selectmode_flush(em); - - DEG_id_tag_update(obedit->data, ID_RECALC_SELECT); - WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); - } - MEM_freeN(objects); - - return OPERATOR_FINISHED; -} - -void MESH_OT_loop_to_region(wmOperatorType *ot) -{ - /* identifiers */ - ot->name = "Select Loop Inner-Region"; - ot->idname = "MESH_OT_loop_to_region"; - ot->description = "Select region of faces inside of a selected loop of edges"; - - /* api callbacks */ - ot->exec = edbm_loop_to_region_exec; - ot->poll = ED_operator_editmesh; - - /* flags */ - ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; - - RNA_def_boolean(ot->srna, - "select_bigger", - 0, - "Select Bigger", - "Select bigger regions instead of smaller ones"); -} - -/** \} */ diff --git a/source/blender/editors/mesh/editmesh_select.cc b/source/blender/editors/mesh/editmesh_select.cc new file mode 100644 index 00000000000..d5392c7984d --- /dev/null +++ b/source/blender/editors/mesh/editmesh_select.cc @@ -0,0 +1,5353 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later + * Copyright 2004 Blender Foundation. All rights reserved. */ + +/** \file + * \ingroup edmesh + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_bitmap.h" +#include "BLI_heap.h" +#include "BLI_linklist.h" +#include "BLI_listbase.h" +#include "BLI_math.h" +#include "BLI_math_bits.h" +#include "BLI_rand.h" +#include "BLI_string.h" +#include "BLI_utildefines_stack.h" +#include "BLI_vector.hh" + +#include "BKE_context.h" +#include "BKE_customdata.h" +#include "BKE_deform.h" +#include "BKE_editmesh.h" +#include "BKE_layer.h" +#include "BKE_report.h" + +#include "WM_api.h" +#include "WM_types.h" + +#include "RNA_access.h" +#include "RNA_define.h" +#include "RNA_enum_types.h" + +#include "ED_mesh.h" +#include "ED_object.h" +#include "ED_screen.h" +#include "ED_select_utils.h" +#include "ED_transform.h" +#include "ED_view3d.h" + +#include "BLT_translation.h" + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" + +#include "UI_resources.h" + +#include "bmesh_tools.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "DRW_select_buffer.h" + +#include "mesh_intern.h" /* own include */ + +/* use bmesh operator flags for a few operators */ +#define BMO_ELE_TAG 1 + +/* -------------------------------------------------------------------- */ +/** \name Select Mirror + * \{ */ + +void EDBM_select_mirrored(BMEditMesh *em, + const Mesh *me, + const int axis, + const bool extend, + int *r_totmirr, + int *r_totfail) +{ + BMesh *bm = em->bm; + BMIter iter; + int totmirr = 0; + int totfail = 0; + bool use_topology = me->editflag & ME_EDIT_MIRROR_TOPO; + + *r_totmirr = *r_totfail = 0; + + /* select -> tag */ + if (bm->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + BM_elem_flag_set(v, BM_ELEM_TAG, BM_elem_flag_test(v, BM_ELEM_SELECT)); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT)); + } + } + else { + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_TAG, BM_elem_flag_test(f, BM_ELEM_SELECT)); + } + } + + EDBM_verts_mirror_cache_begin(em, axis, true, true, false, use_topology); + + if (!extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + if (bm->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN) && BM_elem_flag_test(v, BM_ELEM_TAG)) { + BMVert *v_mirr = EDBM_verts_mirror_get(em, v); + if (v_mirr && !BM_elem_flag_test(v_mirr, BM_ELEM_HIDDEN)) { + BM_vert_select_set(bm, v_mirr, true); + totmirr++; + } + else { + totfail++; + } + } + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN) && BM_elem_flag_test(e, BM_ELEM_TAG)) { + BMEdge *e_mirr = EDBM_verts_mirror_get_edge(em, e); + if (e_mirr && !BM_elem_flag_test(e_mirr, BM_ELEM_HIDDEN)) { + BM_edge_select_set(bm, e_mirr, true); + totmirr++; + } + else { + totfail++; + } + } + } + } + else { + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN) && BM_elem_flag_test(f, BM_ELEM_TAG)) { + BMFace *f_mirr = EDBM_verts_mirror_get_face(em, f); + if (f_mirr && !BM_elem_flag_test(f_mirr, BM_ELEM_HIDDEN)) { + BM_face_select_set(bm, f_mirr, true); + totmirr++; + } + else { + totfail++; + } + } + } + } + + EDBM_verts_mirror_cache_end(em); + + *r_totmirr = totmirr; + *r_totfail = totfail; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Back-Buffer OpenGL Selection + * \{ */ + +static BMElem *edbm_select_id_bm_elem_get(Base **bases, const uint sel_id, uint *r_base_index) +{ + uint elem_id; + char elem_type = 0; + bool success = DRW_select_buffer_elem_get(sel_id, &elem_id, r_base_index, &elem_type); + + if (success) { + Object *obedit = bases[*r_base_index]->object; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + switch (elem_type) { + case SCE_SELECT_FACE: + return (BMElem *)BM_face_at_index_find_or_table(em->bm, elem_id); + case SCE_SELECT_EDGE: + return (BMElem *)BM_edge_at_index_find_or_table(em->bm, elem_id); + case SCE_SELECT_VERTEX: + return (BMElem *)BM_vert_at_index_find_or_table(em->bm, elem_id); + default: + BLI_assert(0); + return nullptr; + } + } + + return nullptr; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Find Nearest Vert/Edge/Face + * + * \note Screen-space manhattan distances are used here, + * since its faster and good enough for the purpose of selection. + * + * \note \a dist_bias is used so we can bias against selected items. + * when choosing between elements of a single type, but return the real distance + * to avoid the bias interfering with distance comparisons when mixing types. + * \{ */ + +#define FIND_NEAR_SELECT_BIAS 5 +#define FIND_NEAR_CYCLE_THRESHOLD_MIN 3 + +struct NearestVertUserData_Hit { + float dist; + float dist_bias; + int index; + BMVert *vert; +}; + +struct NearestVertUserData { + float mval_fl[2]; + bool use_select_bias; + bool use_cycle; + int cycle_index_prev; + + NearestVertUserData_Hit hit; + NearestVertUserData_Hit hit_cycle; +}; + +static void findnearestvert__doClosest(void *userData, + BMVert *eve, + const float screen_co[2], + int index) +{ + NearestVertUserData *data = static_cast(userData); + float dist_test, dist_test_bias; + + dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (data->use_select_bias && BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + dist_test_bias += FIND_NEAR_SELECT_BIAS; + } + + if (dist_test_bias < data->hit.dist_bias) { + data->hit.dist_bias = dist_test_bias; + data->hit.dist = dist_test; + data->hit.index = index; + data->hit.vert = eve; + } + + if (data->use_cycle) { + if ((data->hit_cycle.vert == nullptr) && (index > data->cycle_index_prev) && + (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { + data->hit_cycle.dist_bias = dist_test_bias; + data->hit_cycle.dist = dist_test; + data->hit_cycle.index = index; + data->hit_cycle.vert = eve; + } + } +} + +BMVert *EDBM_vert_find_nearest_ex(ViewContext *vc, + float *dist_px_manhattan_p, + const bool use_select_bias, + bool use_cycle, + Base **bases, + uint bases_len, + uint *r_base_index) +{ + uint base_index = 0; + + if (!XRAY_FLAG_ENABLED(vc->v3d)) { + uint dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, + *dist_px_manhattan_p); + uint index; + BMVert *eve; + + /* No afterqueue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ + { + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_VERTEX); + + index = DRW_select_buffer_find_nearest_to_point( + vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); + + if (index) { + eve = (BMVert *)edbm_select_id_bm_elem_get(bases, index, &base_index); + } + else { + eve = nullptr; + } + } + + if (eve) { + if (dist_px_manhattan_test < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = dist_px_manhattan_test; + return eve; + } + } + return nullptr; + } + + NearestVertUserData data = {{0}}; + const NearestVertUserData_Hit *hit = nullptr; + const eV3DProjTest clip_flag = RV3D_CLIPPING_ENABLED(vc->v3d, vc->rv3d) ? + V3D_PROJ_TEST_CLIP_DEFAULT : + V3D_PROJ_TEST_CLIP_DEFAULT & ~V3D_PROJ_TEST_CLIP_BB; + BMesh *prev_select_bm = nullptr; + + static struct { + int index; + const BMVert *elem; + const BMesh *bm; + } prev_select = {0}; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.use_select_bias = use_select_bias; + data.use_cycle = use_cycle; + + for (; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + ED_view3d_viewcontext_init_object(vc, base_iter->object); + if (use_cycle && prev_select.bm == vc->em->bm && + prev_select.elem == BM_vert_at_index_find_or_table(vc->em->bm, prev_select.index)) { + data.cycle_index_prev = prev_select.index; + /* No need to compare in the rest of the loop. */ + use_cycle = false; + } + else { + data.cycle_index_prev = 0; + } + + data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = + *dist_px_manhattan_p; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + mesh_foreachScreenVert(vc, findnearestvert__doClosest, &data, clip_flag); + + hit = (data.use_cycle && data.hit_cycle.vert) ? &data.hit_cycle : &data.hit; + + if (hit->dist < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = hit->dist; + prev_select_bm = vc->em->bm; + } + } + + if (hit == nullptr) { + return nullptr; + } + + prev_select.index = hit->index; + prev_select.elem = hit->vert; + prev_select.bm = prev_select_bm; + + return hit->vert; +} + +BMVert *EDBM_vert_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) +{ + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + return EDBM_vert_find_nearest_ex(vc, dist_px_manhattan_p, false, false, &base, 1, nullptr); +} + +/* find the distance to the edge we already have */ +struct NearestEdgeUserData_ZBuf { + float mval_fl[2]; + float dist; + const BMEdge *edge_test; +}; + +static void find_nearest_edge_center__doZBuf(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int UNUSED(index)) +{ + NearestEdgeUserData_ZBuf *data = static_cast(userData); + + if (eed == data->edge_test) { + float dist_test; + float screen_co_mid[2]; + + mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); + dist_test = len_manhattan_v2v2(data->mval_fl, screen_co_mid); + + if (dist_test < data->dist) { + data->dist = dist_test; + } + } +} + +struct NearestEdgeUserData_Hit { + float dist; + float dist_bias; + int index; + BMEdge *edge; + + /* edges only, un-biased manhattan distance to which ever edge we pick + * (not used for choosing) */ + float dist_center_px_manhattan; +}; + +struct NearestEdgeUserData { + ViewContext vc; + float mval_fl[2]; + bool use_select_bias; + bool use_cycle; + int cycle_index_prev; + + NearestEdgeUserData_Hit hit; + NearestEdgeUserData_Hit hit_cycle; +}; + +/* NOTE: uses v3d, so needs active 3d window. */ +static void find_nearest_edge__doClosest( + void *userData, BMEdge *eed, const float screen_co_a[2], const float screen_co_b[2], int index) +{ + NearestEdgeUserData *data = static_cast(userData); + float dist_test, dist_test_bias; + + float fac = line_point_factor_v2(data->mval_fl, screen_co_a, screen_co_b); + float screen_co[2]; + + if (fac <= 0.0f) { + fac = 0.0f; + copy_v2_v2(screen_co, screen_co_a); + } + else if (fac >= 1.0f) { + fac = 1.0f; + copy_v2_v2(screen_co, screen_co_b); + } + else { + interp_v2_v2v2(screen_co, screen_co_a, screen_co_b, fac); + } + + dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (data->use_select_bias && BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + dist_test_bias += FIND_NEAR_SELECT_BIAS; + } + + if (data->vc.rv3d->rflag & RV3D_CLIPPING) { + float vec[3]; + + interp_v3_v3v3(vec, eed->v1->co, eed->v2->co, fac); + if (ED_view3d_clipping_test(data->vc.rv3d, vec, true)) { + return; + } + } + + if (dist_test_bias < data->hit.dist_bias) { + float screen_co_mid[2]; + + data->hit.dist_bias = dist_test_bias; + data->hit.dist = dist_test; + data->hit.index = index; + data->hit.edge = eed; + + mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); + data->hit.dist_center_px_manhattan = len_manhattan_v2v2(data->mval_fl, screen_co_mid); + } + + if (data->use_cycle) { + if ((data->hit_cycle.edge == nullptr) && (index > data->cycle_index_prev) && + (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { + float screen_co_mid[2]; + + data->hit_cycle.dist_bias = dist_test_bias; + data->hit_cycle.dist = dist_test; + data->hit_cycle.index = index; + data->hit_cycle.edge = eed; + + mid_v2_v2v2(screen_co_mid, screen_co_a, screen_co_b); + data->hit_cycle.dist_center_px_manhattan = len_manhattan_v2v2(data->mval_fl, screen_co_mid); + } + } +} + +BMEdge *EDBM_edge_find_nearest_ex(ViewContext *vc, + float *dist_px_manhattan_p, + float *r_dist_center_px_manhattan, + const bool use_select_bias, + bool use_cycle, + BMEdge **r_eed_zbuf, + Base **bases, + uint bases_len, + uint *r_base_index) +{ + uint base_index = 0; + + if (!XRAY_FLAG_ENABLED(vc->v3d)) { + uint dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, + *dist_px_manhattan_p); + uint index; + BMEdge *eed; + + /* No afterqueue (yet), so we check it now, otherwise the bm_xxxofs indices are bad. */ + { + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_EDGE); + + index = DRW_select_buffer_find_nearest_to_point( + vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); + + if (index) { + eed = (BMEdge *)edbm_select_id_bm_elem_get(bases, index, &base_index); + } + else { + eed = nullptr; + } + } + + if (r_eed_zbuf) { + *r_eed_zbuf = eed; + } + + /* exception for faces (verts don't need this) */ + if (r_dist_center_px_manhattan && eed) { + NearestEdgeUserData_ZBuf data; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.dist = FLT_MAX; + data.edge_test = eed; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + mesh_foreachScreenEdge(vc, + find_nearest_edge_center__doZBuf, + &data, + V3D_PROJ_TEST_CLIP_DEFAULT | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + *r_dist_center_px_manhattan = data.dist; + } + /* end exception */ + + if (eed) { + if (dist_px_manhattan_test < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = dist_px_manhattan_test; + return eed; + } + } + return nullptr; + } + + NearestEdgeUserData data = {{0}}; + const NearestEdgeUserData_Hit *hit = nullptr; + /* interpolate along the edge before doing a clipping plane test */ + const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT & ~V3D_PROJ_TEST_CLIP_BB; + BMesh *prev_select_bm = nullptr; + + static struct { + int index; + const BMEdge *elem; + const BMesh *bm; + } prev_select = {0}; + + data.vc = *vc; + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.use_select_bias = use_select_bias; + data.use_cycle = use_cycle; + + for (; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + ED_view3d_viewcontext_init_object(vc, base_iter->object); + if (use_cycle && prev_select.bm == vc->em->bm && + prev_select.elem == BM_edge_at_index_find_or_table(vc->em->bm, prev_select.index)) { + data.cycle_index_prev = prev_select.index; + /* No need to compare in the rest of the loop. */ + use_cycle = false; + } + else { + data.cycle_index_prev = 0; + } + + data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = + *dist_px_manhattan_p; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + mesh_foreachScreenEdge( + vc, find_nearest_edge__doClosest, &data, clip_flag | V3D_PROJ_TEST_CLIP_CONTENT_DEFAULT); + + hit = (data.use_cycle && data.hit_cycle.edge) ? &data.hit_cycle : &data.hit; + + if (hit->dist < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = hit->dist; + prev_select_bm = vc->em->bm; + } + } + + if (hit == nullptr) { + return nullptr; + } + + if (r_dist_center_px_manhattan) { + *r_dist_center_px_manhattan = hit->dist_center_px_manhattan; + } + + prev_select.index = hit->index; + prev_select.elem = hit->edge; + prev_select.bm = prev_select_bm; + + return hit->edge; +} + +BMEdge *EDBM_edge_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) +{ + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + return EDBM_edge_find_nearest_ex( + vc, dist_px_manhattan_p, nullptr, false, false, nullptr, &base, 1, nullptr); +} + +/* find the distance to the face we already have */ +struct NearestFaceUserData_ZBuf { + float mval_fl[2]; + float dist_px_manhattan; + const BMFace *face_test; +}; + +static void find_nearest_face_center__doZBuf(void *userData, + BMFace *efa, + const float screen_co[2], + int UNUSED(index)) +{ + NearestFaceUserData_ZBuf *data = static_cast(userData); + + if (efa == data->face_test) { + const float dist_test = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (dist_test < data->dist_px_manhattan) { + data->dist_px_manhattan = dist_test; + } + } +} + +struct NearestFaceUserData_Hit { + float dist; + float dist_bias; + int index; + BMFace *face; +}; + +struct NearestFaceUserData { + float mval_fl[2]; + bool use_select_bias; + bool use_cycle; + int cycle_index_prev; + + NearestFaceUserData_Hit hit; + NearestFaceUserData_Hit hit_cycle; +}; + +static void findnearestface__doClosest(void *userData, + BMFace *efa, + const float screen_co[2], + int index) +{ + NearestFaceUserData *data = static_cast(userData); + float dist_test, dist_test_bias; + + dist_test = dist_test_bias = len_manhattan_v2v2(data->mval_fl, screen_co); + + if (data->use_select_bias && BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + dist_test_bias += FIND_NEAR_SELECT_BIAS; + } + + if (dist_test_bias < data->hit.dist_bias) { + data->hit.dist_bias = dist_test_bias; + data->hit.dist = dist_test; + data->hit.index = index; + data->hit.face = efa; + } + + if (data->use_cycle) { + if ((data->hit_cycle.face == nullptr) && (index > data->cycle_index_prev) && + (dist_test_bias < FIND_NEAR_CYCLE_THRESHOLD_MIN)) { + data->hit_cycle.dist_bias = dist_test_bias; + data->hit_cycle.dist = dist_test; + data->hit_cycle.index = index; + data->hit_cycle.face = efa; + } + } +} + +BMFace *EDBM_face_find_nearest_ex(ViewContext *vc, + float *dist_px_manhattan_p, + float *r_dist_center, + const bool use_zbuf_single_px, + const bool use_select_bias, + bool use_cycle, + BMFace **r_efa_zbuf, + Base **bases, + uint bases_len, + uint *r_base_index) +{ + uint base_index = 0; + + if (!XRAY_FLAG_ENABLED(vc->v3d)) { + float dist_test; + uint index; + BMFace *efa; + + { + uint dist_px_manhattan_test = 0; + if (*dist_px_manhattan_p != 0.0f && (use_zbuf_single_px == false)) { + dist_px_manhattan_test = (uint)ED_view3d_backbuf_sample_size_clamp(vc->region, + *dist_px_manhattan_p); + } + + DRW_select_buffer_context_create(bases, bases_len, SCE_SELECT_FACE); + + if (dist_px_manhattan_test == 0) { + index = DRW_select_buffer_sample_point(vc->depsgraph, vc->region, vc->v3d, vc->mval); + dist_test = 0.0f; + } + else { + index = DRW_select_buffer_find_nearest_to_point( + vc->depsgraph, vc->region, vc->v3d, vc->mval, 1, UINT_MAX, &dist_px_manhattan_test); + dist_test = dist_px_manhattan_test; + } + + if (index) { + efa = (BMFace *)edbm_select_id_bm_elem_get(bases, index, &base_index); + } + else { + efa = nullptr; + } + } + + if (r_efa_zbuf) { + *r_efa_zbuf = efa; + } + + /* exception for faces (verts don't need this) */ + if (r_dist_center && efa) { + NearestFaceUserData_ZBuf data; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.dist_px_manhattan = FLT_MAX; + data.face_test = efa; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + + mesh_foreachScreenFace( + vc, find_nearest_face_center__doZBuf, &data, V3D_PROJ_TEST_CLIP_DEFAULT); + + *r_dist_center = data.dist_px_manhattan; + } + /* end exception */ + + if (efa) { + if (dist_test < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = dist_test; + return efa; + } + } + return nullptr; + } + + NearestFaceUserData data = {{0}}; + const NearestFaceUserData_Hit *hit = nullptr; + const eV3DProjTest clip_flag = V3D_PROJ_TEST_CLIP_DEFAULT; + BMesh *prev_select_bm = nullptr; + + static struct { + int index; + const BMFace *elem; + const BMesh *bm; + } prev_select = {0}; + + data.mval_fl[0] = vc->mval[0]; + data.mval_fl[1] = vc->mval[1]; + data.use_select_bias = use_select_bias; + data.use_cycle = use_cycle; + + for (; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + ED_view3d_viewcontext_init_object(vc, base_iter->object); + if (use_cycle && prev_select.bm == vc->em->bm && + prev_select.elem == BM_face_at_index_find_or_table(vc->em->bm, prev_select.index)) { + data.cycle_index_prev = prev_select.index; + /* No need to compare in the rest of the loop. */ + use_cycle = false; + } + else { + data.cycle_index_prev = 0; + } + + data.hit.dist = data.hit_cycle.dist = data.hit.dist_bias = data.hit_cycle.dist_bias = + *dist_px_manhattan_p; + + ED_view3d_init_mats_rv3d(vc->obedit, vc->rv3d); + mesh_foreachScreenFace(vc, findnearestface__doClosest, &data, clip_flag); + + hit = (data.use_cycle && data.hit_cycle.face) ? &data.hit_cycle : &data.hit; + + if (hit->dist < *dist_px_manhattan_p) { + if (r_base_index) { + *r_base_index = base_index; + } + *dist_px_manhattan_p = hit->dist; + prev_select_bm = vc->em->bm; + } + } + + if (hit == nullptr) { + return nullptr; + } + + if (r_dist_center) { + *r_dist_center = hit->dist; + } + + prev_select.index = hit->index; + prev_select.elem = hit->face; + prev_select.bm = prev_select_bm; + + return hit->face; +} + +BMFace *EDBM_face_find_nearest(ViewContext *vc, float *dist_px_manhattan_p) +{ + BKE_view_layer_synced_ensure(vc->scene, vc->view_layer); + Base *base = BKE_view_layer_base_find(vc->view_layer, vc->obact); + return EDBM_face_find_nearest_ex( + vc, dist_px_manhattan_p, nullptr, false, false, false, nullptr, &base, 1, nullptr); +} + +#undef FIND_NEAR_SELECT_BIAS +#undef FIND_NEAR_CYCLE_THRESHOLD_MIN + +/* best distance based on screen coords. + * use em->selectmode to define how to use + * selected vertices and edges get disadvantage + * return 1 if found one + */ +static bool unified_findnearest(ViewContext *vc, + Base **bases, + const uint bases_len, + int *r_base_index, + BMVert **r_eve, + BMEdge **r_eed, + BMFace **r_efa) +{ + BMEditMesh *em = vc->em; + + const bool use_cycle = !WM_cursor_test_motion_and_update(vc->mval); + const float dist_init = ED_view3d_select_dist_px(); + /* since edges select lines, we give dots advantage of ~20 pix */ + const float dist_margin = (dist_init / 2); + float dist = dist_init; + + struct { + struct { + BMVert *ele; + int base_index; + } v; + struct { + BMEdge *ele; + int base_index; + } e, e_zbuf; + struct { + BMFace *ele; + int base_index; + } f, f_zbuf; + } hit = {{nullptr}}; + + /* no afterqueue (yet), so we check it now, otherwise the em_xxxofs indices are bad */ + + if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_FACE)) { + float dist_center = 0.0f; + float *dist_center_p = (em->selectmode & (SCE_SELECT_EDGE | SCE_SELECT_VERTEX)) ? + &dist_center : + nullptr; + + uint base_index = 0; + BMFace *efa_zbuf = nullptr; + BMFace *efa_test = EDBM_face_find_nearest_ex( + vc, &dist, dist_center_p, true, true, use_cycle, &efa_zbuf, bases, bases_len, &base_index); + + if (efa_test && dist_center_p) { + dist = min_ff(dist_margin, dist_center); + } + if (efa_test) { + hit.f.base_index = base_index; + hit.f.ele = efa_test; + } + if (efa_zbuf) { + hit.f_zbuf.base_index = base_index; + hit.f_zbuf.ele = efa_zbuf; + } + } + + if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_EDGE)) { + float dist_center = 0.0f; + float *dist_center_p = (em->selectmode & SCE_SELECT_VERTEX) ? &dist_center : nullptr; + + uint base_index = 0; + BMEdge *eed_zbuf = nullptr; + BMEdge *eed_test = EDBM_edge_find_nearest_ex( + vc, &dist, dist_center_p, true, use_cycle, &eed_zbuf, bases, bases_len, &base_index); + + if (eed_test && dist_center_p) { + dist = min_ff(dist_margin, dist_center); + } + if (eed_test) { + hit.e.base_index = base_index; + hit.e.ele = eed_test; + } + if (eed_zbuf) { + hit.e_zbuf.base_index = base_index; + hit.e_zbuf.ele = eed_zbuf; + } + } + + if ((dist > 0.0f) && (em->selectmode & SCE_SELECT_VERTEX)) { + uint base_index = 0; + BMVert *eve_test = EDBM_vert_find_nearest_ex( + vc, &dist, true, use_cycle, bases, bases_len, &base_index); + + if (eve_test) { + hit.v.base_index = base_index; + hit.v.ele = eve_test; + } + } + + /* Return only one of 3 pointers, for front-buffer redraws. */ + if (hit.v.ele) { + hit.f.ele = nullptr; + hit.e.ele = nullptr; + } + else if (hit.e.ele) { + hit.f.ele = nullptr; + } + + /* there may be a face under the cursor, who's center if too far away + * use this if all else fails, it makes sense to select this */ + if ((hit.v.ele || hit.e.ele || hit.f.ele) == 0) { + if (hit.e_zbuf.ele) { + hit.e.base_index = hit.e_zbuf.base_index; + hit.e.ele = hit.e_zbuf.ele; + } + else if (hit.f_zbuf.ele) { + hit.f.base_index = hit.f_zbuf.base_index; + hit.f.ele = hit.f_zbuf.ele; + } + } + + /* Only one element type will be non-null. */ + BLI_assert(((hit.v.ele != nullptr) + (hit.e.ele != nullptr) + (hit.f.ele != nullptr)) <= 1); + + if (hit.v.ele) { + *r_base_index = hit.v.base_index; + } + if (hit.e.ele) { + *r_base_index = hit.e.base_index; + } + if (hit.f.ele) { + *r_base_index = hit.f.base_index; + } + + *r_eve = hit.v.ele; + *r_eed = hit.e.ele; + *r_efa = hit.f.ele; + + return (hit.v.ele || hit.e.ele || hit.f.ele); +} + +#undef FAKE_SELECT_MODE_BEGIN +#undef FAKE_SELECT_MODE_END + +bool EDBM_unified_findnearest(ViewContext *vc, + Base **bases, + const uint bases_len, + int *r_base_index, + BMVert **r_eve, + BMEdge **r_eed, + BMFace **r_efa) +{ + return unified_findnearest(vc, bases, bases_len, r_base_index, r_eve, r_eed, r_efa); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Alternate Find Nearest Vert/Edge (optional boundary) + * + * \note This uses ray-cast method instead of back-buffer, + * currently used for poly-build. + * \{ */ + +bool EDBM_unified_findnearest_from_raycast(ViewContext *vc, + Base **bases, + const uint bases_len, + bool use_boundary_vertices, + bool use_boundary_edges, + int *r_base_index_vert, + int *r_base_index_edge, + int *r_base_index_face, + BMVert **r_eve, + BMEdge **r_eed, + BMFace **r_efa) +{ + const float mval_fl[2] = {float(vc->mval[0]), float(vc->mval[1])}; + float ray_origin[3], ray_direction[3]; + + struct { + uint base_index; + BMElem *ele; + } best = {0, nullptr}; + /* Currently unused, keep since we may want to pick the best. */ + UNUSED_VARS(best); + + struct { + uint base_index; + BMElem *ele; + } best_vert = {0, nullptr}; + + struct { + uint base_index; + BMElem *ele; + } best_edge = {0, nullptr}; + + struct { + uint base_index; + BMElem *ele; + } best_face = {0, nullptr}; + + if (ED_view3d_win_to_ray_clipped( + vc->depsgraph, vc->region, vc->v3d, mval_fl, ray_origin, ray_direction, true)) { + float dist_sq_best = FLT_MAX; + float dist_sq_best_vert = FLT_MAX; + float dist_sq_best_edge = FLT_MAX; + float dist_sq_best_face = FLT_MAX; + + const bool use_vert = (r_eve != nullptr); + const bool use_edge = (r_eed != nullptr); + const bool use_face = (r_efa != nullptr); + + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *obedit = base_iter->object; + + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + float imat3[3][3]; + + ED_view3d_viewcontext_init_object(vc, obedit); + copy_m3_m4(imat3, obedit->obmat); + invert_m3(imat3); + + const float(*coords)[3] = nullptr; + { + Mesh *me_eval = (Mesh *)DEG_get_evaluated_id(vc->depsgraph, + static_cast(obedit->data)); + if (me_eval->runtime.edit_data) { + coords = me_eval->runtime.edit_data->vertexCos; + } + } + + if (coords != nullptr) { + BM_mesh_elem_index_ensure(bm, BM_VERT); + } + + if ((use_boundary_vertices || use_boundary_edges) && (use_vert || use_edge)) { + BMEdge *e; + BMIter eiter; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if ((BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false) && (BM_edge_is_boundary(e))) { + if (use_vert && use_boundary_vertices) { + for (uint j = 0; j < 2; j++) { + BMVert *v = *((&e->v1) + j); + float point[3]; + mul_v3_m4v3(point, obedit->obmat, coords ? coords[BM_elem_index_get(v)] : v->co); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_vert) { + dist_sq_best_vert = dist_sq_test; + best_vert.base_index = base_index; + best_vert.ele = (BMElem *)v; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)v; + } + } + } + + if (use_edge && use_boundary_edges) { + float point[3]; +#if 0 + const float dist_sq_test = dist_squared_ray_to_seg_v3( + ray_origin, ray_direction, e->v1->co, e->v2->co, point, &depth); +#else + if (coords) { + mid_v3_v3v3( + point, coords[BM_elem_index_get(e->v1)], coords[BM_elem_index_get(e->v2)]); + } + else { + mid_v3_v3v3(point, e->v1->co, e->v2->co); + } + mul_m4_v3(obedit->obmat, point); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_edge) { + dist_sq_best_edge = dist_sq_test; + best_edge.base_index = base_index; + best_edge.ele = (BMElem *)e; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)e; + } +#endif + } + } + } + } + /* Non boundary case. */ + if (use_vert && !use_boundary_vertices) { + BMVert *v; + BMIter viter; + BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_HIDDEN) == false) { + float point[3]; + mul_v3_m4v3(point, obedit->obmat, coords ? coords[BM_elem_index_get(v)] : v->co); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_vert) { + dist_sq_best_vert = dist_sq_test; + best_vert.base_index = base_index; + best_vert.ele = (BMElem *)v; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)v; + } + } + } + } + + if (use_edge && !use_boundary_edges) { + BMEdge *e; + BMIter eiter; + BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false) { + float point[3]; + if (coords) { + mid_v3_v3v3( + point, coords[BM_elem_index_get(e->v1)], coords[BM_elem_index_get(e->v2)]); + } + else { + mid_v3_v3v3(point, e->v1->co, e->v2->co); + } + mul_m4_v3(obedit->obmat, point); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_edge) { + dist_sq_best_edge = dist_sq_test; + best_edge.base_index = base_index; + best_edge.ele = (BMElem *)e; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)e; + } + } + } + } + + if (use_face) { + BMFace *f; + BMIter fiter; + BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_HIDDEN) == false) { + float point[3]; + if (coords) { + BM_face_calc_center_median_vcos(bm, f, point, coords); + } + else { + BM_face_calc_center_median(f, point); + } + mul_m4_v3(obedit->obmat, point); + const float dist_sq_test = dist_squared_to_ray_v3_normalized( + ray_origin, ray_direction, point); + if (dist_sq_test < dist_sq_best_face) { + dist_sq_best_face = dist_sq_test; + best_face.base_index = base_index; + best_face.ele = (BMElem *)f; + } + if (dist_sq_test < dist_sq_best) { + dist_sq_best = dist_sq_test; + best.base_index = base_index; + best.ele = (BMElem *)f; + } + } + } + } + } + } + + *r_base_index_vert = best_vert.base_index; + *r_base_index_edge = best_edge.base_index; + *r_base_index_face = best_face.base_index; + + if (r_eve) { + *r_eve = nullptr; + } + if (r_eed) { + *r_eed = nullptr; + } + if (r_efa) { + *r_efa = nullptr; + } + + if (best_vert.ele) { + *r_eve = (BMVert *)best_vert.ele; + } + if (best_edge.ele) { + *r_eed = (BMEdge *)best_edge.ele; + } + if (best_face.ele) { + *r_efa = (BMFace *)best_face.ele; + } + + return (best_vert.ele != nullptr || best_edge.ele != nullptr || best_face.ele != nullptr); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Similar Region Operator + * \{ */ + +static int edbm_select_similar_region_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + bool changed = false; + + /* group vars */ + int(*group_index)[2]; + int group_tot; + int i; + + if (bm->totfacesel < 2) { + BKE_report(op->reports, RPT_ERROR, "No face regions selected"); + return OPERATOR_CANCELLED; + } + + int *groups_array = static_cast( + MEM_mallocN(sizeof(*groups_array) * bm->totfacesel, __func__)); + group_tot = BM_mesh_calc_face_groups( + bm, groups_array, &group_index, nullptr, nullptr, nullptr, BM_ELEM_SELECT, BM_VERT); + + BM_mesh_elem_table_ensure(bm, BM_FACE); + + for (i = 0; i < group_tot; i++) { + ListBase faces_regions; + int tot; + + const int fg_sta = group_index[i][0]; + const int fg_len = group_index[i][1]; + int j; + BMFace **fg = static_cast(MEM_mallocN(sizeof(*fg) * fg_len, __func__)); + + for (j = 0; j < fg_len; j++) { + fg[j] = BM_face_at_index(bm, groups_array[fg_sta + j]); + } + + tot = BM_mesh_region_match(bm, fg, fg_len, &faces_regions); + + MEM_freeN(fg); + + if (tot) { + LinkData *link; + while ((link = static_cast(BLI_pophead(&faces_regions)))) { + BMFace *f, **faces = static_cast(link->data); + while ((f = *(faces++))) { + BM_face_select_set(bm, f, true); + } + MEM_freeN(link->data); + MEM_freeN(link); + + changed = true; + } + } + } + + MEM_freeN(groups_array); + MEM_freeN(group_index); + + if (changed) { + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + else { + BKE_report(op->reports, RPT_WARNING, "No matching face regions found"); + } + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_similar_region(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Similar Regions"; + ot->idname = "MESH_OT_select_similar_region"; + ot->description = "Select similar face regions to the current selection"; + + /* api callbacks */ + ot->exec = edbm_select_similar_region_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mode Vert/Edge/Face Operator + * \{ */ + +static int edbm_select_mode_exec(bContext *C, wmOperator *op) +{ + const int type = RNA_enum_get(op->ptr, "type"); + const int action = RNA_enum_get(op->ptr, "action"); + const bool use_extend = RNA_boolean_get(op->ptr, "use_extend"); + const bool use_expand = RNA_boolean_get(op->ptr, "use_expand"); + + if (EDBM_selectmode_toggle_multi(C, type, action, use_extend, use_expand)) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +static int edbm_select_mode_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + /* Bypass when in UV non sync-select mode, fall through to keymap that edits. */ + if (CTX_wm_space_image(C)) { + ToolSettings *ts = CTX_data_tool_settings(C); + if ((ts->uv_flag & UV_SYNC_SELECTION) == 0) { + return OPERATOR_PASS_THROUGH; + } + /* Bypass when no action is needed. */ + if (!RNA_struct_property_is_set(op->ptr, "type")) { + return OPERATOR_CANCELLED; + } + } + + /* detecting these options based on shift/ctrl here is weak, but it's done + * to make this work when clicking buttons or menus */ + if (!RNA_struct_property_is_set(op->ptr, "use_extend")) { + RNA_boolean_set(op->ptr, "use_extend", event->modifier & KM_SHIFT); + } + if (!RNA_struct_property_is_set(op->ptr, "use_expand")) { + RNA_boolean_set(op->ptr, "use_expand", event->modifier & KM_CTRL); + } + + return edbm_select_mode_exec(C, op); +} + +static char *edbm_select_mode_get_description(bContext *UNUSED(C), + wmOperatorType *UNUSED(op), + PointerRNA *values) +{ + const int type = RNA_enum_get(values, "type"); + + /* Because the special behavior for shift and ctrl click depend on user input, they may be + * incorrect if the operator is used from a script or from a special button. So only return the + * specialized descriptions if only the "type" is set, which conveys that the operator is meant + * to be used with the logic in the `invoke` method. */ + if (RNA_struct_property_is_set(values, "type") && + !RNA_struct_property_is_set(values, "use_extend") && + !RNA_struct_property_is_set(values, "use_expand") && + !RNA_struct_property_is_set(values, "action")) { + switch (type) { + case SCE_SELECT_VERTEX: + return BLI_strdup(TIP_( + "Vertex select - Shift-Click for multiple modes, Ctrl-Click contracts selection")); + case SCE_SELECT_EDGE: + return BLI_strdup( + TIP_("Edge select - Shift-Click for multiple modes, " + "Ctrl-Click expands/contracts selection depending on the current mode")); + case SCE_SELECT_FACE: + return BLI_strdup( + TIP_("Face select - Shift-Click for multiple modes, Ctrl-Click expands selection")); + } + } + + return nullptr; +} + +void MESH_OT_select_mode(wmOperatorType *ot) +{ + PropertyRNA *prop; + + static const EnumPropertyItem actions_items[] = { + {0, "DISABLE", 0, "Disable", "Disable selected markers"}, + {1, "ENABLE", 0, "Enable", "Enable selected markers"}, + {2, "TOGGLE", 0, "Toggle", "Toggle disabled flag for selected markers"}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Select Mode"; + ot->idname = "MESH_OT_select_mode"; + ot->description = "Change selection mode"; + + /* api callbacks */ + ot->invoke = edbm_select_mode_invoke; + ot->exec = edbm_select_mode_exec; + ot->poll = ED_operator_editmesh; + ot->get_description = edbm_select_mode_get_description; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + /* Hide all, not to show redo panel. */ + prop = RNA_def_boolean(ot->srna, "use_extend", false, "Extend", ""); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + prop = RNA_def_boolean(ot->srna, "use_expand", false, "Expand", ""); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + ot->prop = prop = RNA_def_enum(ot->srna, "type", rna_enum_mesh_select_mode_items, 0, "Type", ""); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + + prop = RNA_def_enum( + ot->srna, "action", actions_items, 2, "Action", "Selection action to execute"); + RNA_def_property_flag(prop, PROP_HIDDEN); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loop (Non Modal) Operator + * \{ */ + +static void walker_select_count(BMEditMesh *em, + int walkercode, + void *start, + int r_count_by_select[2]) +{ + BMesh *bm = em->bm; + BMElem *ele; + BMWalker walker; + + r_count_by_select[0] = r_count_by_select[1] = 0; + + BMW_init(&walker, + bm, + walkercode, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + for (ele = static_cast(BMW_begin(&walker, start)); ele; + ele = static_cast(BMW_step(&walker))) { + r_count_by_select[BM_elem_flag_test(ele, BM_ELEM_SELECT) ? 1 : 0] += 1; + + /* Early exit when mixed (could be optional if needed. */ + if (r_count_by_select[0] && r_count_by_select[1]) { + r_count_by_select[0] = r_count_by_select[1] = -1; + break; + } + } + + BMW_end(&walker); +} + +static void walker_select(BMEditMesh *em, int walkercode, void *start, const bool select) +{ + BMesh *bm = em->bm; + BMElem *ele; + BMWalker walker; + + BMW_init(&walker, + bm, + walkercode, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + for (ele = static_cast(BMW_begin(&walker, start)); ele; + ele = static_cast(BMW_step(&walker))) { + if (!select) { + BM_select_history_remove(bm, ele); + } + BM_elem_select_set(bm, ele, select); + } + BMW_end(&walker); +} + +static int edbm_loop_multiselect_exec(bContext *C, wmOperator *op) +{ + const bool is_ring = RNA_boolean_get(op->ptr, "ring"); + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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->totedgesel == 0) { + continue; + } + + BMEdge *eed; + int edindex; + BMIter iter; + int totedgesel = 0; + + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + totedgesel++; + } + } + + BMEdge **edarray = static_cast( + MEM_mallocN(sizeof(BMEdge *) * totedgesel, "edge array")); + edindex = 0; + + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + edarray[edindex] = eed; + edindex++; + } + } + + if (is_ring) { + for (edindex = 0; edindex < totedgesel; edindex += 1) { + eed = edarray[edindex]; + walker_select(em, BMW_EDGERING, eed, true); + } + EDBM_selectmode_flush(em); + } + else { + for (edindex = 0; edindex < totedgesel; edindex += 1) { + eed = edarray[edindex]; + bool non_manifold = BM_edge_face_count_is_over(eed, 2); + if (non_manifold) { + walker_select(em, BMW_EDGELOOP_NONMANIFOLD, eed, true); + } + else { + walker_select(em, BMW_EDGELOOP, eed, true); + } + } + EDBM_selectmode_flush(em); + } + MEM_freeN(edarray); + // if (EM_texFaceCheck()) + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_loop_multi_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Multi Select Loops"; + ot->idname = "MESH_OT_loop_multi_select"; + ot->description = "Select a loop of connected edges by connection type"; + + /* api callbacks */ + ot->exec = edbm_loop_multiselect_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_boolean(ot->srna, "ring", 0, "Ring", ""); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loop (Cursor Pick) Operator + * \{ */ + +static void mouse_mesh_loop_face(BMEditMesh *em, BMEdge *eed, bool select, bool select_clear) +{ + if (select_clear) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + walker_select(em, BMW_FACELOOP, eed, select); +} + +static void mouse_mesh_loop_edge_ring(BMEditMesh *em, BMEdge *eed, bool select, bool select_clear) +{ + if (select_clear) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + walker_select(em, BMW_EDGERING, eed, select); +} + +static void mouse_mesh_loop_edge( + BMEditMesh *em, BMEdge *eed, bool select, bool select_clear, bool select_cycle) +{ + bool edge_boundary = false; + bool non_manifold = BM_edge_face_count_is_over(eed, 2); + + /* Cycle between BMW_EDGELOOP / BMW_EDGEBOUNDARY. */ + if (select_cycle && BM_edge_is_boundary(eed)) { + int count_by_select[2]; + + /* If the loops selected toggle the boundaries. */ + walker_select_count(em, BMW_EDGELOOP, eed, count_by_select); + if (count_by_select[!select] == 0) { + edge_boundary = true; + + /* If the boundaries selected, toggle back to the loop. */ + walker_select_count(em, BMW_EDGEBOUNDARY, eed, count_by_select); + if (count_by_select[!select] == 0) { + edge_boundary = false; + } + } + } + + if (select_clear) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + if (edge_boundary) { + walker_select(em, BMW_EDGEBOUNDARY, eed, select); + } + else if (non_manifold) { + walker_select(em, BMW_EDGELOOP_NONMANIFOLD, eed, select); + } + else { + walker_select(em, BMW_EDGELOOP, eed, select); + } +} + +static bool mouse_mesh_loop( + bContext *C, const int mval[2], bool extend, bool deselect, bool toggle, bool ring) +{ + Base *basact = nullptr; + BMVert *eve = nullptr; + BMEdge *eed = nullptr; + BMFace *efa = nullptr; + + ViewContext vc; + BMEditMesh *em; + bool select = true; + bool select_clear = false; + bool select_cycle = true; + float mvalf[2]; + + em_setup_viewcontext(C, &vc); + mvalf[0] = (float)(vc.mval[0] = mval[0]); + mvalf[1] = (float)(vc.mval[1] = mval[1]); + + BMEditMesh *em_original = vc.em; + const short selectmode = em_original->selectmode; + em_original->selectmode = SCE_SELECT_EDGE; + + uint bases_len; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + + { + int base_index = -1; + if (EDBM_unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa)) { + basact = bases[base_index]; + ED_view3d_viewcontext_init_object(&vc, basact->object); + em = vc.em; + } + else { + em = nullptr; + } + } + + em_original->selectmode = selectmode; + + if (em == nullptr || eed == nullptr) { + MEM_freeN(bases); + return false; + } + + if (extend == false && deselect == false && toggle == false) { + select_clear = true; + } + + if (extend) { + select = true; + } + else if (deselect) { + select = false; + } + else if (select_clear || (BM_elem_flag_test(eed, BM_ELEM_SELECT) == 0)) { + select = true; + } + else if (toggle) { + select = false; + select_cycle = false; + } + + if (select_clear) { + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (em_iter->bm->totvertsel == 0) { + continue; + } + + if (em_iter == em) { + continue; + } + + EDBM_flag_disable_all(em_iter, BM_ELEM_SELECT); + DEG_id_tag_update(static_cast(ob_iter->data), ID_RECALC_SELECT); + } + } + + if (em->selectmode & SCE_SELECT_FACE) { + mouse_mesh_loop_face(em, eed, select, select_clear); + } + else { + if (ring) { + mouse_mesh_loop_edge_ring(em, eed, select, select_clear); + } + else { + mouse_mesh_loop_edge(em, eed, select, select_clear, select_cycle); + } + } + + EDBM_selectmode_flush(em); + + /* sets as active, useful for other tools */ + if (select) { + if (em->selectmode & SCE_SELECT_VERTEX) { + /* Find nearest vert from mouse + * (initialize to large values in case only one vertex can be projected) */ + float v1_co[2], v2_co[2]; + float length_1 = FLT_MAX; + float length_2 = FLT_MAX; + + /* We can't be sure this has already been set... */ + ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); + + if (ED_view3d_project_float_object(vc.region, eed->v1->co, v1_co, V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) { + length_1 = len_squared_v2v2(mvalf, v1_co); + } + + if (ED_view3d_project_float_object(vc.region, eed->v2->co, v2_co, V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) { + length_2 = len_squared_v2v2(mvalf, v2_co); + } +#if 0 + printf("mouse to v1: %f\nmouse to v2: %f\n", + len_squared_v2v2(mvalf, v1_co), + len_squared_v2v2(mvalf, v2_co)); +#endif + BM_select_history_store(em->bm, (length_1 < length_2) ? eed->v1 : eed->v2); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BM_select_history_store(em->bm, eed); + } + else if (em->selectmode & SCE_SELECT_FACE) { + /* Select the face of eed which is the nearest of mouse. */ + BMFace *f; + BMIter iterf; + float best_dist = FLT_MAX; + efa = nullptr; + + /* We can't be sure this has already been set... */ + ED_view3d_init_mats_rv3d(vc.obedit, vc.rv3d); + + BM_ITER_ELEM (f, &iterf, eed, BM_FACES_OF_EDGE) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + float cent[3]; + float co[2], tdist; + + BM_face_calc_center_median(f, cent); + if (ED_view3d_project_float_object(vc.region, cent, co, V3D_PROJ_TEST_CLIP_NEAR) == + V3D_PROJ_RET_OK) { + tdist = len_squared_v2v2(mvalf, co); + if (tdist < best_dist) { + // printf("Best face: %p (%f)\n", f, tdist); + best_dist = tdist; + efa = f; + } + } + } + } + if (efa) { + BM_mesh_active_face_set(em->bm, efa); + BM_select_history_store(em->bm, efa); + } + } + } + + MEM_freeN(bases); + + DEG_id_tag_update(static_cast(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + + return true; +} + +static int edbm_select_loop_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + + view3d_operator_needs_opengl(C); + + if (mouse_mesh_loop(C, + event->mval, + RNA_boolean_get(op->ptr, "extend"), + RNA_boolean_get(op->ptr, "deselect"), + RNA_boolean_get(op->ptr, "toggle"), + RNA_boolean_get(op->ptr, "ring"))) { + return OPERATOR_FINISHED; + } + return OPERATOR_CANCELLED; +} + +void MESH_OT_loop_select(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Loop Select"; + ot->idname = "MESH_OT_loop_select"; + ot->description = "Select a loop of connected edges"; + + /* api callbacks */ + ot->invoke = edbm_select_loop_invoke; + ot->poll = ED_operator_editmesh_region_view3d; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* properties */ + PropertyRNA *prop; + + prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend Select", "Extend the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "ring", 0, "Select Ring", "Select ring"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +void MESH_OT_edgering_select(wmOperatorType *ot) +{ + /* description */ + ot->name = "Edge Ring Select"; + ot->idname = "MESH_OT_edgering_select"; + ot->description = "Select an edge ring"; + + /* callbacks */ + ot->invoke = edbm_select_loop_invoke; + ot->poll = ED_operator_editmesh_region_view3d; + + /* flags */ + ot->flag = OPTYPE_UNDO; + + /* Properties. */ + PropertyRNA *prop; + prop = RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", "Remove from the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "toggle", 0, "Toggle Select", "Toggle the selection"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); + prop = RNA_def_boolean(ot->srna, "ring", 1, "Select Ring", "Select ring"); + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name (De)Select All Operator + * \{ */ + +static int edbm_select_all_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + int action = RNA_enum_get(op->ptr, "action"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + if (action == SEL_TOGGLE) { + action = SEL_SELECT; + 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->totvertsel || em->bm->totedgesel || em->bm->totfacesel) { + action = SEL_DESELECT; + break; + } + } + } + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit = objects[ob_index]; + BMEditMesh *em = BKE_editmesh_from_object(obedit); + switch (action) { + case SEL_SELECT: + EDBM_flag_enable_all(em, BM_ELEM_SELECT); + break; + case SEL_DESELECT: + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + break; + case SEL_INVERT: + EDBM_select_swap(em); + EDBM_selectmode_flush(em); + break; + } + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_all(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "(De)select All"; + ot->idname = "MESH_OT_select_all"; + ot->description = "(De)select all vertices, edges or faces"; + + /* api callbacks */ + ot->exec = edbm_select_all_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + WM_operator_properties_select_all(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Interior Faces Operator + * \{ */ + +static int edbm_faces_select_interior_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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 (!EDBM_select_interior_faces(em)) { + continue; + } + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_interior_faces(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Interior Faces"; + ot->idname = "MESH_OT_select_interior_faces"; + ot->description = "Select faces where all edges have more than 2 face users"; + + /* api callbacks */ + ot->exec = edbm_faces_select_interior_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Picking API + * + * Here actual select happens, + * Gets called via generic mouse select operator. + * \{ */ + +bool EDBM_select_pick(bContext *C, const int mval[2], const SelectPick_Params *params) +{ + ViewContext vc; + + int base_index_active = -1; + BMVert *eve = nullptr; + BMEdge *eed = nullptr; + BMFace *efa = nullptr; + + /* setup view context for argument to callbacks */ + em_setup_viewcontext(C, &vc); + vc.mval[0] = mval[0]; + vc.mval[1] = mval[1]; + + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + + bool changed = false; + bool found = unified_findnearest(&vc, bases, bases_len, &base_index_active, &eve, &eed, &efa); + + if (params->sel_op == SEL_OP_SET) { + BMElem *ele = efa ? (BMElem *)efa : (eed ? (BMElem *)eed : (BMElem *)eve); + if ((found && params->select_passthrough) && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { + found = false; + } + else if (found || params->deselect_all) { + /* Deselect everything. */ + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + EDBM_flag_disable_all(BKE_editmesh_from_object(ob_iter), BM_ELEM_SELECT); + DEG_id_tag_update(static_cast(ob_iter->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); + } + changed = true; + } + } + + if (found) { + Base *basact = bases[base_index_active]; + ED_view3d_viewcontext_init_object(&vc, basact->object); + + if (efa) { + switch (params->sel_op) { + case SEL_OP_ADD: { + BM_mesh_active_face_set(vc.em->bm, efa); + + /* Work-around: deselect first, so we can guarantee it will + * be active even if it was already selected. */ + BM_select_history_remove(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, false); + BM_select_history_store(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, true); + break; + } + case SEL_OP_SUB: { + BM_select_history_remove(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, false); + break; + } + case SEL_OP_XOR: { + BM_mesh_active_face_set(vc.em->bm, efa); + if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, true); + } + else { + BM_select_history_remove(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, false); + } + break; + } + case SEL_OP_SET: { + BM_mesh_active_face_set(vc.em->bm, efa); + if (!BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, efa); + BM_face_select_set(vc.em->bm, efa, true); + } + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + } + else if (eed) { + + switch (params->sel_op) { + case SEL_OP_ADD: { + /* Work-around: deselect first, so we can guarantee it will + * be active even if it was already selected. */ + BM_select_history_remove(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, false); + BM_select_history_store(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, true); + break; + } + case SEL_OP_SUB: { + BM_select_history_remove(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, false); + break; + } + case SEL_OP_XOR: { + if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, true); + } + else { + BM_select_history_remove(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, false); + } + break; + } + case SEL_OP_SET: { + if (!BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eed); + BM_edge_select_set(vc.em->bm, eed, true); + } + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + } + else if (eve) { + switch (params->sel_op) { + case SEL_OP_ADD: { + /* Work-around: deselect first, so we can guarantee it will + * be active even if it was already selected. */ + BM_select_history_remove(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, false); + BM_select_history_store(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, true); + break; + } + case SEL_OP_SUB: { + BM_select_history_remove(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, false); + break; + } + case SEL_OP_XOR: { + if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, true); + } + else { + BM_select_history_remove(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, false); + } + break; + } + case SEL_OP_SET: { + if (!BM_elem_flag_test(eve, BM_ELEM_SELECT)) { + BM_select_history_store(vc.em->bm, eve); + BM_vert_select_set(vc.em->bm, eve, true); + } + break; + } + case SEL_OP_AND: { + BLI_assert_unreachable(); /* Doesn't make sense for picking. */ + break; + } + } + } + + EDBM_selectmode_flush(vc.em); + + if (efa) { + /* Change active material on object. */ + if (efa->mat_nr != vc.obedit->actcol - 1) { + vc.obedit->actcol = efa->mat_nr + 1; + vc.em->mat_nr = efa->mat_nr; + WM_event_add_notifier(C, NC_MATERIAL | ND_SHADING_LINKS, nullptr); + } + + /* Change active face-map on object. */ + if (!BLI_listbase_is_empty(&vc.obedit->fmaps)) { + const int cd_fmap_offset = CustomData_get_offset(&vc.em->bm->pdata, CD_FACEMAP); + if (cd_fmap_offset != -1) { + int map = *((int *)BM_ELEM_CD_GET_VOID_P(efa, cd_fmap_offset)); + if ((map < -1) || (map > BLI_listbase_count_at_most(&vc.obedit->fmaps, map))) { + map = -1; + } + map += 1; + if (map != vc.obedit->actfmap) { + /* We may want to add notifiers later, + * currently select update handles redraw. */ + vc.obedit->actfmap = map; + } + } + } + } + + /* Changing active object is handy since it allows us to + * switch UV layers, vgroups for eg. */ + BKE_view_layer_synced_ensure(vc.scene, vc.view_layer); + if (BKE_view_layer_active_base_get(vc.view_layer) != basact) { + ED_object_base_activate(C, basact); + } + + DEG_id_tag_update(static_cast(vc.obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, vc.obedit->data); + + changed = true; + } + + MEM_freeN(bases); + + return changed; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mode Utilities + * \{ */ + +static void edbm_strip_selections(BMEditMesh *em) +{ + BMEditSelection *ese, *nextese; + + if (!(em->selectmode & SCE_SELECT_VERTEX)) { + ese = static_cast(em->bm->selected.first); + while (ese) { + nextese = ese->next; + if (ese->htype == BM_VERT) { + BLI_freelinkN(&(em->bm->selected), ese); + } + ese = nextese; + } + } + if (!(em->selectmode & SCE_SELECT_EDGE)) { + ese = static_cast(em->bm->selected.first); + while (ese) { + nextese = ese->next; + if (ese->htype == BM_EDGE) { + BLI_freelinkN(&(em->bm->selected), ese); + } + ese = nextese; + } + } + if (!(em->selectmode & SCE_SELECT_FACE)) { + ese = static_cast(em->bm->selected.first); + while (ese) { + nextese = ese->next; + if (ese->htype == BM_FACE) { + BLI_freelinkN(&(em->bm->selected), ese); + } + ese = nextese; + } + } +} + +void EDBM_selectmode_set(BMEditMesh *em) +{ + BMVert *eve; + BMEdge *eed; + BMFace *efa; + BMIter iter; + + em->bm->selectmode = em->selectmode; + + /* strip BMEditSelections from em->selected that are not relevant to new mode */ + edbm_strip_selections(em); + + if (em->bm->totvertsel == 0 && em->bm->totedgesel == 0 && em->bm->totfacesel == 0) { + return; + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + if (em->bm->totvertsel) { + EDBM_select_flush(em); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + /* deselect vertices, and select again based on edge select */ + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + BM_vert_select_set(em->bm, eve, false); + } + + if (em->bm->totedgesel) { + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_SELECT)) { + BM_edge_select_set(em->bm, eed, true); + } + } + + /* selects faces based on edge status */ + EDBM_selectmode_flush(em); + } + } + else if (em->selectmode & SCE_SELECT_FACE) { + /* Deselect edges, and select again based on face select. */ + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + BM_edge_select_set(em->bm, eed, false); + } + + if (em->bm->totfacesel) { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_SELECT)) { + BM_face_select_set(em->bm, efa, true); + } + } + } + } +} + +void EDBM_selectmode_convert(BMEditMesh *em, + const short selectmode_old, + const short selectmode_new) +{ + BMesh *bm = em->bm; + + BMVert *eve; + BMEdge *eed; + BMFace *efa; + BMIter iter; + + /* first tag-to-select, then select --- this avoids a feedback loop */ + + /* Have to find out what the selection-mode was previously. */ + if (selectmode_old == SCE_SELECT_VERTEX) { + if (bm->totvertsel == 0) { + /* pass */ + } + else if (selectmode_new == SCE_SELECT_EDGE) { + /* flush up (vert -> edge) */ + + /* select all edges associated with every selected vert */ + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + BM_elem_flag_set(eed, BM_ELEM_TAG, BM_edge_is_any_vert_flag_test(eed, BM_ELEM_SELECT)); + } + + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_TAG)) { + BM_edge_select_set(bm, eed, true); + } + } + } + else if (selectmode_new == SCE_SELECT_FACE) { + /* flush up (vert -> face) */ + + /* select all faces associated with every selected vert */ + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(efa, BM_ELEM_TAG, BM_face_is_any_vert_flag_test(efa, BM_ELEM_SELECT)); + } + + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { + BM_face_select_set(bm, efa, true); + } + } + } + } + else if (selectmode_old == SCE_SELECT_EDGE) { + if (bm->totedgesel == 0) { + /* pass */ + } + else if (selectmode_new == SCE_SELECT_FACE) { + /* flush up (edge -> face) */ + + /* select all faces associated with every selected edge */ + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(efa, BM_ELEM_TAG, BM_face_is_any_edge_flag_test(efa, BM_ELEM_SELECT)); + } + + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_TAG)) { + BM_face_select_set(bm, efa, true); + } + } + } + else if (selectmode_new == SCE_SELECT_VERTEX) { + /* flush down (edge -> vert) */ + + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_vert_is_all_edge_flag_test(eve, BM_ELEM_SELECT, true)) { + BM_vert_select_set(bm, eve, false); + } + } + /* deselect edges without both verts selected */ + BM_mesh_deselect_flush(bm); + } + } + else if (selectmode_old == SCE_SELECT_FACE) { + if (bm->totfacesel == 0) { + /* pass */ + } + else if (selectmode_new == SCE_SELECT_EDGE) { + /* flush down (face -> edge) */ + + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + if (!BM_edge_is_all_face_flag_test(eed, BM_ELEM_SELECT, true)) { + BM_edge_select_set(bm, eed, false); + } + } + /* Deselect faces without edges selected. */ + BM_mesh_deselect_flush(bm); + } + else if (selectmode_new == SCE_SELECT_VERTEX) { + /* flush down (face -> vert) */ + + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_vert_is_all_face_flag_test(eve, BM_ELEM_SELECT, true)) { + BM_vert_select_set(bm, eve, false); + } + } + /* deselect faces without verts selected */ + BM_mesh_deselect_flush(bm); + } + } +} + +bool EDBM_selectmode_toggle_multi(bContext *C, + const short selectmode_new, + const int action, + const bool use_extend, + const bool use_expand) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + ToolSettings *ts = CTX_data_tool_settings(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = nullptr; + bool ret = false; + + if (obedit && obedit->type == OB_MESH) { + em = BKE_editmesh_from_object(obedit); + } + + if (em == nullptr) { + return ret; + } + + bool only_update = false; + switch (action) { + case -1: + /* already set */ + break; + case 0: /* disable */ + /* check we have something to do */ + if ((em->selectmode & selectmode_new) == 0) { + only_update = true; + break; + } + em->selectmode &= ~selectmode_new; + break; + case 1: /* enable */ + /* check we have something to do */ + if ((em->selectmode & selectmode_new) != 0) { + only_update = true; + break; + } + em->selectmode |= selectmode_new; + break; + case 2: /* toggle */ + /* can't disable this flag if its the only one set */ + if (em->selectmode == selectmode_new) { + only_update = true; + break; + } + em->selectmode ^= selectmode_new; + break; + default: + BLI_assert(0); + break; + } + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + if (em_iter != em) { + em_iter->selectmode = em->selectmode; + } + } + + if (only_update) { + MEM_freeN(objects); + return false; + } + + if (use_extend == 0 || em->selectmode == 0) { + if (use_expand) { + const short selmode_max = highest_order_bit_s(ts->selectmode); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + EDBM_selectmode_convert(em_iter, selmode_max, selectmode_new); + } + } + } + + switch (selectmode_new) { + case SCE_SELECT_VERTEX: + if (use_extend == 0 || em->selectmode == 0) { + em->selectmode = SCE_SELECT_VERTEX; + } + ret = true; + break; + case SCE_SELECT_EDGE: + if (use_extend == 0 || em->selectmode == 0) { + em->selectmode = SCE_SELECT_EDGE; + } + ret = true; + break; + case SCE_SELECT_FACE: + if (use_extend == 0 || em->selectmode == 0) { + em->selectmode = SCE_SELECT_FACE; + } + ret = true; + break; + default: + BLI_assert(0); + break; + } + + if (ret == true) { + ts->selectmode = em->selectmode; + em = nullptr; + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + em_iter->selectmode = ts->selectmode; + EDBM_selectmode_set(em_iter); + DEG_id_tag_update(static_cast(ob_iter->data), + ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); + } + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + } + + MEM_freeN(objects); + return ret; +} + +bool EDBM_selectmode_set_multi(bContext *C, const short selectmode) +{ + BLI_assert(selectmode != 0); + bool changed = false; + + { + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = nullptr; + if (obedit && obedit->type == OB_MESH) { + em = BKE_editmesh_from_object(obedit); + } + if (em == nullptr) { + return changed; + } + } + + ViewLayer *view_layer = CTX_data_view_layer(C); + Scene *scene = CTX_data_scene(C); + ToolSettings *ts = scene->toolsettings; + + if (ts->selectmode != selectmode) { + ts->selectmode = selectmode; + changed = true; + } + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *ob_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + if (em_iter->selectmode != ts->selectmode) { + em_iter->selectmode = ts->selectmode; + EDBM_selectmode_set(em_iter); + DEG_id_tag_update(static_cast(ob_iter->data), + ID_RECALC_COPY_ON_WRITE | ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, ob_iter->data); + changed = true; + } + } + MEM_freeN(objects); + + if (changed) { + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, nullptr); + DEG_id_tag_update(&scene->id, ID_RECALC_COPY_ON_WRITE); + } + return changed; +} + +bool EDBM_selectmode_disable(Scene *scene, + BMEditMesh *em, + const short selectmode_disable, + const short selectmode_fallback) +{ + /* note essential, but switch out of vertex mode since the + * selected regions won't be nicely isolated after flushing */ + if (em->selectmode & selectmode_disable) { + if (em->selectmode == selectmode_disable) { + em->selectmode = selectmode_fallback; + } + else { + em->selectmode &= ~selectmode_disable; + } + scene->toolsettings->selectmode = em->selectmode; + EDBM_selectmode_set(em); + + WM_main_add_notifier(NC_SCENE | ND_TOOLSETTINGS, scene); + + return true; + } + return false; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Toggle + * \{ */ + +bool EDBM_deselect_by_material(BMEditMesh *em, const short index, const bool select) +{ + BMIter iter; + BMFace *efa; + bool changed = false; + + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + if (efa->mat_nr == index) { + changed = true; + BM_face_select_set(em->bm, efa, select); + } + } + return changed; +} + +void EDBM_select_toggle_all(BMEditMesh *em) /* exported for UV */ +{ + if (em->bm->totvertsel || em->bm->totedgesel || em->bm->totfacesel) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + else { + EDBM_flag_enable_all(em, BM_ELEM_SELECT); + } +} + +void EDBM_select_swap(BMEditMesh *em) /* exported for UV */ +{ + BMIter iter; + BMVert *eve; + BMEdge *eed; + BMFace *efa; + + if (em->bm->selectmode & SCE_SELECT_VERTEX) { + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + continue; + } + BM_vert_select_set(em->bm, eve, !BM_elem_flag_test(eve, BM_ELEM_SELECT)); + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { + continue; + } + BM_edge_select_set(em->bm, eed, !BM_elem_flag_test(eed, BM_ELEM_SELECT)); + } + } + else { + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + continue; + } + BM_face_select_set(em->bm, efa, !BM_elem_flag_test(efa, BM_ELEM_SELECT)); + } + } +} + +bool EDBM_mesh_deselect_all_multi_ex(Base **bases, const uint bases_len) +{ + bool changed_multi = false; + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (em_iter->bm->totvertsel == 0) { + continue; + } + + EDBM_flag_disable_all(em_iter, BM_ELEM_SELECT); + DEG_id_tag_update(static_cast(ob_iter->data), ID_RECALC_SELECT); + changed_multi = true; + } + return changed_multi; +} + +bool EDBM_mesh_deselect_all_multi(bContext *C) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + bool changed_multi = EDBM_mesh_deselect_all_multi_ex(bases, bases_len); + MEM_freeN(bases); + return changed_multi; +} + +bool EDBM_selectmode_disable_multi_ex(Scene *scene, + Base **bases, + const uint bases_len, + const short selectmode_disable, + const short selectmode_fallback) +{ + bool changed_multi = false; + for (uint base_index = 0; base_index < bases_len; base_index++) { + Base *base_iter = bases[base_index]; + Object *ob_iter = base_iter->object; + BMEditMesh *em_iter = BKE_editmesh_from_object(ob_iter); + + if (EDBM_selectmode_disable(scene, em_iter, selectmode_disable, selectmode_fallback)) { + changed_multi = true; + } + } + return changed_multi; +} + +bool EDBM_selectmode_disable_multi(bContext *C, + const short selectmode_disable, + const short selectmode_fallback) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + Scene *scene = CTX_data_scene(C); + ViewContext vc; + ED_view3d_viewcontext_init(C, &vc, depsgraph); + uint bases_len = 0; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode_unique_data( + vc.scene, vc.view_layer, nullptr, &bases_len); + bool changed_multi = EDBM_selectmode_disable_multi_ex( + scene, bases, bases_len, selectmode_disable, selectmode_fallback); + MEM_freeN(bases); + return changed_multi; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Interior Faces + * + * Overview of the algorithm: + * - Groups faces surrounded by edges with 3+ faces using them. + * - Calculates a cost of each face group comparing its angle with the faces + * connected to its non-manifold edges. + * - Mark the face group as interior, and mark connected face groups for recalculation. + * - Continue to remove the face groups with the highest 'cost'. + * + * \{ */ + +struct BMFaceLink { + struct BMFaceLink *next, *prev; + BMFace *face; + float area; +}; + +static bool bm_interior_loop_filter_fn(const BMLoop *l, void *UNUSED(user_data)) +{ + if (BM_elem_flag_test(l->e, BM_ELEM_TAG)) { + return false; + } + return true; +} +static bool bm_interior_edge_is_manifold_except_face_index(BMEdge *e, + int face_index, + BMLoop *r_l_pair[2]) +{ + + BMLoop *l_iter = e->l; + int loop_index = 0; + do { + BMFace *f = l_iter->f; + int i = BM_elem_index_get(f); + if (!ELEM(i, -1, face_index)) { + if (loop_index == 2) { + return false; + } + r_l_pair[loop_index++] = l_iter; + } + } while ((l_iter = l_iter->radial_next) != e->l); + return (loop_index == 2); +} + +/** + * Calculate the cost of the face group. + * A higher value means it's more likely to remove first. + */ +static float bm_interior_face_group_calc_cost(ListBase *ls, const float *edge_lengths) +{ + /* Dividing by the area is important so larger face groups (which will become the outer shell) + * aren't detected as having a high cost. */ + float area = 0.0f; + float cost = 0.0f; + bool found = false; + LISTBASE_FOREACH (BMFaceLink *, f_link, ls) { + BMFace *f = f_link->face; + area += f_link->area; + int i = BM_elem_index_get(f); + BLI_assert(i != -1); + BMLoop *l_iter, *l_first; + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_iter->e, BM_ELEM_TAG)) { + float cost_test = 0.0f; + int cost_count = 0; + /* All other faces. */ + BMLoop *l_radial_iter = l_iter; + do { + int i_other = BM_elem_index_get(l_radial_iter->f); + if (!ELEM(i_other, -1, i)) { + float angle = angle_normalized_v3v3(f->no, l_radial_iter->f->no); + /* Ignore face direction since in the case on non-manifold faces connecting edges, + * the face flipping may not be meaningful. */ + if (angle > DEG2RADF(90)) { + angle = DEG2RADF(180) - angle; + } + /* Avoid calculating it inline, pass in pre-calculated edge lengths. */ +#if 0 + cost_test += BM_edge_calc_length(l_iter->e) * angle; +#else + BLI_assert(edge_lengths[BM_elem_index_get(l_iter->e)] != -1.0f); + cost_test += edge_lengths[BM_elem_index_get(l_iter->e)] * angle; +#endif + cost_count += 1; + } + } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); + + if (cost_count >= 2) { + cost += cost_test; + found = true; + } + } + } while ((l_iter = l_iter->next) != l_first); + } + return found ? cost / area : FLT_MAX; +} + +bool EDBM_select_interior_faces(BMEditMesh *em) +{ + BMesh *bm = em->bm; + BMIter iter; + bool changed = false; + + float *edge_lengths = static_cast( + MEM_mallocN(sizeof(*edge_lengths) * bm->totedge, __func__)); + + { + bool has_nonmanifold = false; + BMEdge *e; + int i; + BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) { + const bool is_over = BM_edge_face_count_is_over(e, 2); + if (is_over) { + BM_elem_flag_enable(e, BM_ELEM_TAG); + has_nonmanifold = true; + edge_lengths[i] = BM_edge_calc_length(e); + } + else { + BM_elem_flag_disable(e, BM_ELEM_TAG); + edge_lengths[i] = -1.0; + } + + BM_elem_index_set(e, i); /* set_inline */ + } + bm->elem_index_dirty &= ~BM_EDGE; + + if (has_nonmanifold == false) { + MEM_freeN(edge_lengths); + return false; + } + } + + /* group vars */ + int(*fgroup_index)[2]; + int fgroup_len; + + int *fgroup_array = static_cast( + MEM_mallocN(sizeof(*fgroup_array) * bm->totface, __func__)); + fgroup_len = BM_mesh_calc_face_groups( + bm, fgroup_array, &fgroup_index, bm_interior_loop_filter_fn, nullptr, nullptr, 0, BM_EDGE); + + int *fgroup_recalc_stack = static_cast( + MEM_mallocN(sizeof(*fgroup_recalc_stack) * fgroup_len, __func__)); + STACK_DECLARE(fgroup_recalc_stack); + STACK_INIT(fgroup_recalc_stack, fgroup_len); + + BM_mesh_elem_table_ensure(bm, BM_FACE); + + { + BMFace *f; + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BM_elem_index_set(f, -1); /* set_dirty! */ + } + } + bm->elem_index_dirty |= BM_FACE; + + ListBase *fgroup_listbase = static_cast( + MEM_callocN(sizeof(*fgroup_listbase) * fgroup_len, __func__)); + BMFaceLink *f_link_array = static_cast( + MEM_callocN(sizeof(*f_link_array) * bm->totface, __func__)); + + for (int i = 0; i < fgroup_len; i++) { + const int fg_sta = fgroup_index[i][0]; + const int fg_len = fgroup_index[i][1]; + for (int j = 0; j < fg_len; j++) { + const int face_index = fgroup_array[fg_sta + j]; + BMFace *f = BM_face_at_index(bm, face_index); + BM_elem_index_set(f, i); + + BMFaceLink *f_link = &f_link_array[face_index]; + f_link->face = f; + f_link->area = BM_face_calc_area(f); + BLI_addtail(&fgroup_listbase[i], f_link); + } + } + + MEM_freeN(fgroup_array); + MEM_freeN(fgroup_index); + + Heap *fgroup_heap = BLI_heap_new_ex(fgroup_len); + HeapNode **fgroup_table = static_cast( + MEM_mallocN(sizeof(*fgroup_table) * fgroup_len, __func__)); + bool *fgroup_dirty = static_cast( + MEM_callocN(sizeof(*fgroup_dirty) * fgroup_len, __func__)); + + for (int i = 0; i < fgroup_len; i++) { + const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); + if (cost != FLT_MAX) { + fgroup_table[i] = BLI_heap_insert(fgroup_heap, -cost, POINTER_FROM_INT(i)); + } + else { + fgroup_table[i] = nullptr; + } + } + + /* Avoid re-running cost calculations for large face-groups which will end up forming the + * outer shell and not be considered interior. + * As these face groups become increasingly bigger - their chance of being considered + * interior reduces as does the time to calculate their cost. + * + * This delays recalculating them until they are considered can dates to remove + * which becomes less and less likely as they increase in area. */ + +#define USE_DELAY_FACE_GROUP_COST_CALC + + while (true) { + +#if defined(USE_DELAY_FACE_GROUP_COST_CALC) + while (!BLI_heap_is_empty(fgroup_heap)) { + HeapNode *node_min = BLI_heap_top(fgroup_heap); + const int i = POINTER_AS_INT(BLI_heap_node_ptr(node_min)); + if (fgroup_dirty[i]) { + const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); + if (cost != FLT_MAX) { + /* The cost may have improves (we may be able to skip this), + * however the cost should _never_ make this a choice. */ + BLI_assert(-BLI_heap_node_value(node_min) >= cost); + BLI_heap_node_value_update(fgroup_heap, fgroup_table[i], -cost); + } + else { + BLI_heap_remove(fgroup_heap, fgroup_table[i]); + fgroup_table[i] = nullptr; + } + fgroup_dirty[i] = false; + } + else { + break; + } + } +#endif + + if (BLI_heap_is_empty(fgroup_heap)) { + break; + } + + const int i_min = POINTER_AS_INT(BLI_heap_pop_min(fgroup_heap)); + BLI_assert(fgroup_table[i_min] != nullptr); + BLI_assert(fgroup_dirty[i_min] == false); + fgroup_table[i_min] = nullptr; + changed = true; + + BMFaceLink *f_link; + while ((f_link = static_cast(BLI_pophead(&fgroup_listbase[i_min])))) { + BMFace *f = f_link->face; + BM_face_select_set(bm, f, true); + BM_elem_index_set(f, -1); /* set-dirty */ + + BMLoop *l_iter, *l_first; + + /* Loop over edges face edges, merging groups which are no longer separated + * by non-manifold edges (when manifold check ignores faces from this group). */ + l_iter = l_first = BM_FACE_FIRST_LOOP(f); + do { + BMLoop *l_pair[2]; + if (bm_interior_edge_is_manifold_except_face_index(l_iter->e, i_min, l_pair)) { + BM_elem_flag_disable(l_iter->e, BM_ELEM_TAG); + + int i_a = BM_elem_index_get(l_pair[0]->f); + int i_b = BM_elem_index_get(l_pair[1]->f); + if (i_a != i_b) { + /* Only for predictable results that don't depend on the order of radial loops, + * not essential. */ + if (i_a > i_b) { + SWAP(int, i_a, i_b); + } + + /* Merge the groups. */ + LISTBASE_FOREACH (LinkData *, n, &fgroup_listbase[i_b]) { + BMFace *f_iter = static_cast(n->data); + BM_elem_index_set(f_iter, i_a); + } + BLI_movelisttolist(&fgroup_listbase[i_a], &fgroup_listbase[i_b]); + + /* This may have been added to 'fgroup_recalc_stack', instead of removing it, + * just check the heap node isn't nullptr before recalculating. */ + BLI_heap_remove(fgroup_heap, fgroup_table[i_b]); + fgroup_table[i_b] = nullptr; + /* Keep the dirty flag as-is for 'i_b', because it may be in the 'fgroup_recalc_stack' + * and we don't want to add it again. + * Instead rely on the 'fgroup_table[i_b]' being nullptr as a secondary check. */ + + if (fgroup_dirty[i_a] == false) { + BLI_assert(fgroup_table[i_a] != nullptr); + STACK_PUSH(fgroup_recalc_stack, i_a); + fgroup_dirty[i_a] = true; + } + } + } + + /* Mark all connected groups for re-calculation. */ + BMLoop *l_radial_iter = l_iter->radial_next; + if (l_radial_iter != l_iter) { + do { + int i_other = BM_elem_index_get(l_radial_iter->f); + if (!ELEM(i_other, -1, i_min)) { + if ((fgroup_table[i_other] != nullptr) && (fgroup_dirty[i_other] == false)) { +#if !defined(USE_DELAY_FACE_GROUP_COST_CALC) + STACK_PUSH(fgroup_recalc_stack, i_other); +#endif + fgroup_dirty[i_other] = true; + } + } + } while ((l_radial_iter = l_radial_iter->radial_next) != l_iter); + } + + } while ((l_iter = l_iter->next) != l_first); + } + + for (int index = 0; index < STACK_SIZE(fgroup_recalc_stack); index++) { + const int i = fgroup_recalc_stack[index]; + if (fgroup_table[i] != nullptr && fgroup_dirty[i] == true) { + /* First update edge tags. */ + const float cost = bm_interior_face_group_calc_cost(&fgroup_listbase[i], edge_lengths); + if (cost != FLT_MAX) { + BLI_heap_node_value_update(fgroup_heap, fgroup_table[i], -cost); + } + else { + BLI_heap_remove(fgroup_heap, fgroup_table[i]); + fgroup_table[i] = nullptr; + } + } + fgroup_dirty[i] = false; + } + STACK_CLEAR(fgroup_recalc_stack); + } + + MEM_freeN(edge_lengths); + MEM_freeN(f_link_array); + MEM_freeN(fgroup_listbase); + MEM_freeN(fgroup_recalc_stack); + MEM_freeN(fgroup_table); + MEM_freeN(fgroup_dirty); + + BLI_heap_free(fgroup_heap, nullptr); + + return changed; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Linked Operator + * + * Support delimiting on different edge properties. + * \{ */ + +/* so we can have last-used default depend on selection mode (rare exception!) */ +#define USE_LINKED_SELECT_DEFAULT_HACK + +struct DelimitData { + int cd_loop_type; + int cd_loop_offset; +}; + +static bool select_linked_delimit_test(BMEdge *e, int delimit, const DelimitData *delimit_data) +{ + BLI_assert(delimit); + + if (delimit & BMO_DELIM_SEAM) { + if (BM_elem_flag_test(e, BM_ELEM_SEAM)) { + return true; + } + } + + if (delimit & BMO_DELIM_SHARP) { + if (BM_elem_flag_test(e, BM_ELEM_SMOOTH) == 0) { + return true; + } + } + + if (delimit & BMO_DELIM_NORMAL) { + if (!BM_edge_is_contiguous(e)) { + return true; + } + } + + if (delimit & BMO_DELIM_MATERIAL) { + if (e->l && e->l->radial_next != e->l) { + const short mat_nr = e->l->f->mat_nr; + BMLoop *l_iter = e->l->radial_next; + do { + if (l_iter->f->mat_nr != mat_nr) { + return true; + } + } while ((l_iter = l_iter->radial_next) != e->l); + } + } + + if (delimit & BMO_DELIM_UV) { + if (BM_edge_is_contiguous_loop_cd( + e, delimit_data->cd_loop_type, delimit_data->cd_loop_offset) == 0) { + return true; + } + } + + return false; +} + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK +/** + * Gets the default from the operator fallback to own last-used value + * (selected based on mode) + */ +static int select_linked_delimit_default_from_op(wmOperator *op, const int select_mode) +{ + static char delimit_last_store[2] = {0, BMO_DELIM_SEAM}; + int delimit_last_index = (select_mode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0; + char *delimit_last = &delimit_last_store[delimit_last_index]; + PropertyRNA *prop_delimit = RNA_struct_find_property(op->ptr, "delimit"); + int delimit; + + if (RNA_property_is_set(op->ptr, prop_delimit)) { + delimit = RNA_property_enum_get(op->ptr, prop_delimit); + *delimit_last = delimit; + } + else { + delimit = *delimit_last; + RNA_property_enum_set(op->ptr, prop_delimit, delimit); + } + return delimit; +} +#endif + +static void select_linked_delimit_validate(BMesh *bm, int *delimit) +{ + if ((*delimit) & BMO_DELIM_UV) { + if (!CustomData_has_layer(&bm->ldata, CD_MLOOPUV)) { + (*delimit) &= ~BMO_DELIM_UV; + } + } +} + +static void select_linked_delimit_begin(BMesh *bm, int delimit) +{ + DelimitData delimit_data = {0}; + + if (delimit & BMO_DELIM_UV) { + delimit_data.cd_loop_type = CD_MLOOPUV; + delimit_data.cd_loop_offset = CustomData_get_offset(&bm->ldata, delimit_data.cd_loop_type); + if (delimit_data.cd_loop_offset == -1) { + delimit &= ~BMO_DELIM_UV; + } + } + + /* grr, shouldn't need to alloc BMO flags here */ + BM_mesh_elem_toolflags_ensure(bm); + + { + BMIter iter; + BMEdge *e; + + BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) { + const bool is_walk_ok = (select_linked_delimit_test(e, delimit, &delimit_data) == false); + + BMO_edge_flag_set(bm, e, BMO_ELE_TAG, is_walk_ok); + } + } +} + +static void select_linked_delimit_end(BMEditMesh *em) +{ + BMesh *bm = em->bm; + + BM_mesh_elem_toolflags_clear(bm); +} + +static int edbm_select_linked_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + const int delimit_init = select_linked_delimit_default_from_op(op, + scene->toolsettings->selectmode); +#else + const int delimit_init = RNA_enum_get(op->ptr, "delimit"); +#endif + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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; + BMIter iter; + BMWalker walker; + + int delimit = delimit_init; + + select_linked_delimit_validate(bm, &delimit); + + if (delimit) { + select_linked_delimit_begin(em->bm, delimit); + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + BM_elem_flag_set(v, BM_ELEM_TAG, BM_elem_flag_test(v, BM_ELEM_SELECT)); + } + + /* exclude all delimited verts */ + if (delimit) { + BMEdge *e; + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BMO_edge_flag_test(bm, e, BMO_ELE_TAG)) { + /* Check the edge for selected faces, + * this supports stepping off isolated vertices which would otherwise be ignored. */ + if (BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)) { + BM_elem_flag_disable(e->v1, BM_ELEM_TAG); + BM_elem_flag_disable(e->v2, BM_ELEM_TAG); + } + } + } + } + + BMW_init(&walker, + em->bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, v) { + if (ele_walk->head.htype == BM_LOOP) { + BMVert *v_step = ((BMLoop *)ele_walk)->v; + BM_vert_select_set(em->bm, v_step, true); + BM_elem_flag_disable(v_step, BM_ELEM_TAG); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(em->bm, e_step, true); + BM_elem_flag_disable(e_step->v1, BM_ELEM_TAG); + BM_elem_flag_disable(e_step->v2, BM_ELEM_TAG); + } + } + } + } + } + else { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_TAG)) { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, v) { + BM_edge_select_set(em->bm, e_walk, true); + BM_elem_flag_disable(e_walk, BM_ELEM_TAG); + } + } + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + + if (delimit) { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + /* Check the edge for selected faces, + * this supports stepping off isolated edges which would otherwise be ignored. */ + BM_elem_flag_set(e, + BM_ELEM_TAG, + (BM_elem_flag_test(e, BM_ELEM_SELECT) && + (BMO_edge_flag_test(bm, e, BMO_ELE_TAG) || + !BM_edge_is_any_face_flag_test(e, BM_ELEM_SELECT)))); + } + } + else { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + BM_elem_flag_set(e, BM_ELEM_TAG, BM_elem_flag_test(e, BM_ELEM_SELECT)); + } + } + + BMW_init(&walker, + em->bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, e) { + if (ele_walk->head.htype == BM_LOOP) { + BMLoop *l_step = (BMLoop *)ele_walk; + BM_edge_select_set(em->bm, l_step->e, true); + BM_edge_select_set(em->bm, l_step->prev->e, true); + BM_elem_flag_disable(l_step->e, BM_ELEM_TAG); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(em->bm, e_step, true); + BM_elem_flag_disable(e_step, BM_ELEM_TAG); + } + } + } + } + } + else { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_TAG)) { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, e) { + BM_edge_select_set(em->bm, e_walk, true); + BM_elem_flag_disable(e_walk, BM_ELEM_TAG); + } + } + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else { + BMFace *f; + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BM_elem_flag_set(f, BM_ELEM_TAG, BM_elem_flag_test(f, BM_ELEM_SELECT)); + } + + BMW_init(&walker, + bm, + BMW_ISLAND, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG)) { + BMFace *f_walk; + BMW_ITER (f_walk, &walker, f) { + BM_face_select_set(bm, f_walk, true); + BM_elem_flag_disable(f_walk, BM_ELEM_TAG); + } + } + } + + BMW_end(&walker); + } + + if (delimit) { + select_linked_delimit_end(em); + } + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_linked(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Linked All"; + ot->idname = "MESH_OT_select_linked"; + ot->description = "Select all vertices connected to the current selection"; + + /* api callbacks */ + ot->exec = edbm_select_linked_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + prop = RNA_def_enum_flag(ot->srna, + "delimit", + rna_enum_mesh_delimit_mode_items, + BMO_DELIM_SEAM, + "Delimit", + "Delimit selected region"); +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +#else + UNUSED_VARS(prop); +#endif +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Linked (Cursor Pick) Operator + * \{ */ + +static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op); + +static void edbm_select_linked_pick_ex(BMEditMesh *em, BMElem *ele, bool sel, int delimit) +{ + BMesh *bm = em->bm; + BMWalker walker; + + select_linked_delimit_validate(bm, &delimit); + + if (delimit) { + select_linked_delimit_begin(bm, delimit); + } + + /* NOTE: logic closely matches #edbm_select_linked_exec, keep in sync. */ + + if (ele->head.htype == BM_VERT) { + BMVert *eve = (BMVert *)ele; + + BMW_init(&walker, + bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, eve) { + if (ele_walk->head.htype == BM_LOOP) { + BMVert *v_step = ((BMLoop *)ele_walk)->v; + BM_vert_select_set(bm, v_step, sel); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(bm, e_step, sel); + } + } + } + else { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, eve) { + BM_edge_select_set(bm, e_walk, sel); + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else if (ele->head.htype == BM_EDGE) { + BMEdge *eed = (BMEdge *)ele; + + BMW_init(&walker, + bm, + delimit ? BMW_LOOP_SHELL_WIRE : BMW_VERT_SHELL, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + if (delimit) { + BMElem *ele_walk; + BMW_ITER (ele_walk, &walker, eed) { + if (ele_walk->head.htype == BM_LOOP) { + BMEdge *e_step = ((BMLoop *)ele_walk)->e; + BM_edge_select_set(bm, e_step, sel); + } + else { + BMEdge *e_step = (BMEdge *)ele_walk; + BLI_assert(ele_walk->head.htype == BM_EDGE); + BM_edge_select_set(bm, e_step, sel); + } + } + } + else { + BMEdge *e_walk; + BMW_ITER (e_walk, &walker, eed) { + BM_edge_select_set(bm, e_walk, sel); + } + } + + BMW_end(&walker); + + EDBM_selectmode_flush(em); + } + else if (ele->head.htype == BM_FACE) { + BMFace *efa = (BMFace *)ele; + + BMW_init(&walker, + bm, + BMW_ISLAND, + BMW_MASK_NOP, + delimit ? BMO_ELE_TAG : BMW_MASK_NOP, + BMW_MASK_NOP, + BMW_FLAG_TEST_HIDDEN, + BMW_NIL_LAY); + + { + BMFace *f_walk; + BMW_ITER (f_walk, &walker, efa) { + BM_face_select_set(bm, f_walk, sel); + BM_elem_flag_disable(f_walk, BM_ELEM_TAG); + } + } + + BMW_end(&walker); + } + + if (delimit) { + select_linked_delimit_end(em); + } +} + +static int edbm_select_linked_pick_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + ViewContext vc; + Base *basact = nullptr; + BMVert *eve; + BMEdge *eed; + BMFace *efa; + const bool sel = !RNA_boolean_get(op->ptr, "deselect"); + int index; + + if (RNA_struct_property_is_set(op->ptr, "index")) { + return edbm_select_linked_pick_exec(C, op); + } + + /* #unified_findnearest needs OpenGL. */ + view3d_operator_needs_opengl(C); + + /* setup view context for argument to callbacks */ + em_setup_viewcontext(C, &vc); + + uint bases_len; + Base **bases = BKE_view_layer_array_from_bases_in_edit_mode( + vc.scene, vc.view_layer, vc.v3d, &bases_len); + + { + bool has_edges = false; + for (uint base_index = 0; base_index < bases_len; base_index++) { + Object *ob_iter = bases[base_index]->object; + ED_view3d_viewcontext_init_object(&vc, ob_iter); + if (vc.em->bm->totedge) { + has_edges = true; + } + } + if (has_edges == false) { + MEM_freeN(bases); + return OPERATOR_CANCELLED; + } + } + + vc.mval[0] = event->mval[0]; + vc.mval[1] = event->mval[1]; + + /* return warning! */ + { + int base_index = -1; + const bool ok = unified_findnearest(&vc, bases, bases_len, &base_index, &eve, &eed, &efa); + if (!ok) { + MEM_freeN(bases); + return OPERATOR_CANCELLED; + } + basact = bases[base_index]; + } + + ED_view3d_viewcontext_init_object(&vc, basact->object); + BMEditMesh *em = vc.em; + BMesh *bm = em->bm; + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + int delimit = select_linked_delimit_default_from_op(op, vc.scene->toolsettings->selectmode); +#else + int delimit = RNA_enum_get(op->ptr, "delimit"); +#endif + + BMElem *ele = EDBM_elem_from_selectmode(em, eve, eed, efa); + + edbm_select_linked_pick_ex(em, ele, sel, delimit); + + /* To support redo. */ + { + /* Note that the `base_index` can't be used as the index depends on the 3D Viewport + * which might not be available on redo. */ + BM_mesh_elem_index_ensure(bm, ele->head.htype); + int object_index; + index = EDBM_elem_to_index_any_multi(vc.scene, vc.view_layer, em, ele, &object_index); + BLI_assert(object_index >= 0); + RNA_int_set(op->ptr, "object_index", object_index); + RNA_int_set(op->ptr, "index", index); + } + + DEG_id_tag_update(static_cast(basact->object->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, basact->object->data); + + MEM_freeN(bases); + return OPERATOR_FINISHED; +} + +static int edbm_select_linked_pick_exec(bContext *C, wmOperator *op) +{ + Object *obedit = nullptr; + BMElem *ele; + + { + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + /* Intentionally wrap negative values so the lookup fails. */ + const uint object_index = (uint)RNA_int_get(op->ptr, "object_index"); + const uint index = (uint)RNA_int_get(op->ptr, "index"); + ele = EDBM_elem_from_index_any_multi(scene, view_layer, object_index, index, &obedit); + } + + if (ele == nullptr) { + return OPERATOR_CANCELLED; + } + + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const bool sel = !RNA_boolean_get(op->ptr, "deselect"); + +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + int delimit = select_linked_delimit_default_from_op(op, em->selectmode); +#else + int delimit = RNA_enum_get(op->ptr, "delimit"); +#endif + + edbm_select_linked_pick_ex(em, ele, sel, delimit); + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_linked_pick(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Linked"; + ot->idname = "MESH_OT_select_linked_pick"; + ot->description = "(De)select all vertices linked to the edge under the mouse cursor"; + + /* api callbacks */ + ot->invoke = edbm_select_linked_pick_invoke; + ot->exec = edbm_select_linked_pick_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "deselect", 0, "Deselect", ""); + prop = RNA_def_enum_flag(ot->srna, + "delimit", + rna_enum_mesh_delimit_mode_items, + BMO_DELIM_SEAM, + "Delimit", + "Delimit selected region"); +#ifdef USE_LINKED_SELECT_DEFAULT_HACK + RNA_def_property_flag(prop, PROP_SKIP_SAVE); +#endif + + /* use for redo */ + prop = RNA_def_int(ot->srna, "object_index", -1, -1, INT_MAX, "", "", 0, INT_MAX); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); + prop = RNA_def_int(ot->srna, "index", -1, -1, INT_MAX, "", "", 0, INT_MAX); + RNA_def_property_flag(prop, PropertyFlag(PROP_HIDDEN | PROP_SKIP_SAVE)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Face by Sides Operator + * \{ */ + +static int edbm_select_face_by_sides_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + uint objects_len = 0; + const bool extend = RNA_boolean_get(op->ptr, "extend"); + const int numverts = RNA_int_get(op->ptr, "number"); + const int type = RNA_enum_get(op->ptr, "type"); + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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); + BMFace *efa; + BMIter iter; + + if (!extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + bool select; + + switch (type) { + case 0: + select = (efa->len < numverts); + break; + case 1: + select = (efa->len == numverts); + break; + case 2: + select = (efa->len > numverts); + break; + case 3: + select = (efa->len != numverts); + break; + default: + BLI_assert(0); + select = false; + break; + } + + if (select) { + BM_face_select_set(em->bm, efa, true); + } + } + + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_face_by_sides(wmOperatorType *ot) +{ + static const EnumPropertyItem type_items[] = { + {0, "LESS", 0, "Less Than", ""}, + {1, "EQUAL", 0, "Equal To", ""}, + {2, "GREATER", 0, "Greater Than", ""}, + {3, "NOTEQUAL", 0, "Not Equal To", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Select Faces by Sides"; + ot->description = "Select vertices or faces by the number of polygon sides"; + ot->idname = "MESH_OT_select_face_by_sides"; + + /* api callbacks */ + ot->exec = edbm_select_face_by_sides_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_int(ot->srna, "number", 4, 3, INT_MAX, "Number of Vertices", "", 3, INT_MAX); + RNA_def_enum(ot->srna, "type", type_items, 1, "Type", "Type of comparison to make"); + RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend the selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loose Operator + * \{ */ + +static int edbm_select_loose_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool extend = RNA_boolean_get(op->ptr, "extend"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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; + BMIter iter; + + if (!extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMVert *eve; + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + if (!eve->e) { + BM_vert_select_set(bm, eve, true); + } + } + } + + if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *eed; + BM_ITER_MESH (eed, &iter, bm, BM_EDGES_OF_MESH) { + if (BM_edge_is_wire(eed)) { + BM_edge_select_set(bm, eed, true); + } + } + } + + if (em->selectmode & SCE_SELECT_FACE) { + BMFace *efa; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BMIter liter; + BMLoop *l; + bool is_loose = true; + BM_ITER_ELEM (l, &liter, efa, BM_LOOPS_OF_FACE) { + if (!BM_edge_is_boundary(l->e)) { + is_loose = false; + break; + } + } + if (is_loose) { + BM_face_select_set(bm, efa, true); + } + } + } + + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_loose(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Loose Geometry"; + ot->description = "Select loose geometry based on the selection mode"; + ot->idname = "MESH_OT_select_loose"; + + /* api callbacks */ + ot->exec = edbm_select_loose_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Mirror Operator + * \{ */ + +static int edbm_select_mirror_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const int axis_flag = RNA_enum_get(op->ptr, "axis"); + const bool extend = RNA_boolean_get(op->ptr, "extend"); + Object *obedit_active = CTX_data_edit_object(C); + BMEditMesh *em_active = BKE_editmesh_from_object(obedit_active); + const int select_mode = em_active->bm->selectmode; + int tot_mirr = 0, tot_fail = 0; + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) { + continue; + } + + int tot_mirr_iter = 0, tot_fail_iter = 0; + + for (int axis = 0; axis < 3; axis++) { + if ((1 << axis) & axis_flag) { + EDBM_select_mirrored(em, + static_cast(obedit->data), + axis, + extend, + &tot_mirr_iter, + &tot_fail_iter); + } + } + + if (tot_mirr_iter) { + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + tot_fail += tot_fail_iter; + tot_mirr += tot_mirr_iter; + } + MEM_freeN(objects); + + if (tot_mirr || tot_fail) { + ED_mesh_report_mirror_ex(op, tot_mirr, tot_fail, select_mode); + } + return OPERATOR_FINISHED; +} + +void MESH_OT_select_mirror(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Mirror"; + ot->description = "Select mesh items at mirrored locations"; + ot->idname = "MESH_OT_select_mirror"; + + /* api callbacks */ + ot->exec = edbm_select_mirror_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_enum_flag(ot->srna, "axis", rna_enum_axis_flag_xyz_items, (1 << 0), "Axis", ""); + + RNA_def_boolean(ot->srna, "extend", 0, "Extend", "Extend the existing selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select More Operator + * \{ */ + +static int edbm_select_more_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) && (bm->totedgesel == 0) && (bm->totfacesel == 0)) { + continue; + } + + EDBM_select_more(em, use_face_step); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_more(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select More"; + ot->idname = "MESH_OT_select_more"; + ot->description = "Select more vertices, edges or faces connected to initial selection"; + + /* api callbacks */ + ot->exec = edbm_select_more_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "use_face_step", true, "Face Step", "Connected faces (instead of edges)"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select More Operator + * \{ */ + +static int edbm_select_less_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + const bool use_face_step = RNA_boolean_get(op->ptr, "use_face_step"); + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) && (bm->totedgesel == 0) && (bm->totfacesel == 0)) { + continue; + } + + EDBM_select_less(em, use_face_step); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_less(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Less"; + ot->idname = "MESH_OT_select_less"; + ot->description = "Deselect vertices, edges or faces at the boundary of each selection region"; + + /* api callbacks */ + ot->exec = edbm_select_less_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean( + ot->srna, "use_face_step", true, "Face Step", "Connected faces (instead of edges)"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select N'th Operator + * \{ */ + +/** + * Check if we're connected to another selected edge. + */ +static bool bm_edge_is_select_isolated(BMEdge *e) +{ + BMIter viter; + BMVert *v; + + BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) { + BMIter eiter; + BMEdge *e_other; + + BM_ITER_ELEM (e_other, &eiter, v, BM_EDGES_OF_VERT) { + if ((e_other != e) && BM_elem_flag_test(e_other, BM_ELEM_SELECT)) { + return false; + } + } + } + return true; +} + +/* Walk all reachable elements of the same type as h_act in breadth-first + * order, starting from h_act. Deselects elements if the depth when they + * are reached is not a multiple of "nth". */ +static void walker_deselect_nth(BMEditMesh *em, + const CheckerIntervalParams *op_params, + BMHeader *h_act) +{ + BMElem *ele; + BMesh *bm = em->bm; + BMWalker walker; + BMIter iter; + int walktype = 0, itertype = 0, flushtype = 0; + short mask_vert = 0, mask_edge = 0, mask_face = 0; + + /* No active element from which to start - nothing to do */ + if (h_act == nullptr) { + return; + } + + /* Determine which type of iter, walker, and select flush to use + * based on type of the elements being deselected */ + switch (h_act->htype) { + case BM_VERT: + itertype = BM_VERTS_OF_MESH; + walktype = BMW_CONNECTED_VERTEX; + flushtype = SCE_SELECT_VERTEX; + mask_vert = BMO_ELE_TAG; + break; + case BM_EDGE: + /* When an edge has no connected-selected edges, + * use face-stepping (supports edge-rings) */ + itertype = BM_EDGES_OF_MESH; + walktype = bm_edge_is_select_isolated((BMEdge *)h_act) ? BMW_FACE_SHELL : BMW_VERT_SHELL; + flushtype = SCE_SELECT_EDGE; + mask_edge = BMO_ELE_TAG; + break; + case BM_FACE: + itertype = BM_FACES_OF_MESH; + walktype = BMW_ISLAND; + flushtype = SCE_SELECT_FACE; + mask_face = BMO_ELE_TAG; + break; + } + + /* grr, shouldn't need to alloc BMO flags here */ + BM_mesh_elem_toolflags_ensure(bm); + + /* Walker restrictions uses BMO flags, not header flags, + * so transfer BM_ELEM_SELECT from HFlags onto a BMO flag layer. */ + BMO_push(bm, nullptr); + BM_ITER_MESH (ele, &iter, bm, itertype) { + if (BM_elem_flag_test(ele, BM_ELEM_SELECT)) { + BMO_elem_flag_enable(bm, (BMElemF *)ele, BMO_ELE_TAG); + } + } + + /* Walk over selected elements starting at active */ + BMW_init(&walker, + bm, + walktype, + mask_vert, + mask_edge, + mask_face, + BMW_FLAG_NOP, /* don't use BMW_FLAG_TEST_HIDDEN here since we want to desel all */ + BMW_NIL_LAY); + + /* use tag to avoid touching the same verts twice */ + BM_ITER_MESH (ele, &iter, bm, itertype) { + BM_elem_flag_disable(ele, BM_ELEM_TAG); + } + + BLI_assert(walker.order == BMW_BREADTH_FIRST); + for (ele = static_cast(BMW_begin(&walker, h_act)); ele != nullptr; + ele = static_cast(BMW_step(&walker))) { + if (!BM_elem_flag_test(ele, BM_ELEM_TAG)) { + /* Deselect elements that aren't at "nth" depth from active */ + const int depth = BMW_current_depth(&walker) - 1; + if (!WM_operator_properties_checker_interval_test(op_params, depth)) { + BM_elem_select_set(bm, ele, false); + } + BM_elem_flag_enable(ele, BM_ELEM_TAG); + } + } + BMW_end(&walker); + + BMO_pop(bm); + + /* Flush selection up */ + EDBM_selectmode_flush_ex(em, flushtype); +} + +static void deselect_nth_active(BMEditMesh *em, BMVert **r_eve, BMEdge **r_eed, BMFace **r_efa) +{ + BMIter iter; + BMElem *ele; + + *r_eve = nullptr; + *r_eed = nullptr; + *r_efa = nullptr; + + EDBM_selectmode_flush(em); + ele = BM_mesh_active_elem_get(em->bm); + + if (ele && BM_elem_flag_test(ele, BM_ELEM_SELECT)) { + switch (ele->head.htype) { + case BM_VERT: + *r_eve = (BMVert *)ele; + return; + case BM_EDGE: + *r_eed = (BMEdge *)ele; + return; + case BM_FACE: + *r_efa = (BMFace *)ele; + return; + } + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + BMVert *v; + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + *r_eve = v; + return; + } + } + } + else if (em->selectmode & SCE_SELECT_EDGE) { + BMEdge *e; + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + *r_eed = e; + return; + } + } + } + else if (em->selectmode & SCE_SELECT_FACE) { + BMFace *f = BM_mesh_active_face_get(em->bm, true, false); + if (f && BM_elem_flag_test(f, BM_ELEM_SELECT)) { + *r_efa = f; + return; + } + } +} + +static bool edbm_deselect_nth(BMEditMesh *em, const CheckerIntervalParams *op_params) +{ + BMVert *v; + BMEdge *e; + BMFace *f; + + deselect_nth_active(em, &v, &e, &f); + + if (v) { + walker_deselect_nth(em, op_params, &v->head); + return true; + } + if (e) { + walker_deselect_nth(em, op_params, &e->head); + return true; + } + if (f) { + walker_deselect_nth(em, op_params, &f->head); + return true; + } + + return false; +} + +static int edbm_select_nth_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + CheckerIntervalParams op_params; + WM_operator_properties_checker_interval_from_op(op, &op_params); + bool found_active_elt = false; + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode_unique_data( + scene, view_layer, CTX_wm_view3d(C), &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->totvertsel == 0) && (em->bm->totedgesel == 0) && (em->bm->totfacesel == 0)) { + continue; + } + + if (edbm_deselect_nth(em, &op_params) == true) { + found_active_elt = true; + EDBMUpdate_Params params{}; + params.calc_looptri = false; + params.calc_normals = false; + params.is_destructive = false; + EDBM_update(static_cast(obedit->data), ¶ms); + } + } + MEM_freeN(objects); + + if (!found_active_elt) { + BKE_report(op->reports, RPT_ERROR, "Mesh object(s) have no active vertex/edge/face"); + return OPERATOR_CANCELLED; + } + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_nth(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Checker Deselect"; + ot->idname = "MESH_OT_select_nth"; + ot->description = "Deselect every Nth element starting from the active vertex, edge or face"; + + /* api callbacks */ + ot->exec = edbm_select_nth_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + WM_operator_properties_checker_interval(ot, false); +} + +void em_setup_viewcontext(bContext *C, ViewContext *vc) +{ + Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); + ED_view3d_viewcontext_init(C, vc, depsgraph); + + if (vc->obedit) { + vc->em = BKE_editmesh_from_object(vc->obedit); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Sharp Edges Operator + * \{ */ + +static int edbm_select_sharp_edges_exec(bContext *C, wmOperator *op) +{ + /* Find edges that have exactly two neighboring faces, + * check the angle between those faces, and if angle is + * small enough, select the edge + */ + const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); + + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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); + BMIter iter; + BMEdge *e; + BMLoop *l1, *l2; + + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_HIDDEN) == false && BM_edge_loop_pair(e, &l1, &l2)) { + /* edge has exactly two neighboring faces, check angle */ + const float angle_cos = dot_v3v3(l1->f->no, l2->f->no); + + if (angle_cos < angle_limit_cos) { + BM_edge_select_set(em->bm, e, true); + } + } + } + + if ((em->bm->selectmode & (SCE_SELECT_VERTEX | SCE_SELECT_EDGE)) == 0) { + /* Since we can't select individual edges, select faces connected to them. */ + EDBM_selectmode_convert(em, SCE_SELECT_EDGE, SCE_SELECT_FACE); + } + else { + EDBM_selectmode_flush(em); + } + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_edges_select_sharp(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Sharp Edges"; + ot->description = "Select all sharp enough edges"; + ot->idname = "MESH_OT_edges_select_sharp"; + + /* api callbacks */ + ot->exec = edbm_select_sharp_edges_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + prop = RNA_def_float_rotation(ot->srna, + "sharpness", + 0, + nullptr, + DEG2RADF(0.01f), + DEG2RADF(180.0f), + "Sharpness", + "", + DEG2RADF(1.0f), + DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(30.0f)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Linked Flat Faces Operator + * \{ */ + +static int edbm_select_linked_flat_faces_exec(bContext *C, wmOperator *op) +{ + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + const float angle_limit_cos = cosf(RNA_float_get(op->ptr, "sharpness")); + + 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->totfacesel == 0) { + continue; + } + + blender::Vector stack; + + BMIter iter, liter, liter2; + BMFace *f; + BMLoop *l, *l2; + + BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false); + + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + if ((BM_elem_flag_test(f, BM_ELEM_HIDDEN) != 0) || + (BM_elem_flag_test(f, BM_ELEM_TAG) != 0) || + (BM_elem_flag_test(f, BM_ELEM_SELECT) == 0)) { + continue; + } + + BLI_assert(stack.is_empty()); + + do { + BM_face_select_set(bm, f, true); + + BM_elem_flag_enable(f, BM_ELEM_TAG); + + BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) { + BM_ITER_ELEM (l2, &liter2, l, BM_LOOPS_OF_LOOP) { + float angle_cos; + + if (BM_elem_flag_test(l2->f, BM_ELEM_TAG) || + BM_elem_flag_test(l2->f, BM_ELEM_HIDDEN)) { + continue; + } + + angle_cos = dot_v3v3(f->no, l2->f->no); + + if (angle_cos > angle_limit_cos) { + stack.append(l2->f); + } + } + } + } while (!stack.is_empty() && (f = stack.pop_last())); + } + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_faces_select_linked_flat(wmOperatorType *ot) +{ + PropertyRNA *prop; + + /* identifiers */ + ot->name = "Select Linked Flat Faces"; + ot->description = "Select linked faces by angle"; + ot->idname = "MESH_OT_faces_select_linked_flat"; + + /* api callbacks */ + ot->exec = edbm_select_linked_flat_faces_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + prop = RNA_def_float_rotation(ot->srna, + "sharpness", + 0, + nullptr, + DEG2RADF(0.01f), + DEG2RADF(180.0f), + "Sharpness", + "", + DEG2RADF(1.0f), + DEG2RADF(180.0f)); + RNA_def_property_float_default(prop, DEG2RADF(1.0f)); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Non-Manifold Operator + * \{ */ + +static int edbm_select_non_manifold_exec(bContext *C, wmOperator *op) +{ + const bool use_extend = RNA_boolean_get(op->ptr, "extend"); + const bool use_wire = RNA_boolean_get(op->ptr, "use_wire"); + const bool use_boundary = RNA_boolean_get(op->ptr, "use_boundary"); + const bool use_multi_face = RNA_boolean_get(op->ptr, "use_multi_face"); + const bool use_non_contiguous = RNA_boolean_get(op->ptr, "use_non_contiguous"); + const bool use_verts = RNA_boolean_get(op->ptr, "use_verts"); + + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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); + BMVert *v; + BMEdge *e; + BMIter iter; + + if (!use_extend) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + } + + /* Selects isolated verts, and edges that do not have 2 neighboring + * faces + */ + + if (em->selectmode == SCE_SELECT_FACE) { + BKE_report(op->reports, RPT_ERROR, "Does not work in face selection mode"); + MEM_freeN(objects); + return OPERATOR_CANCELLED; + } + + if (use_verts) { + BM_ITER_MESH (v, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN)) { + if (!BM_vert_is_manifold(v)) { + BM_vert_select_set(em->bm, v, true); + } + } + } + } + + if (use_wire || use_boundary || use_multi_face || use_non_contiguous) { + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(e, BM_ELEM_HIDDEN)) { + if ((use_wire && BM_edge_is_wire(e)) || (use_boundary && BM_edge_is_boundary(e)) || + (use_non_contiguous && (BM_edge_is_manifold(e) && !BM_edge_is_contiguous(e))) || + (use_multi_face && (BM_edge_face_count_is_over(e, 2)))) { + /* check we never select perfect edge (in test above) */ + BLI_assert(!(BM_edge_is_manifold(e) && BM_edge_is_contiguous(e))); + + BM_edge_select_set(em->bm, e, true); + } + } + } + } + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + + EDBM_selectmode_flush(em); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_select_non_manifold(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Non-Manifold"; + ot->description = "Select all non-manifold vertices or edges"; + ot->idname = "MESH_OT_select_non_manifold"; + + /* api callbacks */ + ot->exec = edbm_select_non_manifold_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + RNA_def_boolean(ot->srna, "extend", true, "Extend", "Extend the selection"); + /* edges */ + RNA_def_boolean(ot->srna, "use_wire", true, "Wire", "Wire edges"); + RNA_def_boolean(ot->srna, "use_boundary", true, "Boundaries", "Boundary edges"); + RNA_def_boolean( + ot->srna, "use_multi_face", true, "Multiple Faces", "Edges shared by more than two faces"); + RNA_def_boolean(ot->srna, + "use_non_contiguous", + true, + "Non Contiguous", + "Edges between faces pointing in alternate directions"); + /* verts */ + RNA_def_boolean( + ot->srna, "use_verts", true, "Vertices", "Vertices connecting multiple face regions"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Random Operator + * \{ */ + +static int edbm_select_random_exec(bContext *C, wmOperator *op) +{ + const bool select = (RNA_enum_get(op->ptr, "action") == SEL_SELECT); + const float randfac = RNA_float_get(op->ptr, "ratio"); + const int seed = WM_operator_properties_select_random_seed_increment_get(op); + + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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); + BMIter iter; + int seed_iter = seed; + + /* This gives a consistent result regardless of object order. */ + if (ob_index) { + seed_iter += BLI_ghashutil_strhash_p(obedit->id.name); + } + + if (em->selectmode & SCE_SELECT_VERTEX) { + int elem_map_len = 0; + BMVert **elem_map = static_cast( + MEM_mallocN(sizeof(*elem_map) * em->bm->totvert, __func__)); + BMVert *eve; + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + elem_map[elem_map_len++] = eve; + } + } + + BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); + const int count_select = elem_map_len * randfac; + for (int i = 0; i < count_select; i++) { + BM_vert_select_set(em->bm, elem_map[i], select); + } + MEM_freeN(elem_map); + } + else if (em->selectmode & SCE_SELECT_EDGE) { + int elem_map_len = 0; + BMEdge **elem_map = static_cast( + MEM_mallocN(sizeof(*elem_map) * em->bm->totedge, __func__)); + BMEdge *eed; + BM_ITER_MESH (eed, &iter, em->bm, BM_EDGES_OF_MESH) { + if (!BM_elem_flag_test(eed, BM_ELEM_HIDDEN)) { + elem_map[elem_map_len++] = eed; + } + } + BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); + const int count_select = elem_map_len * randfac; + for (int i = 0; i < count_select; i++) { + BM_edge_select_set(em->bm, elem_map[i], select); + } + MEM_freeN(elem_map); + } + else { + int elem_map_len = 0; + BMFace **elem_map = static_cast( + MEM_mallocN(sizeof(*elem_map) * em->bm->totface, __func__)); + BMFace *efa; + BM_ITER_MESH (efa, &iter, em->bm, BM_FACES_OF_MESH) { + if (!BM_elem_flag_test(efa, BM_ELEM_HIDDEN)) { + elem_map[elem_map_len++] = efa; + } + } + BLI_array_randomize(elem_map, sizeof(*elem_map), elem_map_len, seed_iter); + const int count_select = elem_map_len * randfac; + for (int i = 0; i < count_select; i++) { + BM_face_select_set(em->bm, elem_map[i], select); + } + MEM_freeN(elem_map); + } + + if (select) { + /* was EDBM_select_flush, but it over select in edge/face mode */ + EDBM_selectmode_flush(em); + } + else { + EDBM_deselect_flush(em); + } + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_random(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Random"; + ot->description = "Randomly select vertices"; + ot->idname = "MESH_OT_select_random"; + + /* api callbacks */ + ot->exec = edbm_select_random_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* props */ + WM_operator_properties_select_random(ot); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Ungrouped Operator + * \{ */ + +static bool edbm_select_ungrouped_poll(bContext *C) +{ + if (ED_operator_editmesh(C)) { + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + const ListBase *defbase = BKE_object_defgroup_list(obedit); + if ((em->selectmode & SCE_SELECT_VERTEX) == 0) { + CTX_wm_operator_poll_msg_set(C, "Must be in vertex selection mode"); + } + else if (BLI_listbase_is_empty(defbase) || cd_dvert_offset == -1) { + CTX_wm_operator_poll_msg_set(C, "No weights/vertex groups on object"); + } + else { + return true; + } + } + return false; +} + +static int edbm_select_ungrouped_exec(bContext *C, wmOperator *op) +{ + const bool extend = RNA_boolean_get(op->ptr, "extend"); + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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); + + const int cd_dvert_offset = CustomData_get_offset(&em->bm->vdata, CD_MDEFORMVERT); + + if (cd_dvert_offset == -1) { + continue; + } + + BMVert *eve; + BMIter iter; + + bool changed = false; + + if (!extend) { + if (em->bm->totvertsel) { + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + changed = true; + } + } + + BM_ITER_MESH (eve, &iter, em->bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(eve, BM_ELEM_HIDDEN)) { + MDeformVert *dv = static_cast(BM_ELEM_CD_GET_VOID_P(eve, cd_dvert_offset)); + /* no dv or dv set with no weight */ + if (ELEM(nullptr, dv, dv->dw)) { + BM_vert_select_set(em->bm, eve, true); + changed = true; + } + } + } + + if (changed) { + EDBM_selectmode_flush(em); + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + } + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_ungrouped(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Ungrouped"; + ot->idname = "MESH_OT_select_ungrouped"; + ot->description = "Select vertices without a group"; + + /* api callbacks */ + ot->exec = edbm_select_ungrouped_exec; + ot->poll = edbm_select_ungrouped_poll; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "extend", false, "Extend", "Extend the selection"); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Axis Operator + * \{ */ + +enum { + SELECT_AXIS_POS = 0, + SELECT_AXIS_NEG = 1, + SELECT_AXIS_ALIGN = 2, +}; + +static int edbm_select_axis_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + ViewLayer *view_layer = CTX_data_view_layer(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMVert *v_act = BM_mesh_active_vert_get(em->bm); + const int orientation = RNA_enum_get(op->ptr, "orientation"); + const int axis = RNA_enum_get(op->ptr, "axis"); + const int sign = RNA_enum_get(op->ptr, "sign"); + + if (v_act == nullptr) { + BKE_report( + op->reports, RPT_WARNING, "This operator requires an active vertex (last selected)"); + return OPERATOR_CANCELLED; + } + + const float limit = RNA_float_get(op->ptr, "threshold"); + + float value; + float axis_mat[3][3]; + + /* 3D view variables may be nullptr, (no need to check in poll function). */ + ED_transform_calc_orientation_from_type_ex(scene, + view_layer, + CTX_wm_view3d(C), + CTX_wm_region_view3d(C), + obedit, + obedit, + orientation, + V3D_AROUND_ACTIVE, + axis_mat); + + const float *axis_vector = axis_mat[axis]; + + { + float vertex_world[3]; + mul_v3_m4v3(vertex_world, obedit->obmat, v_act->co); + value = dot_v3v3(axis_vector, vertex_world); + } + + if (sign == SELECT_AXIS_NEG) { + value += limit; + } + else if (sign == SELECT_AXIS_POS) { + value -= limit; + } + + uint objects_len = 0; + Object **objects = BKE_view_layer_array_from_objects_in_edit_mode( + scene, view_layer, CTX_wm_view3d(C), &objects_len); + for (uint ob_index = 0; ob_index < objects_len; ob_index++) { + Object *obedit_iter = objects[ob_index]; + BMEditMesh *em_iter = BKE_editmesh_from_object(obedit_iter); + BMesh *bm = em_iter->bm; + + if (bm->totvert == bm->totvertsel) { + continue; + } + + BMIter iter; + BMVert *v; + bool changed = false; + + BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) { + if (!BM_elem_flag_test(v, BM_ELEM_HIDDEN | BM_ELEM_SELECT)) { + float v_iter_world[3]; + mul_v3_m4v3(v_iter_world, obedit_iter->obmat, v->co); + const float value_iter = dot_v3v3(axis_vector, v_iter_world); + switch (sign) { + case SELECT_AXIS_ALIGN: + if (fabsf(value_iter - value) < limit) { + BM_vert_select_set(bm, v, true); + changed = true; + } + break; + case SELECT_AXIS_NEG: + if (value_iter < value) { + BM_vert_select_set(bm, v, true); + changed = true; + } + break; + case SELECT_AXIS_POS: + if (value_iter > value) { + BM_vert_select_set(bm, v, true); + changed = true; + } + break; + } + } + } + if (changed) { + EDBM_selectmode_flush(em_iter); + WM_event_add_notifier(C, NC_GEOM | ND_DATA, obedit_iter->data); + DEG_id_tag_update(static_cast(obedit_iter->data), ID_RECALC_SELECT); + } + } + MEM_freeN(objects); + return OPERATOR_FINISHED; +} + +void MESH_OT_select_axis(wmOperatorType *ot) +{ + static const EnumPropertyItem axis_sign_items[] = { + {SELECT_AXIS_POS, "POS", 0, "Positive Axis", ""}, + {SELECT_AXIS_NEG, "NEG", 0, "Negative Axis", ""}, + {SELECT_AXIS_ALIGN, "ALIGN", 0, "Aligned Axis", ""}, + {0, nullptr, 0, nullptr, nullptr}, + }; + + /* identifiers */ + ot->name = "Select Axis"; + ot->description = "Select all data in the mesh on a single axis"; + ot->idname = "MESH_OT_select_axis"; + + /* api callbacks */ + ot->exec = edbm_select_axis_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + /* properties */ + RNA_def_enum(ot->srna, + "orientation", + rna_enum_transform_orientation_items, + V3D_ORIENT_LOCAL, + "Axis Mode", + "Axis orientation"); + RNA_def_enum(ot->srna, "sign", axis_sign_items, SELECT_AXIS_POS, "Axis Sign", "Side to select"); + RNA_def_enum(ot->srna, + "axis", + rna_enum_axis_xyz_items, + 0, + "Axis", + "Select the axis to compare each vertex on"); + RNA_def_float( + ot->srna, "threshold", 0.0001f, 0.000001f, 50.0f, "Threshold", "", 0.00001f, 10.0f); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Region to Loop Operator + * \{ */ + +static int edbm_region_to_loop_exec(bContext *C, wmOperator *UNUSED(op)) +{ + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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; + } + BMFace *f; + BMEdge *e; + BMIter iter; + + BM_mesh_elem_hflag_disable_all(em->bm, BM_EDGE, BM_ELEM_TAG, false); + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + BMLoop *l1, *l2; + BMIter liter1, liter2; + + BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) { + int tot = 0, totsel = 0; + + BM_ITER_ELEM (l2, &liter2, l1->e, BM_LOOPS_OF_EDGE) { + tot++; + totsel += BM_elem_flag_test(l2->f, BM_ELEM_SELECT) != 0; + } + + if ((tot != totsel && totsel > 0) || (totsel == 1 && tot == 1)) { + BM_elem_flag_enable(l1->e, BM_ELEM_TAG); + } + } + } + + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + + 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); + } + } + + /* If in face-only select mode, switch to edge select mode so that + * an edge-only selection is not inconsistent state */ + if (em->selectmode == SCE_SELECT_FACE) { + em->selectmode = SCE_SELECT_EDGE; + EDBM_selectmode_set(em); + EDBM_selectmode_to_scene(C); + } + + DEG_id_tag_update(&obedit->id, ID_RECALC_GEOMETRY); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_region_to_loop(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Boundary Loop"; + ot->idname = "MESH_OT_region_to_loop"; + ot->description = "Select boundary edges around the selected faces"; + + /* api callbacks */ + ot->exec = edbm_region_to_loop_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Select Loop to Region Operator + * \{ */ + +static int loop_find_region(BMLoop *l, int flag, GSet *visit_face_set, BMFace ***region_out) +{ + blender::Vector stack; + blender::Vector region; + + stack.append(l->f); + BLI_gset_insert(visit_face_set, l->f); + + while (!stack.is_empty()) { + BMIter liter1, liter2; + BMLoop *l1, *l2; + + BMFace *f = stack.pop_last(); + region.append(f); + + BM_ITER_ELEM (l1, &liter1, f, BM_LOOPS_OF_FACE) { + if (BM_elem_flag_test(l1->e, flag)) { + continue; + } + + BM_ITER_ELEM (l2, &liter2, l1->e, BM_LOOPS_OF_EDGE) { + /* avoids finding same region twice + * (otherwise) the logic works fine without */ + if (BM_elem_flag_test(l2->f, BM_ELEM_TAG)) { + continue; + } + + if (BLI_gset_add(visit_face_set, l2->f)) { + stack.append(l2->f); + } + } + } + } + + BMFace **region_alloc = static_cast( + MEM_malloc_arrayN(region.size(), sizeof(BMFace *), __func__)); + memcpy(region_alloc, region.data(), region.as_span().size_in_bytes()); + *region_out = region_alloc; + return region.size(); +} + +static int verg_radial(const void *va, const void *vb) +{ + const BMEdge *e_a = *((const BMEdge **)va); + const BMEdge *e_b = *((const BMEdge **)vb); + + const int a = BM_edge_face_count(e_a); + const int b = BM_edge_face_count(e_b); + + if (a > b) { + return -1; + } + if (a < b) { + return 1; + } + return 0; +} + +/** + * This function leaves faces tagged which are a part of the new region. + * + * \note faces already tagged are ignored, to avoid finding the same regions twice: + * important when we have regions with equal face counts, see: T40309 + */ +static int loop_find_regions(BMEditMesh *em, const bool selbigger) +{ + GSet *visit_face_set; + BMIter iter; + const int edges_len = em->bm->totedgesel; + BMEdge *e; + int count = 0, i; + + visit_face_set = BLI_gset_ptr_new_ex(__func__, edges_len); + BMEdge **edges = static_cast(MEM_mallocN(sizeof(*edges) * edges_len, __func__)); + + i = 0; + BM_ITER_MESH (e, &iter, em->bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + edges[i++] = e; + BM_elem_flag_enable(e, BM_ELEM_TAG); + } + else { + BM_elem_flag_disable(e, BM_ELEM_TAG); + } + } + + /* sort edges by radial cycle length */ + qsort(edges, edges_len, sizeof(*edges), verg_radial); + + for (i = 0; i < edges_len; i++) { + BMIter liter; + BMLoop *l; + BMFace **region = nullptr, **region_out; + int c, tot = 0; + + e = edges[i]; + + if (!BM_elem_flag_test(e, BM_ELEM_TAG)) { + continue; + } + + BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) { + if (BLI_gset_haskey(visit_face_set, l->f)) { + continue; + } + + c = loop_find_region(l, BM_ELEM_SELECT, visit_face_set, ®ion_out); + + if (!region || (selbigger ? c >= tot : c < tot)) { + /* this region is the best seen so far */ + tot = c; + if (region) { + /* free the previous best */ + MEM_freeN(region); + } + /* track the current region as the new best */ + region = region_out; + } + else { + /* this region is not as good as best so far, just free it */ + MEM_freeN(region_out); + } + } + + if (region) { + int j; + + for (j = 0; j < tot; j++) { + BM_elem_flag_enable(region[j], BM_ELEM_TAG); + BM_ITER_ELEM (l, &liter, region[j], BM_LOOPS_OF_FACE) { + BM_elem_flag_disable(l->e, BM_ELEM_TAG); + } + } + + count += tot; + + MEM_freeN(region); + } + } + + MEM_freeN(edges); + BLI_gset_free(visit_face_set, nullptr); + + return count; +} + +static int edbm_loop_to_region_exec(bContext *C, wmOperator *op) +{ + const bool select_bigger = RNA_boolean_get(op->ptr, "select_bigger"); + + const Scene *scene = CTX_data_scene(C); + 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( + scene, view_layer, CTX_wm_view3d(C), &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->totedgesel == 0) { + continue; + } + + BMIter iter; + BMFace *f; + + /* find the set of regions with smallest number of total faces */ + BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); + const int a = loop_find_regions(em, select_bigger); + const int b = loop_find_regions(em, !select_bigger); + + BM_mesh_elem_hflag_disable_all(em->bm, BM_FACE, BM_ELEM_TAG, false); + loop_find_regions(em, ((a <= b) != select_bigger) ? select_bigger : !select_bigger); + + EDBM_flag_disable_all(em, BM_ELEM_SELECT); + + BM_ITER_MESH (f, &iter, em->bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_TAG) && !BM_elem_flag_test(f, BM_ELEM_HIDDEN)) { + BM_face_select_set(em->bm, f, true); + } + } + + EDBM_selectmode_flush(em); + + DEG_id_tag_update(static_cast(obedit->data), ID_RECALC_SELECT); + WM_event_add_notifier(C, NC_GEOM | ND_SELECT, obedit->data); + } + MEM_freeN(objects); + + return OPERATOR_FINISHED; +} + +void MESH_OT_loop_to_region(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Select Loop Inner-Region"; + ot->idname = "MESH_OT_loop_to_region"; + ot->description = "Select region of faces inside of a selected loop of edges"; + + /* api callbacks */ + ot->exec = edbm_loop_to_region_exec; + ot->poll = ED_operator_editmesh; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, + "select_bigger", + 0, + "Select Bigger", + "Select bigger regions instead of smaller ones"); +} + +/** \} */ diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 9f90ccc30ab..e6505715324 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -192,7 +192,7 @@ void MESH_OT_loopcut(struct wmOperatorType *ot); void MESH_OT_rip(struct wmOperatorType *ot); void MESH_OT_rip_edge(struct wmOperatorType *ot); -/* *** editmesh_select.c *** */ +/* *** editmesh_select.cc *** */ void MESH_OT_select_similar(struct wmOperatorType *ot); void MESH_OT_select_similar_region(struct wmOperatorType *ot); diff --git a/source/blender/editors/space_view3d/CMakeLists.txt b/source/blender/editors/space_view3d/CMakeLists.txt index 97e39c00c48..579e27b9259 100644 --- a/source/blender/editors/space_view3d/CMakeLists.txt +++ b/source/blender/editors/space_view3d/CMakeLists.txt @@ -46,7 +46,7 @@ set(SRC view3d_gizmo_ruler.c view3d_gizmo_tool_generic.c view3d_header.c - view3d_iterators.c + view3d_iterators.cc view3d_navigate.c view3d_navigate_dolly.c view3d_navigate_fly.c diff --git a/source/blender/editors/space_view3d/view3d_iterators.c b/source/blender/editors/space_view3d/view3d_iterators.c deleted file mode 100644 index 34f68e87880..00000000000 --- a/source/blender/editors/space_view3d/view3d_iterators.c +++ /dev/null @@ -1,883 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - -/** \file - * \ingroup spview3d - */ - -#include "DNA_armature_types.h" -#include "DNA_curve_types.h" -#include "DNA_lattice_types.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_meta_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" - -#include "BLI_math_geom.h" -#include "BLI_rect.h" -#include "BLI_utildefines.h" - -#include "BKE_DerivedMesh.h" -#include "BKE_action.h" -#include "BKE_armature.h" -#include "BKE_curve.h" -#include "BKE_displist.h" -#include "BKE_editmesh.h" -#include "BKE_mesh.h" -#include "BKE_mesh_iterators.h" -#include "BKE_mesh_runtime.h" -#include "BKE_mesh_wrapper.h" -#include "BKE_modifier.h" - -#include "DEG_depsgraph.h" -#include "DEG_depsgraph_query.h" - -#include "bmesh.h" - -#include "ED_armature.h" -#include "ED_screen.h" -#include "ED_view3d.h" - -/* -------------------------------------------------------------------- */ -/** \name Internal Clipping Utilities - * \{ */ - -/** - * Calculate clipping planes to use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. - * - * Planes are selected from the viewpoint using `clip_flag` - * to detect which planes should be applied (maximum 6). - * - * \return The number of planes written into `planes`. - */ -static int content_planes_from_clip_flag(const ARegion *region, - const Object *ob, - const eV3DProjTest clip_flag, - float planes[6][4]) -{ - BLI_assert(clip_flag & V3D_PROJ_TEST_CLIP_CONTENT); - - float *clip_xmin = NULL, *clip_xmax = NULL; - float *clip_ymin = NULL, *clip_ymax = NULL; - float *clip_zmin = NULL, *clip_zmax = NULL; - - int planes_len = 0; - - /* The order of `planes` has been selected based on the likelihood of points being fully - * outside the plane to increase the chance of an early exit in #clip_segment_v3_plane_n. - * With "near" being most likely and "far" being unlikely. - * - * Otherwise the order of axes in `planes` isn't significant. */ - - if (clip_flag & V3D_PROJ_TEST_CLIP_NEAR) { - clip_zmin = planes[planes_len++]; - } - if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - clip_xmin = planes[planes_len++]; - clip_xmax = planes[planes_len++]; - clip_ymin = planes[planes_len++]; - clip_ymax = planes[planes_len++]; - } - if (clip_flag & V3D_PROJ_TEST_CLIP_FAR) { - clip_zmax = planes[planes_len++]; - } - - BLI_assert(planes_len <= 6); - if (planes_len != 0) { - RegionView3D *rv3d = region->regiondata; - float projmat[4][4]; - ED_view3d_ob_project_mat_get(rv3d, ob, projmat); - planes_from_projmat(projmat, clip_xmin, clip_xmax, clip_ymin, clip_ymax, clip_zmin, clip_zmax); - } - return planes_len; -} - -/** - * Edge projection is more involved since part of the edge may be behind the view - * or extend beyond the far limits. In the case of single points, these can be ignored. - * However it just may still be visible on screen, so constrained the edge to planes - * defined by the port to ensure both ends of the edge can be projected, see T32214. - * - * \note This is unrelated to #V3D_PROJ_TEST_CLIP_BB which must be checked separately. - */ -static bool view3d_project_segment_to_screen_with_content_clip_planes( - const ARegion *region, - const float v_a[3], - const float v_b[3], - const eV3DProjTest clip_flag, - const rctf *win_rect, - const float content_planes[][4], - const int content_planes_len, - /* Output. */ - float r_screen_co_a[2], - float r_screen_co_b[2]) -{ - /* Clipping already handled, no need to check in projection. */ - eV3DProjTest clip_flag_nowin = clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; - - const eV3DProjStatus status_a = ED_view3d_project_float_object( - region, v_a, r_screen_co_a, clip_flag_nowin); - const eV3DProjStatus status_b = ED_view3d_project_float_object( - region, v_b, r_screen_co_b, clip_flag_nowin); - - if ((status_a == V3D_PROJ_RET_OK) && (status_b == V3D_PROJ_RET_OK)) { - if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { - if (!BLI_rctf_isect_segment(win_rect, r_screen_co_a, r_screen_co_b)) { - return false; - } - } - } - else { - if (content_planes_len == 0) { - return false; - } - - /* Both too near, ignore. */ - if ((status_a & V3D_PROJ_TEST_CLIP_NEAR) && (status_b & V3D_PROJ_TEST_CLIP_NEAR)) { - return false; - } - - /* Both too far, ignore. */ - if ((status_a & V3D_PROJ_TEST_CLIP_FAR) && (status_b & V3D_PROJ_TEST_CLIP_FAR)) { - return false; - } - - /* Simple cases have been ruled out, clip by viewport planes, then re-project. */ - float v_a_clip[3], v_b_clip[3]; - if (!clip_segment_v3_plane_n( - v_a, v_b, content_planes, content_planes_len, v_a_clip, v_b_clip)) { - return false; - } - - if ((ED_view3d_project_float_object(region, v_a_clip, r_screen_co_a, clip_flag_nowin) != - V3D_PROJ_RET_OK) || - (ED_view3d_project_float_object(region, v_b_clip, r_screen_co_b, clip_flag_nowin) != - V3D_PROJ_RET_OK)) { - return false; - } - - /* No need for #V3D_PROJ_TEST_CLIP_WIN check here, - * clipping the segment by planes handle this. */ - } - - return true; -} - -/** - * Project an edge, points that fail to project are tagged with #IS_CLIPPED. - */ -static bool view3d_project_segment_to_screen_with_clip_tag(const ARegion *region, - const float v_a[3], - const float v_b[3], - const eV3DProjTest clip_flag, - /* Output. */ - float r_screen_co_a[2], - float r_screen_co_b[2]) -{ - int count = 0; - - if (ED_view3d_project_float_object(region, v_a, r_screen_co_a, clip_flag) == V3D_PROJ_RET_OK) { - count++; - } - else { - r_screen_co_a[0] = IS_CLIPPED; /* weak */ - /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ - } - - if (ED_view3d_project_float_object(region, v_b, r_screen_co_b, clip_flag) == V3D_PROJ_RET_OK) { - count++; - } - else { - r_screen_co_b[0] = IS_CLIPPED; /* weak */ - /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ - } - - /* Caller may want to know this value, for now it's not needed. */ - return count != 0; -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Private User Data Structures - * \{ */ - -typedef struct foreachScreenObjectVert_userData { - void (*func)(void *userData, MVert *mv, const float screen_co[2], int index); - void *userData; - ViewContext vc; - MVert *verts; - const bool *hide_vert; - eV3DProjTest clip_flag; -} foreachScreenObjectVert_userData; - -typedef struct foreachScreenVert_userData { - void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index); - void *userData; - ViewContext vc; - eV3DProjTest clip_flag; -} foreachScreenVert_userData; - -/* user data structures for derived mesh callbacks */ -typedef struct foreachScreenEdge_userData { - void (*func)(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index); - void *userData; - ViewContext vc; - eV3DProjTest clip_flag; - - rctf win_rect; /* copy of: vc.region->winx/winy, use for faster tests, minx/y will always be 0 */ - - /** - * Clip plans defined by the view bounds, - * use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. - */ - float content_planes[6][4]; - int content_planes_len; -} foreachScreenEdge_userData; - -typedef struct foreachScreenFace_userData { - void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index); - void *userData; - ViewContext vc; - eV3DProjTest clip_flag; -} foreachScreenFace_userData; - -/** - * \note foreach functions should be called while drawing or directly after - * if not, #ED_view3d_init_mats_rv3d() can be used for selection tools - * but would not give correct results with dupli's for eg. which don't - * use the object matrix in the usual way. - */ - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Vertex - * \{ */ - -static void meshobject_foreachScreenVert__mapFunc(void *userData, - int index, - const float co[3], - const float UNUSED(no[3])) -{ - foreachScreenObjectVert_userData *data = userData; - if (data->hide_vert && data->hide_vert[index]) { - return; - } - MVert *mv = &data->verts[index]; - - float screen_co[2]; - - if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, mv, screen_co, index); -} - -void meshobject_foreachScreenVert( - ViewContext *vc, - void (*func)(void *userData, MVert *eve, const float screen_co[2], int index), - void *userData, - eV3DProjTest clip_flag) -{ - BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); - foreachScreenObjectVert_userData data; - Mesh *me; - - Scene *scene_eval = DEG_get_evaluated_scene(vc->depsgraph); - Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); - - me = mesh_get_eval_final(vc->depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - data.verts = BKE_mesh_verts_for_write((Mesh *)vc->obact->data); - data.hide_vert = (const bool *)CustomData_get_layer_named( - &me->vdata, CD_PROP_BOOL, ".hide_vert"); - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obact->obmat); - } - - BKE_mesh_foreach_mapped_vert(me, meshobject_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); -} - -static void mesh_foreachScreenVert__mapFunc(void *userData, - int index, - const float co[3], - const float UNUSED(no[3])) -{ - foreachScreenVert_userData *data = userData; - BMVert *eve = BM_vert_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(eve, BM_ELEM_HIDDEN))) { - return; - } - - float screen_co[2]; - if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, eve, screen_co, index); -} - -void mesh_foreachScreenVert( - ViewContext *vc, - void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index), - void *userData, - eV3DProjTest clip_flag) -{ - foreachScreenVert_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ - } - - BM_mesh_elem_table_ensure(vc->em->bm, BM_VERT); - BKE_mesh_foreach_mapped_vert(me, mesh_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Mesh Edge - * \{ */ - -static void mesh_foreachScreenEdge__mapFunc(void *userData, - int index, - const float v_a[3], - const float v_b[3]) -{ - foreachScreenEdge_userData *data = userData; - BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { - return; - } - - float screen_co_a[2], screen_co_b[2]; - if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, - v_a, - v_b, - data->clip_flag, - &data->win_rect, - data->content_planes, - data->content_planes_len, - screen_co_a, - screen_co_b)) { - return; - } - - data->func(data->userData, eed, screen_co_a, screen_co_b, index); -} - -void mesh_foreachScreenEdge(ViewContext *vc, - void (*func)(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index), - void *userData, - eV3DProjTest clip_flag) -{ - foreachScreenEdge_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - - data.win_rect.xmin = 0; - data.win_rect.ymin = 0; - data.win_rect.xmax = vc->region->winx; - data.win_rect.ymax = vc->region->winy; - - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ - } - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - data.content_planes_len = content_planes_from_clip_flag( - vc->region, vc->obedit, clip_flag, data.content_planes); - } - else { - data.content_planes_len = 0; - } - - BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); - BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Edge (Bounding Box Clipped) - * \{ */ - -/** - * Only call for bound-box clipping. - * Otherwise call #mesh_foreachScreenEdge__mapFunc - */ -static void mesh_foreachScreenEdge_clip_bb_segment__mapFunc(void *userData, - int index, - const float v_a[3], - const float v_b[3]) -{ - foreachScreenEdge_userData *data = userData; - BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { - return; - } - - BLI_assert(data->clip_flag & V3D_PROJ_TEST_CLIP_BB); - - float v_a_clip[3], v_b_clip[3]; - if (!clip_segment_v3_plane_n(v_a, v_b, data->vc.rv3d->clip_local, 4, v_a_clip, v_b_clip)) { - return; - } - - float screen_co_a[2], screen_co_b[2]; - if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, - v_a_clip, - v_b_clip, - data->clip_flag, - &data->win_rect, - data->content_planes, - data->content_planes_len, - screen_co_a, - screen_co_b)) { - return; - } - - data->func(data->userData, eed, screen_co_a, screen_co_b, index); -} - -void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc, - void (*func)(void *userData, - BMEdge *eed, - const float screen_co_a[2], - const float screen_co_b[2], - int index), - void *userData, - eV3DProjTest clip_flag) -{ - foreachScreenEdge_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - - data.win_rect.xmin = 0; - data.win_rect.ymin = 0; - data.win_rect.xmax = vc->region->winx; - data.win_rect.ymax = vc->region->winy; - - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - data.content_planes_len = content_planes_from_clip_flag( - vc->region, vc->obedit, clip_flag, data.content_planes); - } - else { - data.content_planes_len = 0; - } - - BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); - - if ((clip_flag & V3D_PROJ_TEST_CLIP_BB) && (vc->rv3d->clipbb != NULL)) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups. */ - BKE_mesh_foreach_mapped_edge( - me, vc->em->bm->totedge, mesh_foreachScreenEdge_clip_bb_segment__mapFunc, &data); - } - else { - BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Mesh: For Each Screen Face Center - * \{ */ - -static void mesh_foreachScreenFace__mapFunc(void *userData, - int index, - const float cent[3], - const float UNUSED(no[3])) -{ - foreachScreenFace_userData *data = userData; - BMFace *efa = BM_face_at_index(data->vc.em->bm, index); - if (UNLIKELY(BM_elem_flag_test(efa, BM_ELEM_HIDDEN))) { - return; - } - - float screen_co[2]; - if (ED_view3d_project_float_object(data->vc.region, cent, screen_co, data->clip_flag) != - V3D_PROJ_RET_OK) { - return; - } - - data->func(data->userData, efa, screen_co, index); -} - -void mesh_foreachScreenFace( - ViewContext *vc, - void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index), - void *userData, - const eV3DProjTest clip_flag) -{ - BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); - foreachScreenFace_userData data; - - Mesh *me = editbmesh_get_eval_cage_from_orig( - vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); - me = BKE_mesh_wrapper_ensure_subdivision(me); - ED_view3d_check_mats_rv3d(vc->rv3d); - - data.vc = *vc; - data.func = func; - data.userData = userData; - data.clip_flag = clip_flag; - - BM_mesh_elem_table_ensure(vc->em->bm, BM_FACE); - - if (me->runtime.subsurf_face_dot_tags != NULL) { - BKE_mesh_foreach_mapped_subdiv_face_center( - me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); - } - else { - BKE_mesh_foreach_mapped_face_center( - me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Nurbs: For Each Screen Vertex - * \{ */ - -void nurbs_foreachScreenVert(ViewContext *vc, - void (*func)(void *userData, - Nurb *nu, - BPoint *bp, - BezTriple *bezt, - int beztindex, - bool handles_visible, - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - Curve *cu = vc->obedit->data; - Nurb *nu; - int i; - ListBase *nurbs = BKE_curve_editNurbs_get(cu); - /* If no point in the triple is selected, the handles are invisible. */ - const bool only_selected = (vc->v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); - - ED_view3d_check_mats_rv3d(vc->rv3d); - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ - } - - for (nu = nurbs->first; nu; nu = nu->next) { - if (nu->type == CU_BEZIER) { - for (i = 0; i < nu->pntsu; i++) { - BezTriple *bezt = &nu->bezt[i]; - - if (bezt->hide == 0) { - const bool handles_visible = (vc->v3d->overlay.handle_display != CURVE_HANDLE_NONE) && - (!only_selected || BEZT_ISSEL_ANY(bezt)); - float screen_co[2]; - - if (!handles_visible) { - if (ED_view3d_project_float_object(vc->region, - bezt->vec[1], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 1, false, screen_co); - } - } - else { - if (ED_view3d_project_float_object(vc->region, - bezt->vec[0], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 0, true, screen_co); - } - if (ED_view3d_project_float_object(vc->region, - bezt->vec[1], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 1, true, screen_co); - } - if (ED_view3d_project_float_object(vc->region, - bezt->vec[2], - screen_co, - V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, NULL, bezt, 2, true, screen_co); - } - } - } - } - } - else { - for (i = 0; i < nu->pntsu * nu->pntsv; i++) { - BPoint *bp = &nu->bp[i]; - - if (bp->hide == 0) { - float screen_co[2]; - if (ED_view3d_project_float_object( - vc->region, bp->vec, screen_co, V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN) == - V3D_PROJ_RET_OK) { - func(userData, nu, bp, NULL, -1, false, screen_co); - } - } - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Meta: For Each Screen Meta-Element - * \{ */ - -void mball_foreachScreenElem(struct ViewContext *vc, - void (*func)(void *userData, - struct MetaElem *ml, - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - MetaBall *mb = (MetaBall *)vc->obedit->data; - MetaElem *ml; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - for (ml = mb->editelems->first; ml; ml = ml->next) { - float screen_co[2]; - if (ED_view3d_project_float_object(vc->region, &ml->x, screen_co, clip_flag) == - V3D_PROJ_RET_OK) { - func(userData, ml, screen_co); - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Lattice: For Each Screen Vertex - * \{ */ - -void lattice_foreachScreenVert(ViewContext *vc, - void (*func)(void *userData, BPoint *bp, const float screen_co[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - Object *obedit = vc->obedit; - Lattice *lt = obedit->data; - BPoint *bp = lt->editlatt->latt->def; - DispList *dl = obedit->runtime.curve_cache ? - BKE_displist_find(&obedit->runtime.curve_cache->disp, DL_VERTS) : - NULL; - const float *co = dl ? dl->verts : NULL; - int i, N = lt->editlatt->latt->pntsu * lt->editlatt->latt->pntsv * lt->editlatt->latt->pntsw; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { - ED_view3d_clipping_local(vc->rv3d, obedit->obmat); /* for local clipping lookups */ - } - - for (i = 0; i < N; i++, bp++, co += 3) { - if (bp->hide == 0) { - float screen_co[2]; - if (ED_view3d_project_float_object(vc->region, dl ? co : bp->vec, screen_co, clip_flag) == - V3D_PROJ_RET_OK) { - func(userData, bp, screen_co); - } - } - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Edit-Armature: For Each Screen Bone - * \{ */ - -void armature_foreachScreenBone(struct ViewContext *vc, - void (*func)(void *userData, - struct EditBone *ebone, - const float screen_co_a[2], - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - bArmature *arm = vc->obedit->data; - EditBone *ebone; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - float content_planes[6][4]; - int content_planes_len; - rctf win_rect; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - content_planes_len = content_planes_from_clip_flag( - vc->region, vc->obedit, clip_flag, content_planes); - win_rect.xmin = 0; - win_rect.ymin = 0; - win_rect.xmax = vc->region->winx; - win_rect.ymax = vc->region->winy; - } - else { - content_planes_len = 0; - } - - for (ebone = arm->edbo->first; ebone; ebone = ebone->next) { - if (!EBONE_VISIBLE(arm, ebone)) { - continue; - } - - float screen_co_a[2], screen_co_b[2]; - const float *v_a = ebone->head, *v_b = ebone->tail; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, - v_a, - v_b, - clip_flag, - &win_rect, - content_planes, - content_planes_len, - screen_co_a, - screen_co_b)) { - continue; - } - } - else { - if (!view3d_project_segment_to_screen_with_clip_tag( - vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { - continue; - } - } - - func(userData, ebone, screen_co_a, screen_co_b); - } -} - -/** \} */ - -/* -------------------------------------------------------------------- */ -/** \name Pose: For Each Screen Bone - * \{ */ - -void pose_foreachScreenBone(struct ViewContext *vc, - void (*func)(void *userData, - struct bPoseChannel *pchan, - const float screen_co_a[2], - const float screen_co_b[2]), - void *userData, - const eV3DProjTest clip_flag) -{ - /* Almost _exact_ copy of #armature_foreachScreenBone */ - - const Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); - const bArmature *arm_eval = ob_eval->data; - bPose *pose = vc->obact->pose; - bPoseChannel *pchan; - - ED_view3d_check_mats_rv3d(vc->rv3d); - - float content_planes[6][4]; - int content_planes_len; - rctf win_rect; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - content_planes_len = content_planes_from_clip_flag( - vc->region, ob_eval, clip_flag, content_planes); - win_rect.xmin = 0; - win_rect.ymin = 0; - win_rect.xmax = vc->region->winx; - win_rect.ymax = vc->region->winy; - } - else { - content_planes_len = 0; - } - - for (pchan = pose->chanbase.first; pchan; pchan = pchan->next) { - if (!PBONE_VISIBLE(arm_eval, pchan->bone)) { - continue; - } - - bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); - float screen_co_a[2], screen_co_b[2]; - const float *v_a = pchan_eval->pose_head, *v_b = pchan_eval->pose_tail; - - if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { - if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, - v_a, - v_b, - clip_flag, - &win_rect, - content_planes, - content_planes_len, - screen_co_a, - screen_co_b)) { - continue; - } - } - else { - if (!view3d_project_segment_to_screen_with_clip_tag( - vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { - continue; - } - } - - func(userData, pchan, screen_co_a, screen_co_b); - } -} - -/** \} */ diff --git a/source/blender/editors/space_view3d/view3d_iterators.cc b/source/blender/editors/space_view3d/view3d_iterators.cc new file mode 100644 index 00000000000..139ac9de6e4 --- /dev/null +++ b/source/blender/editors/space_view3d/view3d_iterators.cc @@ -0,0 +1,886 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup spview3d + */ + +#include "DNA_armature_types.h" +#include "DNA_curve_types.h" +#include "DNA_lattice_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_meta_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BLI_math_geom.h" +#include "BLI_rect.h" +#include "BLI_utildefines.h" + +#include "BKE_DerivedMesh.h" +#include "BKE_action.h" +#include "BKE_armature.h" +#include "BKE_curve.h" +#include "BKE_displist.h" +#include "BKE_editmesh.h" +#include "BKE_mesh.h" +#include "BKE_mesh_iterators.h" +#include "BKE_mesh_runtime.h" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" + +#include "DEG_depsgraph.h" +#include "DEG_depsgraph_query.h" + +#include "bmesh.h" + +#include "ED_armature.h" +#include "ED_screen.h" +#include "ED_view3d.h" + +/* -------------------------------------------------------------------- */ +/** \name Internal Clipping Utilities + * \{ */ + +/** + * Calculate clipping planes to use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. + * + * Planes are selected from the viewpoint using `clip_flag` + * to detect which planes should be applied (maximum 6). + * + * \return The number of planes written into `planes`. + */ +static int content_planes_from_clip_flag(const ARegion *region, + const Object *ob, + const eV3DProjTest clip_flag, + float planes[6][4]) +{ + BLI_assert(clip_flag & V3D_PROJ_TEST_CLIP_CONTENT); + + float *clip_xmin = nullptr, *clip_xmax = nullptr; + float *clip_ymin = nullptr, *clip_ymax = nullptr; + float *clip_zmin = nullptr, *clip_zmax = nullptr; + + int planes_len = 0; + + /* The order of `planes` has been selected based on the likelihood of points being fully + * outside the plane to increase the chance of an early exit in #clip_segment_v3_plane_n. + * With "near" being most likely and "far" being unlikely. + * + * Otherwise the order of axes in `planes` isn't significant. */ + + if (clip_flag & V3D_PROJ_TEST_CLIP_NEAR) { + clip_zmin = planes[planes_len++]; + } + if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { + clip_xmin = planes[planes_len++]; + clip_xmax = planes[planes_len++]; + clip_ymin = planes[planes_len++]; + clip_ymax = planes[planes_len++]; + } + if (clip_flag & V3D_PROJ_TEST_CLIP_FAR) { + clip_zmax = planes[planes_len++]; + } + + BLI_assert(planes_len <= 6); + if (planes_len != 0) { + RegionView3D *rv3d = static_cast(region->regiondata); + float projmat[4][4]; + ED_view3d_ob_project_mat_get(rv3d, ob, projmat); + planes_from_projmat(projmat, clip_xmin, clip_xmax, clip_ymin, clip_ymax, clip_zmin, clip_zmax); + } + return planes_len; +} + +/** + * Edge projection is more involved since part of the edge may be behind the view + * or extend beyond the far limits. In the case of single points, these can be ignored. + * However it just may still be visible on screen, so constrained the edge to planes + * defined by the port to ensure both ends of the edge can be projected, see T32214. + * + * \note This is unrelated to #V3D_PROJ_TEST_CLIP_BB which must be checked separately. + */ +static bool view3d_project_segment_to_screen_with_content_clip_planes( + const ARegion *region, + const float v_a[3], + const float v_b[3], + const eV3DProjTest clip_flag, + const rctf *win_rect, + const float content_planes[][4], + const int content_planes_len, + /* Output. */ + float r_screen_co_a[2], + float r_screen_co_b[2]) +{ + /* Clipping already handled, no need to check in projection. */ + eV3DProjTest clip_flag_nowin = clip_flag & ~V3D_PROJ_TEST_CLIP_WIN; + + const eV3DProjStatus status_a = ED_view3d_project_float_object( + region, v_a, r_screen_co_a, clip_flag_nowin); + const eV3DProjStatus status_b = ED_view3d_project_float_object( + region, v_b, r_screen_co_b, clip_flag_nowin); + + if ((status_a == V3D_PROJ_RET_OK) && (status_b == V3D_PROJ_RET_OK)) { + if (clip_flag & V3D_PROJ_TEST_CLIP_WIN) { + if (!BLI_rctf_isect_segment(win_rect, r_screen_co_a, r_screen_co_b)) { + return false; + } + } + } + else { + if (content_planes_len == 0) { + return false; + } + + /* Both too near, ignore. */ + if ((status_a & V3D_PROJ_TEST_CLIP_NEAR) && (status_b & V3D_PROJ_TEST_CLIP_NEAR)) { + return false; + } + + /* Both too far, ignore. */ + if ((status_a & V3D_PROJ_TEST_CLIP_FAR) && (status_b & V3D_PROJ_TEST_CLIP_FAR)) { + return false; + } + + /* Simple cases have been ruled out, clip by viewport planes, then re-project. */ + float v_a_clip[3], v_b_clip[3]; + if (!clip_segment_v3_plane_n( + v_a, v_b, content_planes, content_planes_len, v_a_clip, v_b_clip)) { + return false; + } + + if ((ED_view3d_project_float_object(region, v_a_clip, r_screen_co_a, clip_flag_nowin) != + V3D_PROJ_RET_OK) || + (ED_view3d_project_float_object(region, v_b_clip, r_screen_co_b, clip_flag_nowin) != + V3D_PROJ_RET_OK)) { + return false; + } + + /* No need for #V3D_PROJ_TEST_CLIP_WIN check here, + * clipping the segment by planes handle this. */ + } + + return true; +} + +/** + * Project an edge, points that fail to project are tagged with #IS_CLIPPED. + */ +static bool view3d_project_segment_to_screen_with_clip_tag(const ARegion *region, + const float v_a[3], + const float v_b[3], + const eV3DProjTest clip_flag, + /* Output. */ + float r_screen_co_a[2], + float r_screen_co_b[2]) +{ + int count = 0; + + if (ED_view3d_project_float_object(region, v_a, r_screen_co_a, clip_flag) == V3D_PROJ_RET_OK) { + count++; + } + else { + r_screen_co_a[0] = IS_CLIPPED; /* weak */ + /* screen_co_a[1]: intentionally don't set this so we get errors on misuse */ + } + + if (ED_view3d_project_float_object(region, v_b, r_screen_co_b, clip_flag) == V3D_PROJ_RET_OK) { + count++; + } + else { + r_screen_co_b[0] = IS_CLIPPED; /* weak */ + /* screen_co_b[1]: intentionally don't set this so we get errors on misuse */ + } + + /* Caller may want to know this value, for now it's not needed. */ + return count != 0; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Private User Data Structures + * \{ */ + +struct foreachScreenObjectVert_userData { + void (*func)(void *userData, MVert *mv, const float screen_co[2], int index); + void *userData; + ViewContext vc; + MVert *verts; + const bool *hide_vert; + eV3DProjTest clip_flag; +}; + +struct foreachScreenVert_userData { + void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index); + void *userData; + ViewContext vc; + eV3DProjTest clip_flag; +}; + +/* user data structures for derived mesh callbacks */ +struct foreachScreenEdge_userData { + void (*func)(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index); + void *userData; + ViewContext vc; + eV3DProjTest clip_flag; + + rctf win_rect; /* copy of: vc.region->winx/winy, use for faster tests, minx/y will always be 0 */ + + /** + * Clip plans defined by the view bounds, + * use when #V3D_PROJ_TEST_CLIP_CONTENT is enabled. + */ + float content_planes[6][4]; + int content_planes_len; +}; + +struct foreachScreenFace_userData { + void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index); + void *userData; + ViewContext vc; + eV3DProjTest clip_flag; +}; + +/** + * \note foreach functions should be called while drawing or directly after + * if not, #ED_view3d_init_mats_rv3d() can be used for selection tools + * but would not give correct results with dupli's for eg. which don't + * use the object matrix in the usual way. + */ + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Vertex + * \{ */ + +static void meshobject_foreachScreenVert__mapFunc(void *userData, + int index, + const float co[3], + const float UNUSED(no[3])) +{ + foreachScreenObjectVert_userData *data = static_cast( + userData); + if (data->hide_vert && data->hide_vert[index]) { + return; + } + MVert *mv = &data->verts[index]; + + float screen_co[2]; + + if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; + } + + data->func(data->userData, mv, screen_co, index); +} + +void meshobject_foreachScreenVert( + ViewContext *vc, + void (*func)(void *userData, MVert *eve, const float screen_co[2], int index), + void *userData, + eV3DProjTest clip_flag) +{ + BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); + foreachScreenObjectVert_userData data; + Mesh *me; + + Scene *scene_eval = DEG_get_evaluated_scene(vc->depsgraph); + Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); + + me = mesh_get_eval_final(vc->depsgraph, scene_eval, ob_eval, &CD_MASK_BAREMESH); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + data.verts = BKE_mesh_verts_for_write((Mesh *)vc->obact->data); + data.hide_vert = (const bool *)CustomData_get_layer_named( + &me->vdata, CD_PROP_BOOL, ".hide_vert"); + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obact->obmat); + } + + BKE_mesh_foreach_mapped_vert(me, meshobject_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); +} + +static void mesh_foreachScreenVert__mapFunc(void *userData, + int index, + const float co[3], + const float UNUSED(no[3])) +{ + foreachScreenVert_userData *data = static_cast(userData); + BMVert *eve = BM_vert_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eve, BM_ELEM_HIDDEN))) { + return; + } + + float screen_co[2]; + if (ED_view3d_project_float_object(data->vc.region, co, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; + } + + data->func(data->userData, eve, screen_co, index); +} + +void mesh_foreachScreenVert( + ViewContext *vc, + void (*func)(void *userData, BMVert *eve, const float screen_co[2], int index), + void *userData, + eV3DProjTest clip_flag) +{ + foreachScreenVert_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ + } + + BM_mesh_elem_table_ensure(vc->em->bm, BM_VERT); + BKE_mesh_foreach_mapped_vert(me, mesh_foreachScreenVert__mapFunc, &data, MESH_FOREACH_NOP); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Mesh Edge + * \{ */ + +static void mesh_foreachScreenEdge__mapFunc(void *userData, + int index, + const float v_a[3], + const float v_b[3]) +{ + foreachScreenEdge_userData *data = static_cast(userData); + BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { + return; + } + + float screen_co_a[2], screen_co_b[2]; + if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, + v_a, + v_b, + data->clip_flag, + &data->win_rect, + data->content_planes, + data->content_planes_len, + screen_co_a, + screen_co_b)) { + return; + } + + data->func(data->userData, eed, screen_co_a, screen_co_b, index); +} + +void mesh_foreachScreenEdge(ViewContext *vc, + void (*func)(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index), + void *userData, + eV3DProjTest clip_flag) +{ + foreachScreenEdge_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + + data.win_rect.xmin = 0; + data.win_rect.ymin = 0; + data.win_rect.xmax = vc->region->winx; + data.win_rect.ymax = vc->region->winy; + + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ + } + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + data.content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, data.content_planes); + } + else { + data.content_planes_len = 0; + } + + BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); + BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Edge (Bounding Box Clipped) + * \{ */ + +/** + * Only call for bound-box clipping. + * Otherwise call #mesh_foreachScreenEdge__mapFunc + */ +static void mesh_foreachScreenEdge_clip_bb_segment__mapFunc(void *userData, + int index, + const float v_a[3], + const float v_b[3]) +{ + foreachScreenEdge_userData *data = static_cast(userData); + BMEdge *eed = BM_edge_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(eed, BM_ELEM_HIDDEN))) { + return; + } + + BLI_assert(data->clip_flag & V3D_PROJ_TEST_CLIP_BB); + + float v_a_clip[3], v_b_clip[3]; + if (!clip_segment_v3_plane_n(v_a, v_b, data->vc.rv3d->clip_local, 4, v_a_clip, v_b_clip)) { + return; + } + + float screen_co_a[2], screen_co_b[2]; + if (!view3d_project_segment_to_screen_with_content_clip_planes(data->vc.region, + v_a_clip, + v_b_clip, + data->clip_flag, + &data->win_rect, + data->content_planes, + data->content_planes_len, + screen_co_a, + screen_co_b)) { + return; + } + + data->func(data->userData, eed, screen_co_a, screen_co_b, index); +} + +void mesh_foreachScreenEdge_clip_bb_segment(ViewContext *vc, + void (*func)(void *userData, + BMEdge *eed, + const float screen_co_a[2], + const float screen_co_b[2], + int index), + void *userData, + eV3DProjTest clip_flag) +{ + foreachScreenEdge_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + + data.win_rect.xmin = 0; + data.win_rect.ymin = 0; + data.win_rect.xmax = vc->region->winx; + data.win_rect.ymax = vc->region->winy; + + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + data.content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, data.content_planes); + } + else { + data.content_planes_len = 0; + } + + BM_mesh_elem_table_ensure(vc->em->bm, BM_EDGE); + + if ((clip_flag & V3D_PROJ_TEST_CLIP_BB) && (vc->rv3d->clipbb != nullptr)) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups. */ + BKE_mesh_foreach_mapped_edge( + me, vc->em->bm->totedge, mesh_foreachScreenEdge_clip_bb_segment__mapFunc, &data); + } + else { + BKE_mesh_foreach_mapped_edge(me, vc->em->bm->totedge, mesh_foreachScreenEdge__mapFunc, &data); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Mesh: For Each Screen Face Center + * \{ */ + +static void mesh_foreachScreenFace__mapFunc(void *userData, + int index, + const float cent[3], + const float UNUSED(no[3])) +{ + foreachScreenFace_userData *data = static_cast(userData); + BMFace *efa = BM_face_at_index(data->vc.em->bm, index); + if (UNLIKELY(BM_elem_flag_test(efa, BM_ELEM_HIDDEN))) { + return; + } + + float screen_co[2]; + if (ED_view3d_project_float_object(data->vc.region, cent, screen_co, data->clip_flag) != + V3D_PROJ_RET_OK) { + return; + } + + data->func(data->userData, efa, screen_co, index); +} + +void mesh_foreachScreenFace( + ViewContext *vc, + void (*func)(void *userData, BMFace *efa, const float screen_co_b[2], int index), + void *userData, + const eV3DProjTest clip_flag) +{ + BLI_assert((clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) == 0); + foreachScreenFace_userData data; + + Mesh *me = editbmesh_get_eval_cage_from_orig( + vc->depsgraph, vc->scene, vc->obedit, &CD_MASK_BAREMESH); + me = BKE_mesh_wrapper_ensure_subdivision(me); + ED_view3d_check_mats_rv3d(vc->rv3d); + + data.vc = *vc; + data.func = func; + data.userData = userData; + data.clip_flag = clip_flag; + + BM_mesh_elem_table_ensure(vc->em->bm, BM_FACE); + + if (me->runtime.subsurf_face_dot_tags != nullptr) { + BKE_mesh_foreach_mapped_subdiv_face_center( + me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); + } + else { + BKE_mesh_foreach_mapped_face_center( + me, mesh_foreachScreenFace__mapFunc, &data, MESH_FOREACH_NOP); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Nurbs: For Each Screen Vertex + * \{ */ + +void nurbs_foreachScreenVert(ViewContext *vc, + void (*func)(void *userData, + Nurb *nu, + BPoint *bp, + BezTriple *bezt, + int beztindex, + bool handles_visible, + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + Curve *cu = static_cast(vc->obedit->data); + int i; + ListBase *nurbs = BKE_curve_editNurbs_get(cu); + /* If no point in the triple is selected, the handles are invisible. */ + const bool only_selected = (vc->v3d->overlay.handle_display == CURVE_HANDLE_SELECTED); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, vc->obedit->obmat); /* for local clipping lookups */ + } + + LISTBASE_FOREACH (Nurb *, nu, nurbs) { + if (nu->type == CU_BEZIER) { + for (i = 0; i < nu->pntsu; i++) { + BezTriple *bezt = &nu->bezt[i]; + + if (bezt->hide == 0) { + const bool handles_visible = (vc->v3d->overlay.handle_display != CURVE_HANDLE_NONE) && + (!only_selected || BEZT_ISSEL_ANY(bezt)); + float screen_co[2]; + + if (!handles_visible) { + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[1], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 1, false, screen_co); + } + } + else { + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[0], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 0, true, screen_co); + } + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[1], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 1, true, screen_co); + } + if (ED_view3d_project_float_object( + vc->region, + bezt->vec[2], + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == + V3D_PROJ_RET_OK) { + func(userData, nu, nullptr, bezt, 2, true, screen_co); + } + } + } + } + } + else { + for (i = 0; i < nu->pntsu * nu->pntsv; i++) { + BPoint *bp = &nu->bp[i]; + + if (bp->hide == 0) { + float screen_co[2]; + if (ED_view3d_project_float_object( + vc->region, + bp->vec, + screen_co, + eV3DProjTest(V3D_PROJ_RET_CLIP_BB | V3D_PROJ_RET_CLIP_WIN)) == V3D_PROJ_RET_OK) { + func(userData, nu, bp, nullptr, -1, false, screen_co); + } + } + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Meta: For Each Screen Meta-Element + * \{ */ + +void mball_foreachScreenElem(ViewContext *vc, + void (*func)(void *userData, + MetaElem *ml, + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + MetaBall *mb = (MetaBall *)vc->obedit->data; + + ED_view3d_check_mats_rv3d(vc->rv3d); + + LISTBASE_FOREACH (MetaElem *, ml, mb->editelems) { + float screen_co[2]; + if (ED_view3d_project_float_object(vc->region, &ml->x, screen_co, clip_flag) == + V3D_PROJ_RET_OK) { + func(userData, ml, screen_co); + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Lattice: For Each Screen Vertex + * \{ */ + +void lattice_foreachScreenVert(ViewContext *vc, + void (*func)(void *userData, BPoint *bp, const float screen_co[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + Object *obedit = vc->obedit; + Lattice *lt = static_cast(obedit->data); + BPoint *bp = lt->editlatt->latt->def; + DispList *dl = obedit->runtime.curve_cache ? + BKE_displist_find(&obedit->runtime.curve_cache->disp, DL_VERTS) : + nullptr; + const float *co = dl ? dl->verts : nullptr; + int i, N = lt->editlatt->latt->pntsu * lt->editlatt->latt->pntsv * lt->editlatt->latt->pntsw; + + ED_view3d_check_mats_rv3d(vc->rv3d); + + if (clip_flag & V3D_PROJ_TEST_CLIP_BB) { + ED_view3d_clipping_local(vc->rv3d, obedit->obmat); /* for local clipping lookups */ + } + + for (i = 0; i < N; i++, bp++, co += 3) { + if (bp->hide == 0) { + float screen_co[2]; + if (ED_view3d_project_float_object(vc->region, dl ? co : bp->vec, screen_co, clip_flag) == + V3D_PROJ_RET_OK) { + func(userData, bp, screen_co); + } + } + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Edit-Armature: For Each Screen Bone + * \{ */ + +void armature_foreachScreenBone(ViewContext *vc, + void (*func)(void *userData, + EditBone *ebone, + const float screen_co_a[2], + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + bArmature *arm = static_cast(vc->obedit->data); + + ED_view3d_check_mats_rv3d(vc->rv3d); + + float content_planes[6][4]; + int content_planes_len; + rctf win_rect; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + content_planes_len = content_planes_from_clip_flag( + vc->region, vc->obedit, clip_flag, content_planes); + win_rect.xmin = 0; + win_rect.ymin = 0; + win_rect.xmax = vc->region->winx; + win_rect.ymax = vc->region->winy; + } + else { + content_planes_len = 0; + } + + LISTBASE_FOREACH (EditBone *, ebone, arm->edbo) { + if (!EBONE_VISIBLE(arm, ebone)) { + continue; + } + + float screen_co_a[2], screen_co_b[2]; + const float *v_a = ebone->head, *v_b = ebone->tail; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, + v_a, + v_b, + clip_flag, + &win_rect, + content_planes, + content_planes_len, + screen_co_a, + screen_co_b)) { + continue; + } + } + else { + if (!view3d_project_segment_to_screen_with_clip_tag( + vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { + continue; + } + } + + func(userData, ebone, screen_co_a, screen_co_b); + } +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Pose: For Each Screen Bone + * \{ */ + +void pose_foreachScreenBone(ViewContext *vc, + void (*func)(void *userData, + bPoseChannel *pchan, + const float screen_co_a[2], + const float screen_co_b[2]), + void *userData, + const eV3DProjTest clip_flag) +{ + /* Almost _exact_ copy of #armature_foreachScreenBone */ + + const Object *ob_eval = DEG_get_evaluated_object(vc->depsgraph, vc->obact); + const bArmature *arm_eval = static_cast(ob_eval->data); + bPose *pose = vc->obact->pose; + + ED_view3d_check_mats_rv3d(vc->rv3d); + + float content_planes[6][4]; + int content_planes_len; + rctf win_rect; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + content_planes_len = content_planes_from_clip_flag( + vc->region, ob_eval, clip_flag, content_planes); + win_rect.xmin = 0; + win_rect.ymin = 0; + win_rect.xmax = vc->region->winx; + win_rect.ymax = vc->region->winy; + } + else { + content_planes_len = 0; + } + + LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) { + if (!PBONE_VISIBLE(arm_eval, pchan->bone)) { + continue; + } + + bPoseChannel *pchan_eval = BKE_pose_channel_find_name(ob_eval->pose, pchan->name); + float screen_co_a[2], screen_co_b[2]; + const float *v_a = pchan_eval->pose_head, *v_b = pchan_eval->pose_tail; + + if (clip_flag & V3D_PROJ_TEST_CLIP_CONTENT) { + if (!view3d_project_segment_to_screen_with_content_clip_planes(vc->region, + v_a, + v_b, + clip_flag, + &win_rect, + content_planes, + content_planes_len, + screen_co_a, + screen_co_b)) { + continue; + } + } + else { + if (!view3d_project_segment_to_screen_with_clip_tag( + vc->region, v_a, v_b, clip_flag, screen_co_a, screen_co_b)) { + continue; + } + } + + func(userData, pchan, screen_co_a, screen_co_b); + } +} + +/** \} */ -- cgit v1.2.3