Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,229 @@
# SPDX-FileCopyrightText: 2006 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
PUBLIC .
../../../intern/eigen
../../../extern/rangetree
)
if(WITH_WEB)
list(APPEND INC
../blenkernel
../depsgraph
)
endif()
set(INC_SYS
)
set(SRC
# Naming convention for BMesh operators is: bmo_*action*_*details*.cc
operators/bmo_beautify.cc
operators/bmo_bevel.cc
operators/bmo_bisect_plane.cc
operators/bmo_bridge.cc
operators/bmo_circularize.cc
operators/bmo_connect.cc
operators/bmo_connect_concave.cc
operators/bmo_connect_nonplanar.cc
operators/bmo_connect_pair.cc
operators/bmo_create.cc
operators/bmo_dissolve.cc
operators/bmo_dupe.cc
operators/bmo_edgenet.cc
operators/bmo_extrude.cc
operators/bmo_fill_attribute.cc
operators/bmo_fill_edgeloop.cc
operators/bmo_fill_grid.cc
operators/bmo_fill_holes.cc
operators/bmo_flatten.cc
operators/bmo_hull.cc
operators/bmo_inset.cc
operators/bmo_join_triangles.cc
operators/bmo_mesh_convert.cc
operators/bmo_mirror.cc
operators/bmo_normals.cc
operators/bmo_offset_edgeloops.cc
operators/bmo_planar_faces.cc
operators/bmo_poke.cc
operators/bmo_primitive.cc
operators/bmo_removedoubles.cc
operators/bmo_rotate_edges.cc
operators/bmo_smooth_laplacian.cc
operators/bmo_space_edge_loops_evenly.cc
operators/bmo_split_edges.cc
operators/bmo_subdivide.cc
operators/bmo_subdivide_edgering.cc
operators/bmo_symmetrize.cc
operators/bmo_triangulate.cc
operators/bmo_unsubdivide.cc
operators/bmo_utils.cc
operators/bmo_wireframe.cc
intern/bmesh_callback_generic.cc
intern/bmesh_callback_generic.hh
intern/bmesh_construct.cc
intern/bmesh_construct.hh
intern/bmesh_core.cc
intern/bmesh_core.hh
intern/bmesh_delete.cc
intern/bmesh_delete.hh
intern/bmesh_edgeloop.cc
intern/bmesh_edgeloop.hh
intern/bmesh_inline.hh
intern/bmesh_interp.cc
intern/bmesh_interp.hh
intern/bmesh_iterators.cc
intern/bmesh_iterators.hh
intern/bmesh_iterators_inline.hh
intern/bmesh_log.cc
intern/bmesh_log.hh
intern/bmesh_marking.cc
intern/bmesh_marking.hh
intern/bmesh_mesh.cc
intern/bmesh_mesh.hh
intern/bmesh_mesh_convert.cc
intern/bmesh_mesh_convert.hh
intern/bmesh_mesh_debug.cc
intern/bmesh_mesh_debug.hh
intern/bmesh_mesh_duplicate.cc
intern/bmesh_mesh_duplicate.hh
intern/bmesh_mesh_normals.cc
intern/bmesh_mesh_normals.hh
intern/bmesh_mesh_partial_update.cc
intern/bmesh_mesh_partial_update.hh
intern/bmesh_mesh_tessellate.cc
intern/bmesh_mesh_tessellate.hh
intern/bmesh_mesh_validate.cc
intern/bmesh_mesh_validate.hh
intern/bmesh_mods.cc
intern/bmesh_mods.hh
intern/bmesh_opdefines.cc
intern/bmesh_operator_api_inline.hh
intern/bmesh_operators.cc
intern/bmesh_operators.hh
intern/bmesh_operators_private.hh
intern/bmesh_polygon.cc
intern/bmesh_polygon.hh
intern/bmesh_polygon_edgenet.cc
intern/bmesh_polygon_edgenet.hh
intern/bmesh_private.hh
intern/bmesh_query.cc
intern/bmesh_query.hh
intern/bmesh_query_inline.hh
intern/bmesh_query_uv.cc
intern/bmesh_query_uv.hh
intern/bmesh_structure.cc
intern/bmesh_structure.hh
intern/bmesh_structure_inline.hh
intern/bmesh_uvselect.cc
intern/bmesh_uvselect.hh
intern/bmesh_walkers.cc
intern/bmesh_walkers.hh
intern/bmesh_walkers_impl.cc
intern/bmesh_walkers_private.hh
intern/bmesh_error.hh
intern/bmesh_operator_api.hh
tools/bmesh_beautify.cc
tools/bmesh_beautify.hh
tools/bmesh_bevel.cc
tools/bmesh_bevel.hh
tools/bmesh_bisect_plane.cc
tools/bmesh_bisect_plane.hh
tools/bmesh_boolean.cc
tools/bmesh_boolean.hh
tools/bmesh_decimate.hh
tools/bmesh_decimate_collapse.cc
tools/bmesh_decimate_dissolve.cc
tools/bmesh_decimate_unsubdivide.cc
tools/bmesh_edgenet.cc
tools/bmesh_edgenet.hh
tools/bmesh_edgesplit.cc
tools/bmesh_edgesplit.hh
tools/bmesh_intersect.cc
tools/bmesh_intersect.hh
tools/bmesh_intersect_edges.cc
tools/bmesh_intersect_edges.hh
tools/bmesh_path.cc
tools/bmesh_path.hh
tools/bmesh_path_region.cc
tools/bmesh_path_region.hh
tools/bmesh_path_region_uv.cc
tools/bmesh_path_region_uv.hh
tools/bmesh_path_uv.cc
tools/bmesh_path_uv.hh
tools/bmesh_region_match.cc
tools/bmesh_region_match.hh
tools/bmesh_separate.cc
tools/bmesh_separate.hh
tools/bmesh_triangulate.cc
tools/bmesh_triangulate.hh
tools/bmesh_wireframe.cc
tools/bmesh_wireframe.hh
bmesh_class.hh
# public includes
bmesh.hh
bmesh_tools.hh
)
if(WITH_WEB)
set(LIB
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::dna
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
extern_rangetree
PRIVATE bf::intern::atomic
)
else()
set(LIB
PRIVATE bf::blenkernel
PRIVATE bf::blenlib
PRIVATE bf::blentranslation
PRIVATE bf::depsgraph
PRIVATE bf::dna
PRIVATE bf::intern::clog
PRIVATE bf::intern::guardedalloc
extern_rangetree
PRIVATE bf::intern::atomic
PRIVATE bf::dependencies::optional::gmp
PRIVATE bf::dependencies::optional::tbb
PRIVATE bf::dependencies::optional::bullet
)
endif()
if(WITH_BULLET)
list(APPEND INC
../../../intern/rigidbody
)
endif()
if(WITH_FREESTYLE)
add_definitions(-DWITH_FREESTYLE)
endif()
blender_add_lib(bf_bmesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
add_library(bf::bmesh ALIAS bf_bmesh)
if(MSVC AND NOT MSVC_CLANG)
target_compile_options(bf_bmesh PRIVATE /WX /wd4101)
endif()
if(WITH_GTESTS)
set(TEST_SRC
tests/bmesh_core_test.cc
)
set(TEST_INC
)
set(TEST_LIB
bf_bmesh
)
blender_add_test_suite_lib(bmesh "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
endif()

View File

@@ -0,0 +1,212 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* \addtogroup bmesh BMesh
*
* \brief BMesh is a non-manifold boundary representation
* designed to support advanced editing operations.
* \section bm_structure The Structure
*
* BMesh stores topology in four main element structures:
*
* - Faces - BMFace
* - Loops - BMLoop, (stores per-face-vertex data, UVs, vertex-colors, etc)
* - Edges - BMEdge
* - Verts - BMVert
* \subsection bm_header_flags Header Flags
* Each element (vertex/edge/face/loop)
* in a mesh has an associated bit-field called "header flags".
*
* BMHeader flags should **never** be read or written to by bmesh operators (see Operators below).
*
* Access to header flags is done with `BM_elem_flag_*()` functions.
* \subsection bm_faces Faces
*
* Faces in BMesh are stored as a circular linked list of loops. Loops store per-face-vertex data
* (among other things outlined later in this document), and define the face boundary.
* \subsection bm_loop The Loop
*
* Loops can be thought of as a *face-corner*, since faces don't reference verts or edges directly.
* Each loop connects the face to one of its corner vertices,
* and also references an edge which connects this loop's vertex to the next loop's vertex.
*
* Loops allow faces to access their verts and edges,
* while edges and faces store their loops, allowing access in the opposite direction too.
*
* Loop pointers:
*
* - BMLoop#v - pointer to the vertex associated with this loop.
* - BMLoop#e - pointer to the edge associated with this loop,
* between verts `(loop->v, loop->next->v)`
* - BMLoop#f - pointer to the face associated with this loop.
* \subsection bm_two_side_face 2-Sided Faces
*
* There are some situations where you need 2-sided faces (e.g. a face of two vertices).
* This is supported by BMesh, but note that such faces should only be used as intermediary steps,
* and should not end up in the final mesh.
* \subsection bm_edges_and_verts Edges and Vertices
*
* Edges and Vertices in BMesh are primitive structures.
*
* \note There can be more than one edge between two vertices in BMesh,
* though the rest of Blender (i.e. DNA and evaluated Mesh) does not support this.
* So it should only occur temporarily during editing operations.
* \subsection bm_queries Queries
*
* The following topological queries are available:
*
* - Edges/Faces/Loops around a vertex.
* - Faces around an edge.
* - Loops around an edge.
*
* These are accessible through the iterator API, which is covered later in this document
*
* See source/blender/bmesh/bmesh_query.hh for more misc. queries.
* \section bm_api The BMesh API
*
* One of the goals of the BMesh API is to make it easy
* and natural to produce highly maintainable code.
* Code duplication, etc are avoided where possible.
* \subsection bm_iter_api Iterator API
*
* Most topological queries in BMesh go through an iterator API (see Queries above).
* These are defined in bmesh_iterators.hh.
* If you can, please use the #BM_ITER_MESH, #BM_ITER_ELEM macros in bmesh_iterators.hh
* \subsection bm_walker_api Walker API
*
* Topological queries that require a stack (e.g. recursive queries) go through the Walker API,
* which is defined in bmesh_walkers.hh. Currently the "walkers" are hard-coded into the API,
* though a mechanism for plugging in new walkers needs to be added at some point.
*
* Most topological queries should go through these two APIs;
* there are additional functions you can use for topological iteration,
* but their meant for internal bmesh code.
*
* Note that the walker API supports delimiter flags,
* to allow the caller to flag elements not to walk past.
* \subsection bm_ops Operators
*
* Operators are an integral part of BMesh. Unlike regular blender operators,
* BMesh operators (abbreviated to `bmo`) are designed to be nested (e.g. call other operators).
*
* Each operator has a number of input/output "slots"
* which are used to pass settings & data into/out of the operator
* (and allows for chaining operators together).
*
* These slots are identified by name, using strings.
*
* Access to slots is done with `BMO_slot_***()` functions.
* \subsection bm_tool_flags Tool Flags
*
* The BMesh API provides a set of flags for faces, edges and vertices,
* which are private to an operator.
* These flags may be used by the client operator code as needed
* (a common example is flagging elements for use in another operator).
* Each call to an operator allocates its own set of tool flags when it's executed,
* avoiding flag conflicts between operators.
*
* These flags should not be confused with header flags, which are used to store persistent flags
* (e.g. selection, hide status, etc).
*
* Access to tool flags is done with `BMO_elem_flag_***()` functions.
*
* \warning Operators are **never** allowed to read or write to header flags.
* They act entirely on the data inside their input slots.
* For example an operator should not check the selected state of an element,
* there are some exceptions to this - some operators check of a face is smooth.
* \subsection bm_slot_types Slot Types
*
* The following slot types are available:
*
* - integer - #BMO_OP_SLOT_INT
* - boolean - #BMO_OP_SLOT_BOOL
* - float - #BMO_OP_SLOT_FLT
* - pointer - #BMO_OP_SLOT_PTR
* - matrix - #BMO_OP_SLOT_MAT
* - vector - #BMO_OP_SLOT_VEC
* - buffer - #BMO_OP_SLOT_ELEMENT_BUF - a list of verts/edges/faces.
* - map - BMO_OP_SLOT_MAPPING - simple hash map.
* \subsection bm_slot_iter Slot Iterators
*
* Access to element buffers or maps must go through the slot iterator API,
* defined in bmesh_operators.hh.
* Use #BMO_ITER where ever possible.
* \subsection bm_elem_buf Element Buffers
*
* The element buffer slot type is used to feed elements (verts/edges/faces) to operators.
* Internally they are stored as pointer arrays (which happily has not caused any problems so far).
* Many operators take in a buffer of elements, process it,
* then spit out a new one; this allows operators to be chained together.
*
* \note Element buffers may have elements of different types within the same buffer
* (this is supported by the API).
* \section bm_fname Function Naming Conventions
*
* These conventions should be used throughout the bmesh module.
*
* - `bmesh_kernel_*()` - Low level API, for primitive functions that others are built on top of.
* - `bmesh_***()` - Low level API function.
* - `bm_***()` - 'static' functions, not a part of the API at all,
* but use prefix since they operate on BMesh data.
* - `BM_***()` - High level BMesh API function for use anywhere.
* - `BMO_***()` - High level operator API function for use anywhere.
* - `bmo_***()` - Low level / internal operator API functions.
* - `_bm_***()` - Functions which are called via macros only.
*
* \section bm_todo BMesh TODO's
*
* There may be a better place for this section, but adding here for now.
*
* \subsection bm_todo_optimize Optimizations
*
* - Skip normal calc when its not needed
* (when calling chain of operators & for modifiers, flag as dirty)
* - Skip BMO flag allocation, its not needed in many cases,
* this is fairly redundant to calc by default.
* - Ability to call BMO's with option not to create return data (will save some time)
* - Binary diff UNDO, currently this uses huge amount of ram
* when all shapes are stored for each undo step for eg.
* - Use two different iterator types for BMO map/buffer types.
*/
#include <cstdlib>
#include "bmesh_class.hh" // IWYU pragma: export
/* include the rest of the API */
#include "intern/bmesh_error.hh" // IWYU pragma: export
#include "intern/bmesh_operator_api.hh" // IWYU pragma: export
#include "intern/bmesh_callback_generic.hh" // IWYU pragma: export
#include "intern/bmesh_construct.hh" // IWYU pragma: export
#include "intern/bmesh_core.hh" // IWYU pragma: export
#include "intern/bmesh_delete.hh" // IWYU pragma: export
#include "intern/bmesh_edgeloop.hh" // IWYU pragma: export
#include "intern/bmesh_interp.hh" // IWYU pragma: export
#include "intern/bmesh_iterators.hh" // IWYU pragma: export
#include "intern/bmesh_log.hh" // IWYU pragma: export
#include "intern/bmesh_marking.hh" // IWYU pragma: export
#include "intern/bmesh_mesh.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_convert.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_debug.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_duplicate.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_normals.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_partial_update.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_tessellate.hh" // IWYU pragma: export
#include "intern/bmesh_mesh_validate.hh" // IWYU pragma: export
#include "intern/bmesh_mods.hh" // IWYU pragma: export
#include "intern/bmesh_operators.hh" // IWYU pragma: export
#include "intern/bmesh_polygon.hh" // IWYU pragma: export
#include "intern/bmesh_polygon_edgenet.hh" // IWYU pragma: export
#include "intern/bmesh_query.hh" // IWYU pragma: export
#include "intern/bmesh_query_uv.hh" // IWYU pragma: export
#include "intern/bmesh_uvselect.hh" // IWYU pragma: export
#include "intern/bmesh_walkers.hh" // IWYU pragma: export
#include "intern/bmesh_inline.hh" // IWYU pragma: export

View File

@@ -0,0 +1,735 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* #BMesh data structures, used for mesh editing operations
* that benefit from accessing connectivity information.
*/
#include "BLI_assert.h"
#include "BLI_sys_types.h"
#include "DNA_customdata_types.h"
#include "DNA_listBase.h"
namespace blender {
/* disable holes for now,
* these are ifdef'd because they use more memory and can't be saved in DNA currently */
// #define USE_BMESH_HOLES
struct BMEditSelection;
struct BMEdge;
struct BMFace;
struct BMLoop;
struct BMOpError;
struct BMVert;
struct BMesh;
struct MLoopNorSpaceArray;
struct BLI_mempool;
// #pragma GCC diagnostic push
// #pragma GCC diagnostic error "-Wpadded"
/**
* #BMHeader
*
* All mesh elements begin with a #BMHeader. This structure
* hold several types of data
*
* 1: The type of the element (vert, edge, loop or face)
* 2: Persistent "header" flags/markings (smooth, seam, select, hidden, etc)
* note that this is different from the "tool" flags.
* 3: Unique ID in the #BMesh.
* 4: some elements for internal record keeping.
*/
struct BMHeader {
/* NOTE: it its essential the #BMHeader is at least the size of two pointers.
* This is a requirement of mempool's method of iteration.
*
* Even though there is only a single pointer, the struct will be padded to two. */
/** CustomData layers. */
void *data;
/**
* \note
* - Use BM_elem_index_get/set macros for index
* - Uninitialized to -1 so we can easily tell its not set.
* - Used for edge/vert/face/loop, check BMesh.elem_index_dirty for valid index values,
* this is abused by various tools which set it dirty.
* - For loops this is used for sorting during tessellation.
*/
int index;
/** Element geometric type (verts/edges/loops/faces). */
char htype;
/** This would be a CD layer, see below. */
char hflag;
/**
* Internal use only!
* \note We are very picky about not bloating this struct
* but in this case its padded up to 16 bytes anyway,
* so adding a flag here gives no increase in size.
*/
char api_flag;
// char _pad;
};
BLI_STATIC_ASSERT((sizeof(BMHeader) <= 16), "BMHeader size has grown!");
/* NOTE: need some way to specify custom locations for custom data layers. so we can
* make them point directly into structs. and some way to make it only happen to the
* active layer, and properly update when switching active layers. */
struct BMVert {
BMHeader head;
/** Vertex coordinate. */
float co[3];
/** Vertex normal. */
float no[3];
/**
* Pointer to (any) edge using this vertex (for disk cycles).
*
* \note Some higher level functions set this to different edges that use this vertex,
* which is a bit of an abuse of internal #BMesh data but also works OK for now
* (use with care!).
*/
struct BMEdge *e;
};
struct BMVert_OFlag {
BMVert base;
struct BMFlagLayer *oflags;
};
/**
* Disk link structure (the element in a circular linked list),
* only used by edges to reference connected edges for the first & second vertices.
*/
struct BMDiskLink {
struct BMEdge *next, *prev;
};
struct BMEdge {
BMHeader head;
/**
* Vertices (unordered),
*
* Although the order can be used at times,
* when extruding a face from a wire-edge for example.
*
* Operations that create/subdivide edges shouldn't flip the order
* unless there is a good reason to do so.
*/
BMVert *v1, *v2;
/**
* The list of loops around the edge, see docstring for #BMLoop.radial_next
* for an example of using this to loop over all faces used by an edge.
*/
struct BMLoop *l;
/**
* Disk Cycle Pointers
*
* relative data: d1 indicates the next/prev
* edge around vertex v1 and d2 does the same for v2.
*/
BMDiskLink v1_disk_link, v2_disk_link;
};
struct BMEdge_OFlag {
BMEdge base;
struct BMFlagLayer *oflags;
};
struct BMLoop {
BMHeader head;
/* Notice no #BMFlagLayer, making this different from other elements. */
/**
* The vertex this loop points to.
*
* - This vertex must be unique within the cycle.
*/
struct BMVert *v;
/**
* The edge this loop uses.
*
* Vertices (#BMLoop.v & #BMLoop.next.v) always contain vertices from (#BMEdge.v1 & #BMEdge.v2).
* Although no assumptions can be made about the order,
* as this isn't meaningful for mesh topology.
*
* - This edge must be unique within the cycle (defined by #BMLoop.next & #BMLoop.prev links).
*/
struct BMEdge *e;
/**
* The face this loop is part of.
*
* - This face must be shared by all within the cycle.
* Used as a back-pointer so loops can know the face they define.
*/
struct BMFace *f;
/**
* Other loops connected to this edge.
*
* This is typically use for accessing an edges faces,
* however this is done by stepping over it's loops.
*
* - This is a circular list, so there are no first/last storage of the "radial" data.
* Instead #BMEdge.l points to any one of the loops that use it.
*
* - Since the list is circular, the particular loop referenced doesn't matter,
* as all other loops can be accessed from it.
*
* - Every loop in this radial list has the same value for #BMLoop.e.
*
* - The value for #BMLoop.v might not match the radial next/previous
* as this depends on the face-winding.
* You can be sure #BMLoop.v will either #BMEdge.v1 or #BMEdge.v2 of #BMLoop.e,
*
* - Unlike face-winding (which defines if the direction the face points),
* next and previous are insignificant. The list could be reversed for example,
* without any impact on the topology.
*
* This is an example of looping over an edges faces using #BMLoop.radial_next.
*
* \code{.c}
* BMLoop *l_iter = edge->l;
* do {
* operate_on_face(l_iter->f);
* } while ((l_iter = l_iter->radial_next) != edge->l);
* \endcode
*/
struct BMLoop *radial_next, *radial_prev;
/**
* Other loops that are part of this face.
*
* This is typically used for accessing all vertices/edges in a faces.
*
* - This is a circular list, so there are no first/last storage of the "cycle" data.
* Instead #BMFace.l_first points to any one of the loops that are part of this face.
*
* - Since the list is circular, the particular loop referenced doesn't matter,
* as all other loops can be accessed from it.
*
* - Every loop in this "cycle" list has the same value for #BMLoop.f.
*
* - The direction of this list defines the face winding.
* Reversing the list flips the face.
*
* This is an example loop over all vertices and edges of a face.
*
* \code{.c}
* BMLoop *l_first, *l_iter;
* l_iter = l_first = BM_FACE_FIRST_LOOP(f);
* do {
* operate_on_vert(l_iter->v);
* operate_on_edge(l_iter->e);
* } while ((l_iter = l_iter->next) != l_first);
* \endcode
*/
struct BMLoop *next, *prev;
};
/**
* A struct which only (#BMFace, #BMEdge, #BMVert) can be cast to.
* But *not* #BMLoop, since these don't have a flag layer.
*/
struct BMElemF {
BMHeader head;
};
/**
* A struct which any element type can be cast to:
* (#BMFace, #BMLoop, #BMEdge, #BMVert).
*/
struct BMElem {
BMHeader head;
};
#ifdef USE_BMESH_HOLES
/**
* NOTE(@ideasman42): this structure was planned for supporting holes in faces.
* although there are no near term plans for this.
*/
struct BMLoopList {
struct BMLoopList *next, *prev;
struct BMLoop *first, *last;
};
#endif
struct BMFace {
BMHeader head;
#ifdef USE_BMESH_HOLES
/** Total boundaries, is one plus the number of holes in the face. */
int totbounds;
ListBaseT<BMLoop> loops;
#else
BMLoop *l_first;
#endif
/**
* Number of vertices in the face
* (the length of #BMFace.l_first circular linked list).
*/
int len;
/**
* Face normal, see #BM_face_calc_normal.
*/
float no[3];
/**
* Material index, typically >= 0 and < #Mesh.totcol although this isn't enforced
* Python for example can set this to any positive value since scripts may create
* mesh data first and setup material slots later.
*
* When using to index into a material array it's range should be checked first,
* values exceeding the range should be ignored or treated as zero
* (if a material slot needs to be used - when drawing for example)
*/
short mat_nr;
// short _pad[3];
};
struct BMFace_OFlag {
BMFace base;
struct BMFlagLayer *oflags;
};
struct BMFlagLayer {
short f; /* flags */
};
// #pragma GCC diagnostic pop
struct BMesh {
int totvert = 0;
int totedge = 0;
int totloop = 0;
int totface = 0;
int totvertsel = 0;
int totedgesel = 0;
int totfacesel = 0;
/**
* Flag index arrays as being dirty so we can check if they are clean and
* avoid looping over the entire vert/edge/face/loop array in those cases.
* valid flags are: `(BM_VERT | BM_EDGE | BM_FACE | BM_LOOP)`
*/
char elem_index_dirty = 0;
/**
* Flag array table as being dirty so we know when its safe to use it,
* or when it needs to be re-created.
*/
char elem_table_dirty = 0;
/** Element pools. */
BLI_mempool *vpool = nullptr;
BLI_mempool *epool = nullptr;
BLI_mempool *lpool = nullptr;
BLI_mempool *fpool = nullptr;
/* #BLI_mempool lookup tables (optional).
* Map indices to elements via #BM_mesh_elem_table_ensure and associated functions.
* Don't touch this or read it directly.
* Use #BM_mesh_elem_table_ensure(), `BM_vert/edge/face_at_index()`. */
/** Vertex table. */
BMVert **vtable = nullptr;
/** Edge table. */
BMEdge **etable = nullptr;
/** Face table. */
BMFace **ftable = nullptr;
/* Size of allocated tables. */
int vtable_tot = 0;
int etable_tot = 0;
int ftable_tot = 0;
/** Operator API stuff (must be all null or all allocated). */
BLI_mempool *vtoolflagpool = nullptr;
BLI_mempool *etoolflagpool = nullptr;
BLI_mempool *ftoolflagpool = nullptr;
bool use_toolflags = false;
/**
* Used when the UV select sync tool-setting is enabled (see: #UV_FLAG_SELECT_SYNC).
*
* When true, UV selection flags are "valid" (see: #BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE).
* Otherwise UV selection is read from vertex/edge/face selection flags used in the viewport.
*
* Notes:
* - This should be cleared aggressively when there is no need
* to store a separate UV selection to avoid unnecessary overhead.
* - Clear using #BM_mesh_uvselect_clear (instead of setting directly).
*
* - See `bmesh_uvselect.hh` for a more comprehensive explanation.
*/
bool uv_select_sync_valid = false;
int toolflag_index = 0;
CustomData vdata;
CustomData edata;
CustomData ldata;
CustomData pdata;
#ifdef USE_BMESH_HOLES
BLI_mempool *looplistpool = nullptr;
#endif
struct MLoopNorSpaceArray *lnor_spacearr = nullptr;
char spacearr_dirty = 0;
/**
* Should be copy of scene select mode.
*
* NOTE(@ideasman42): Stored in #BMEditMesh too, a bit confusing, make sure they're in sync!
* Only use when the edit mesh can't be accessed.
*/
short selectmode = 0;
/** 1-based index of the shape key's #Key::block this #BMesh came from. */
int shapenr = 0;
int totflags = 0;
ListBaseT<BMEditSelection> selected = {};
/**
* The active face.
* This is kept even when unselected, mainly so UV editing can keep showing the
* active faces image while the selection is being modified in the 3D viewport.
*
* Without this the active image in the UV editor would flicker in a distracting way
* while changing selection in the 3D viewport.
*/
BMFace *act_face = nullptr;
/** List of #BMOpError, used for operator error handling. */
ListBaseT<BMOpError> errorstack = {};
/**
* Keep a single reference to the Python instance of this #BMesh (if any exists).
*
* This allows save invalidation of a #BMesh when it's freed,
* so the Python object will report it as having been removed,
* instead of crashing on invalid memory access.
*
* Doesn't hold a #PyObject reference, cleared when the last object is de-referenced.
*/
void *py_handle = nullptr;
};
/** #BMHeader.htype (char) */
enum {
BM_VERT = 1,
BM_EDGE = 2,
BM_LOOP = 4,
BM_FACE = 8,
};
struct BMLoopNorEditData {
int loop_index;
BMLoop *loop;
float niloc[3];
float nloc[3];
float *loc;
short *clnors_data;
};
struct BMLoopNorEditDataArray {
BMLoopNorEditData *lnor_editdata;
/**
* This one has full amount of loops,
* used to map loop index to actual #BMLoopNorEditData struct.
*/
BMLoopNorEditData **lidx_to_lnor_editdata;
int cd_custom_normal_offset;
int totloop;
};
#define BM_ALL (BM_VERT | BM_EDGE | BM_LOOP | BM_FACE)
#define BM_ALL_NOLOOP (BM_VERT | BM_EDGE | BM_FACE)
/** #BMesh.spacearr_dirty */
enum {
BM_SPACEARR_DIRTY = 1 << 0,
BM_SPACEARR_DIRTY_ALL = 1 << 1,
BM_SPACEARR_BMO_SET = 1 << 2,
};
/* args for _Generic */
#define _BM_GENERIC_TYPE_ELEM_NONCONST \
void *, BMVert *, BMEdge *, BMLoop *, BMFace *, BMVert_OFlag *, BMEdge_OFlag *, BMFace_OFlag *, \
BMElem *, BMElemF *, BMHeader *
#define _BM_GENERIC_TYPE_ELEM_CONST \
const void *, const BMVert *, const BMEdge *, const BMLoop *, const BMFace *, \
const BMVert_OFlag *, const BMEdge_OFlag *, const BMFace_OFlag *, const BMElem *, \
const BMElemF *, const BMHeader *
#define BM_CHECK_TYPE_ELEM_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPES_CONST)
#define BM_CHECK_TYPE_ELEM_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST)
#define BM_CHECK_TYPE_ELEM(ele) \
CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST, _BM_GENERIC_TYPE_ELEM_CONST)
/* vert */
#define _BM_GENERIC_TYPE_VERT_NONCONST BMVert *, BMVert_OFlag *
#define _BM_GENERIC_TYPE_VERT_CONST const BMVert *, const BMVert_OFlag *
#define BM_CHECK_TYPE_VERT_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_VERT_CONST)
#define BM_CHECK_TYPE_VERT_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST)
#define BM_CHECK_TYPE_VERT(ele) \
CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_VERT_NONCONST, _BM_GENERIC_TYPE_VERT_CONST)
/* edge */
#define _BM_GENERIC_TYPE_EDGE_NONCONST BMEdge *, BMEdge_OFlag *
#define _BM_GENERIC_TYPE_EDGE_CONST const BMEdge *, const BMEdge_OFlag *
#define BM_CHECK_TYPE_EDGE_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_EDGE_CONST)
#define BM_CHECK_TYPE_EDGE_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST)
#define BM_CHECK_TYPE_EDGE(ele) \
CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_EDGE_NONCONST, _BM_GENERIC_TYPE_EDGE_CONST)
/* face */
#define _BM_GENERIC_TYPE_FACE_NONCONST BMFace *, BMFace_OFlag *
#define _BM_GENERIC_TYPE_FACE_CONST const BMFace *, const BMFace_OFlag *
#define BM_CHECK_TYPE_FACE_CONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_FACE_CONST)
#define BM_CHECK_TYPE_FACE_NONCONST(ele) CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_ELEM_NONCONST)
#define BM_CHECK_TYPE_FACE(ele) \
CHECK_TYPE_ANY(ele, _BM_GENERIC_TYPE_FACE_NONCONST, _BM_GENERIC_TYPE_FACE_CONST)
/**
* Assignment from a void* to a typed pointer is not allowed in C++,
* casting the LHS to void works fine though.
*/
#define BM_CHECK_TYPE_ELEM_ASSIGN(ele) (BM_CHECK_TYPE_ELEM(ele)), *((void **)&ele)
/** #BMHeader.hflag (char) */
enum {
BM_ELEM_SELECT = (1 << 0),
BM_ELEM_HIDDEN = (1 << 1),
BM_ELEM_SEAM = (1 << 2),
/** Used for faces and edges, note from the user POV, this is a sharp edge when disabled. */
BM_ELEM_SMOOTH = (1 << 3),
/**
* Internal flag, used for ensuring correct normals
* during multi-resolution interpolation, and any other time
* when temp tagging is handy.
* always assume dirty & clear before use.
*/
BM_ELEM_TAG = (1 << 4),
/**
* Used for #BMLoop for loop-vertex selection & #BMFace when the face is selected.
* The #BMLoop also stores edge selection: #BM_ELEM_SELECT_UV_EDGE.
*/
BM_ELEM_SELECT_UV = (1 << 5),
/** Spare tag, assumed dirty, use define in each function to name based on use. */
BM_ELEM_TAG_ALT = (1 << 6),
/**
* For low level internal API tagging,
* since tools may want to tag verts and not have functions clobber them.
* Leave cleared!
*/
BM_ELEM_INTERNAL_TAG = (1 << 7),
};
/* Only for #BMLoop to select an edge. */
#define BM_ELEM_SELECT_UV_EDGE BM_ELEM_SEAM
struct BPy_BMGeneric;
extern void bpy_bm_generic_invalidate(struct BPy_BMGeneric *self);
using BMElemFilterFunc = bool (*)(const BMElem *, void *user_data);
using BMVertFilterFunc = bool (*)(const BMVert *, void *user_data);
using BMEdgeFilterFunc = bool (*)(const BMEdge *, void *user_data);
using BMFaceFilterFunc = bool (*)(const BMFace *, void *user_data);
using BMLoopFilterFunc = bool (*)(const BMLoop *, void *user_data);
using BMLoopPairFilterFunc = bool (*)(const BMLoop *, const BMLoop *, void *user_data);
/* defines */
#define BM_ELEM_CD_SET_INT(ele, offset, f) \
{ \
CHECK_TYPE_NONCONST(ele); \
BLI_assert(offset != -1); \
*((int *)((char *)(ele)->head.data + (offset))) = (f); \
} \
(void)0
#define BM_ELEM_CD_GET_INT(ele, offset) \
(BLI_assert(offset != -1), *((int *)((char *)(ele)->head.data + (offset))))
#define BM_ELEM_CD_SET_BOOL(ele, offset, f) \
{ \
CHECK_TYPE_NONCONST(ele); \
BLI_assert(offset != -1); \
*((bool *)((char *)(ele)->head.data + (offset))) = (f); \
} \
(void)0
#define BM_ELEM_CD_GET_BOOL(ele, offset) \
(BLI_assert(offset != -1), *((bool *)((char *)(ele)->head.data + (offset))))
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
# define BM_ELEM_CD_GET_BOOL_P(ele, offset) \
(BLI_assert(offset != -1), \
_Generic(ele, \
GENERIC_TYPE_ANY((bool *)POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_NONCONST), \
GENERIC_TYPE_ANY((const bool *)POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_CONST)))
#else
# define BM_ELEM_CD_GET_BOOL_P(ele, offset) \
(BLI_assert(offset != -1), (bool *)((char *)(ele)->head.data + (offset)))
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
# define BM_ELEM_CD_GET_VOID_P(ele, offset) \
(BLI_assert(offset != -1), \
_Generic(ele, \
GENERIC_TYPE_ANY(POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_NONCONST), \
GENERIC_TYPE_ANY((const void *)POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_CONST)))
#else
# define BM_ELEM_CD_GET_VOID_P(ele, offset) \
(BLI_assert(offset != -1), (void *)((char *)(ele)->head.data + (offset)))
#endif
#define BM_ELEM_CD_SET_FLOAT(ele, offset, f) \
{ \
CHECK_TYPE_NONCONST(ele); \
BLI_assert(offset != -1); \
*((float *)((char *)(ele)->head.data + (offset))) = (f); \
} \
(void)0
#define BM_ELEM_CD_GET_FLOAT(ele, offset) \
(BLI_assert(offset != -1), *((float *)((char *)(ele)->head.data + (offset))))
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
# define BM_ELEM_CD_GET_FLOAT_P(ele, offset) \
(BLI_assert(offset != -1), \
_Generic(ele, \
GENERIC_TYPE_ANY((float *)POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_NONCONST), \
GENERIC_TYPE_ANY((const float *)POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_CONST)))
# define BM_ELEM_CD_GET_FLOAT2_P(ele, offset) \
(BLI_assert(offset != -1), \
_Generic(ele, \
GENERIC_TYPE_ANY((float (*)[2])POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_NONCONST), \
GENERIC_TYPE_ANY((const float (*)[2])POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_CONST)))
# define BM_ELEM_CD_GET_FLOAT3_P(ele, offset) \
(BLI_assert(offset != -1), \
_Generic(ele, \
GENERIC_TYPE_ANY((float (*)[3])POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_NONCONST), \
GENERIC_TYPE_ANY((const float (*)[3])POINTER_OFFSET((ele)->head.data, offset), \
_BM_GENERIC_TYPE_ELEM_CONST)))
#else
# define BM_ELEM_CD_GET_FLOAT_P(ele, offset) \
(BLI_assert(offset != -1), (float *)((char *)(ele)->head.data + (offset)))
# define BM_ELEM_CD_GET_FLOAT2_P(ele, offset) \
(BLI_assert(offset != -1), (float (*)[2])((char *)(ele)->head.data + (offset)))
# define BM_ELEM_CD_GET_FLOAT3_P(ele, offset) \
(BLI_assert(offset != -1), (float (*)[3])((char *)(ele)->head.data + (offset)))
#endif
#define BM_ELEM_CD_SET_FLOAT2(ele, offset, f) \
{ \
CHECK_TYPE_NONCONST(ele); \
BLI_assert(offset != -1); \
((float *)((char *)(ele)->head.data + (offset)))[0] = (f)[0]; \
((float *)((char *)(ele)->head.data + (offset)))[1] = (f)[1]; \
} \
(void)0
#define BM_ELEM_CD_SET_FLOAT3(ele, offset, f) \
{ \
CHECK_TYPE_NONCONST(ele); \
BLI_assert(offset != -1); \
((float *)((char *)(ele)->head.data + (offset)))[0] = (f)[0]; \
((float *)((char *)(ele)->head.data + (offset)))[1] = (f)[1]; \
((float *)((char *)(ele)->head.data + (offset)))[2] = (f)[2]; \
} \
(void)0
#define BM_ELEM_CD_GET_FLOAT_AS_UCHAR(ele, offset) \
(BLI_assert(offset != -1), (uchar)(BM_ELEM_CD_GET_FLOAT(ele, offset) * 255.0f))
/* Forward declarations. */
#ifdef USE_BMESH_HOLES
# define BM_FACE_FIRST_LOOP(p) (((BMLoopList *)((p)->loops.first))->first)
#else
# define BM_FACE_FIRST_LOOP(p) ((p)->l_first)
#endif
#define BM_DISK_EDGE_NEXT(e, v) \
(CHECK_TYPE_INLINE(e, BMEdge *), \
CHECK_TYPE_INLINE(v, BMVert *), \
BLI_assert(BM_vert_in_edge(e, v)), \
(((&e->v1_disk_link)[v == e->v2]).next))
#define BM_DISK_EDGE_PREV(e, v) \
(CHECK_TYPE_INLINE(e, BMEdge *), \
CHECK_TYPE_INLINE(v, BMVert *), \
BLI_assert(BM_vert_in_edge(e, v)), \
(((&e->v1_disk_link)[v == e->v2]).prev))
/**
* Size to use for stack arrays when dealing with NGons, allocate after this limit is reached.
* this value is rather arbitrary.
*/
#define BM_DEFAULT_NGON_STACK_SIZE 32
/**
* Size to use for stack arrays dealing with connected mesh data
* verts of faces, edges of vert - etc.
* often used with #BM_iter_as_arrayN().
*/
#define BM_DEFAULT_ITER_STACK_SIZE 16
/**
* Size to use for stack arrays when gathering topology-related data
* (e.g. collecting edges, faces, or vertices during mesh operations).
* Prefer more specific defines (such as #BM_DEFAULT_NGON_STACK_SIZE) when applicable.
*/
#define BM_DEFAULT_TOPOLOGY_STACK_SIZE 64
/** Avoid an eternal loop, this value is arbitrary but should not error on valid cases. */
#define BM_LOOP_RADIAL_MAX 10000
#define BM_NGON_MAX 100000
/** Minimum number of elements before using threading. */
#define BM_THREAD_LIMIT 10000
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* Utility functions that operate directly on the BMesh,
* These can be used by both Modifiers and BMesh-Operators.
*/
#include "tools/bmesh_beautify.hh" // IWYU pragma: export
#include "tools/bmesh_bevel.hh" // IWYU pragma: export
#include "tools/bmesh_bisect_plane.hh" // IWYU pragma: export
#include "tools/bmesh_boolean.hh" // IWYU pragma: export
#include "tools/bmesh_decimate.hh" // IWYU pragma: export
#include "tools/bmesh_edgenet.hh" // IWYU pragma: export
#include "tools/bmesh_edgesplit.hh" // IWYU pragma: export
#include "tools/bmesh_path.hh" // IWYU pragma: export
#include "tools/bmesh_path_region.hh" // IWYU pragma: export
#include "tools/bmesh_path_region_uv.hh" // IWYU pragma: export
#include "tools/bmesh_path_uv.hh" // IWYU pragma: export
#include "tools/bmesh_region_match.hh" // IWYU pragma: export
#include "tools/bmesh_separate.hh" // IWYU pragma: export
#include "tools/bmesh_triangulate.hh" // IWYU pragma: export

View File

@@ -0,0 +1,47 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM element callback functions.
*/
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_callback_generic.hh"
namespace blender {
bool BM_elem_cb_check_hflag_ex(BMElem *ele, void *user_data)
{
const uint hflag_pair = POINTER_AS_INT(user_data);
const char hflag_p = (hflag_pair & 0xff);
const char hflag_n = (hflag_pair >> 8);
return ((BM_elem_flag_test(ele, hflag_p) != 0) && (BM_elem_flag_test(ele, hflag_n) == 0));
}
bool BM_elem_cb_check_hflag_enabled(BMElem *ele, void *user_data)
{
const char hflag = POINTER_AS_INT(user_data);
return (BM_elem_flag_test(ele, hflag) != 0);
}
bool BM_elem_cb_check_hflag_disabled(BMElem *ele, void *user_data)
{
const char hflag = POINTER_AS_INT(user_data);
return (BM_elem_flag_test(ele, hflag) == 0);
}
bool BM_elem_cb_check_elem_not_equal(BMElem *ele, void *user_data)
{
return (ele != user_data);
}
} // namespace blender

View File

@@ -0,0 +1,30 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
namespace blender {
struct BMElem;
bool BM_elem_cb_check_hflag_enabled(BMElem *, void *user_data);
bool BM_elem_cb_check_hflag_disabled(BMElem *, void *user_data);
bool BM_elem_cb_check_hflag_ex(BMElem *, void *user_data);
bool BM_elem_cb_check_elem_not_equal(BMElem *ele, void *user_data);
#define BM_elem_cb_check_hflag_ex_simple(type, hflag_p, hflag_n) \
(bool (*)(type, void *)) BM_elem_cb_check_hflag_ex, \
POINTER_FROM_UINT(((hflag_p) | (hflag_n << 8)))
#define BM_elem_cb_check_hflag_enabled_simple(type, hflag_p) \
(bool (*)(type, void *)) BM_elem_cb_check_hflag_enabled, POINTER_FROM_UINT((hflag_p))
#define BM_elem_cb_check_hflag_disabled_simple(type, hflag_n) \
(bool (*)(type, void *)) BM_elem_cb_check_hflag_disabled, POINTER_FROM_UINT(hflag_n)
} // namespace blender

View File

@@ -0,0 +1,714 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM construction functions.
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BKE_attribute.hh"
#include "BKE_attribute_legacy_convert.hh"
#include "BKE_attribute_storage.hh"
#include "BKE_customdata.hh"
#include "BKE_geometry_set.hh"
#include "DNA_mesh_types.h"
#include "bmesh.hh"
#include "intern/bmesh_private.hh"
namespace blender {
bool BM_verts_from_edges(BMVert **vert_arr, BMEdge **edge_arr, const int len)
{
int i, i_prev = len - 1;
for (i = 0; i < len; i++) {
vert_arr[i] = BM_edge_share_vert(edge_arr[i_prev], edge_arr[i]);
if (vert_arr[i] == nullptr) {
return false;
}
i_prev = i;
}
return true;
}
bool BM_edges_from_verts(BMEdge **edge_arr, BMVert **vert_arr, const int len)
{
int i, i_prev = len - 1;
for (i = 0; i < len; i++) {
edge_arr[i_prev] = BM_edge_exists(vert_arr[i_prev], vert_arr[i]);
if (edge_arr[i_prev] == nullptr) {
return false;
}
i_prev = i;
}
return true;
}
void BM_edges_from_verts_ensure(BMesh *bm, BMEdge **edge_arr, BMVert **vert_arr, const int len)
{
int i, i_prev = len - 1;
for (i = 0; i < len; i++) {
edge_arr[i_prev] = BM_edge_create(
bm, vert_arr[i_prev], vert_arr[i], nullptr, BM_CREATE_NO_DOUBLE);
i_prev = i;
}
}
BMFace *BM_face_create_quad_tri(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMVert *v3,
BMVert *v4,
const BMFace *f_example,
const eBMCreateFlag create_flag)
{
BMVert *vtar[4] = {v1, v2, v3, v4};
return BM_face_create_verts(bm, vtar, v4 ? 4 : 3, f_example, create_flag, true);
}
void BM_face_copy_shared(BMesh *bm, BMFace *f, BMLoopFilterFunc filter_fn, void *user_data)
{
BMLoop *l_first;
BMLoop *l_iter;
#ifndef NDEBUG
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BLI_assert(BM_ELEM_API_FLAG_TEST(l_iter, _FLAG_OVERLAP) == 0);
} while ((l_iter = l_iter->next) != l_first);
#endif
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMLoop *l_other = l_iter->radial_next;
if (l_other && l_other != l_iter) {
BMLoop *l_src[2];
BMLoop *l_dst[2] = {l_iter, l_iter->next};
uint j;
if (l_other->v == l_iter->v) {
l_src[0] = l_other;
l_src[1] = l_other->next;
}
else {
l_src[0] = l_other->next;
l_src[1] = l_other;
}
for (j = 0; j < 2; j++) {
BLI_assert(l_dst[j]->v == l_src[j]->v);
if (BM_ELEM_API_FLAG_TEST(l_dst[j], _FLAG_OVERLAP) == 0) {
if ((filter_fn == nullptr) || filter_fn(l_src[j], user_data)) {
CustomData_bmesh_copy_block(bm->ldata, l_src[j]->head.data, &l_dst[j]->head.data);
BM_ELEM_API_FLAG_ENABLE(l_dst[j], _FLAG_OVERLAP);
}
}
}
}
} while ((l_iter = l_iter->next) != l_first);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_ELEM_API_FLAG_DISABLE(l_iter, _FLAG_OVERLAP);
} while ((l_iter = l_iter->next) != l_first);
}
/**
* Given an array of edges,
* order them using the winding defined by \a v1 & \a v2
* into \a edges_sort & \a verts_sort.
*
* All arrays must be \a len long.
*/
static bool bm_edges_sort_winding(BMVert *v1,
BMVert *v2,
BMEdge **edges,
const int len,
BMEdge **edges_sort,
BMVert **verts_sort)
{
BMEdge *e_iter, *e_first;
BMVert *v_iter;
int i;
/* all flags _must_ be cleared on exit! */
for (i = 0; i < len; i++) {
BM_ELEM_API_FLAG_ENABLE(edges[i], _FLAG_MF);
BM_ELEM_API_FLAG_ENABLE(edges[i]->v1, _FLAG_MV);
BM_ELEM_API_FLAG_ENABLE(edges[i]->v2, _FLAG_MV);
}
/* find first edge */
i = 0;
v_iter = v1;
e_iter = e_first = v1->e;
do {
if (BM_ELEM_API_FLAG_TEST(e_iter, _FLAG_MF) && (BM_edge_other_vert(e_iter, v_iter) == v2)) {
i = 1;
break;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v_iter)) != e_first);
if (i == 0) {
goto error;
}
i = 0;
do {
/* entering loop will always succeed */
if (BM_ELEM_API_FLAG_TEST(e_iter, _FLAG_MF)) {
if (UNLIKELY(BM_ELEM_API_FLAG_TEST(v_iter, _FLAG_MV) == false)) {
/* vert is in loop multiple times */
goto error;
}
BM_ELEM_API_FLAG_DISABLE(e_iter, _FLAG_MF);
edges_sort[i] = e_iter;
BM_ELEM_API_FLAG_DISABLE(v_iter, _FLAG_MV);
verts_sort[i] = v_iter;
i += 1;
/* walk onto the next vertex */
v_iter = BM_edge_other_vert(e_iter, v_iter);
if (i == len) {
if (UNLIKELY(v_iter != verts_sort[0])) {
goto error;
}
break;
}
e_first = e_iter;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v_iter)) != e_first);
if (i == len) {
return true;
}
error:
for (i = 0; i < len; i++) {
BM_ELEM_API_FLAG_DISABLE(edges[i], _FLAG_MF);
BM_ELEM_API_FLAG_DISABLE(edges[i]->v1, _FLAG_MV);
BM_ELEM_API_FLAG_DISABLE(edges[i]->v2, _FLAG_MV);
}
return false;
}
BMFace *BM_face_create_ngon(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMEdge **edges,
const int len,
const BMFace *f_example,
const eBMCreateFlag create_flag)
{
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges_sort(len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> verts_sort(len);
BLI_assert(len && v1 && v2 && edges && bm);
if (bm_edges_sort_winding(v1, v2, edges, len, edges_sort.data(), verts_sort.data())) {
return BM_face_create(bm, verts_sort.data(), edges_sort.data(), len, f_example, create_flag);
}
return nullptr;
}
BMFace *BM_face_create_ngon_verts(BMesh *bm,
BMVert **vert_arr,
const int len,
const BMFace *f_example,
const eBMCreateFlag create_flag,
const bool calc_winding,
const bool create_edges)
{
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edge_arr(len);
uint winding[2] = {0, 0};
int i, i_prev = len - 1;
BMVert *v_winding[2] = {vert_arr[i_prev], vert_arr[0]};
BLI_assert(len > 2);
for (i = 0; i < len; i++) {
if (create_edges) {
edge_arr[i] = BM_edge_create(
bm, vert_arr[i_prev], vert_arr[i], nullptr, BM_CREATE_NO_DOUBLE);
}
else {
edge_arr[i] = BM_edge_exists(vert_arr[i_prev], vert_arr[i]);
if (edge_arr[i] == nullptr) {
return nullptr;
}
}
if (calc_winding) {
/* the edge may exist already and be attached to a face
* in this case we can find the best winding to use for the new face */
if (edge_arr[i]->l) {
BMVert *test_v1, *test_v2;
/* we want to use the reverse winding to the existing order */
BM_edge_ordered_verts(edge_arr[i], &test_v2, &test_v1);
winding[(vert_arr[i_prev] == test_v2)]++;
BLI_assert(ELEM(vert_arr[i_prev], test_v2, test_v1));
}
}
i_prev = i;
}
/* --- */
if (calc_winding) {
if (winding[0] < winding[1]) {
winding[0] = 1;
winding[1] = 0;
}
else {
winding[0] = 0;
winding[1] = 1;
}
}
else {
winding[0] = 0;
winding[1] = 1;
}
/* --- */
/* create the face */
return BM_face_create_ngon(bm,
v_winding[winding[0]],
v_winding[winding[1]],
edge_arr.data(),
len,
f_example,
create_flag);
}
void BM_verts_sort_radial_plane(BMVert **vert_arr, int len)
{
using AngleIndex = std::pair<float, int>;
Array<AngleIndex, BM_DEFAULT_NGON_STACK_SIZE> vang(len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vert_arr_map(len);
float nor[3], cent[3];
int index_tangent = 0;
BM_verts_calc_normal_from_cloud_ex(vert_arr, len, nor, cent, &index_tangent);
const float *far = vert_arr[index_tangent]->co;
/* Now calculate every points angle around the normal (signed). */
for (int i = 0; i < len; i++) {
vang[i].first = angle_signed_on_axis_v3v3v3_v3(far, cent, vert_arr[i]->co, nor);
vang[i].second = i;
vert_arr_map[i] = vert_arr[i];
}
/* sort by angle and magic! - we have our ngon */
std::ranges::sort(vang,
[](const AngleIndex &a, const AngleIndex &b) { return a.first < b.first; });
/* --- */
for (int i = 0; i < len; i++) {
vert_arr[i] = vert_arr_map[vang[i].second];
}
}
/*************************************************************/
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMVert *src, BMVert *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->vdata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
}
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMEdge *src, BMEdge *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->edata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMFace *src, BMFace *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->pdata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
dst->mat_nr = src->mat_nr;
}
void BM_elem_attrs_copy(BMesh *bm, const BMCustomDataCopyMap &map, const BMLoop *src, BMLoop *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->ldata, map, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_attrs_copy(BMesh *bm, const BMVert *src, BMVert *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->vdata, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
}
void BM_elem_attrs_copy(BMesh *bm, const BMEdge *src, BMEdge *dst)
{
BLI_assert(src != dst);
CustomData_bmesh_copy_block(bm->edata, src->head.data, &dst->head.data);
constexpr char hflag_mask = BM_ELEM_SELECT;
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_attrs_copy(BMesh *bm, const BMFace *src, BMFace *dst)
{
BLI_assert(src != dst);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV;
CustomData_bmesh_copy_block(bm->pdata, src->head.data, &dst->head.data);
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
copy_v3_v3(dst->no, src->no);
dst->mat_nr = src->mat_nr;
}
void BM_elem_attrs_copy(BMesh *bm, const BMLoop *src, BMLoop *dst)
{
BLI_assert(src != dst);
constexpr char hflag_mask = BM_ELEM_SELECT | BM_ELEM_SELECT_UV | BM_ELEM_SELECT_UV_EDGE;
CustomData_bmesh_copy_block(bm->ldata, src->head.data, &dst->head.data);
dst->head.hflag = (dst->head.hflag & hflag_mask) | (src->head.hflag & ~hflag_mask);
}
void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v)
{
BMHeader *ele_dst = static_cast<BMHeader *>(ele_dst_v);
const BMHeader *ele_src = static_cast<const BMHeader *>(ele_src_v);
BLI_assert(ele_src->htype == ele_dst->htype);
if ((ele_src->hflag & BM_ELEM_SELECT) != (ele_dst->hflag & BM_ELEM_SELECT)) {
BM_elem_select_set(
bm_dst, reinterpret_cast<BMElem *>(ele_dst), (ele_src->hflag & BM_ELEM_SELECT) != 0);
}
}
/* helper function for 'BM_mesh_copy' */
static BMFace *bm_mesh_copy_new_face(BMesh *bm_new,
const BMCustomDataCopyMap &face_map,
const BMCustomDataCopyMap &loop_map,
BMVert **vtable,
BMEdge **etable,
BMFace *f)
{
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> loops(f->len);
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> verts(f->len);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges(f->len);
BMFace *f_new;
BMLoop *l_iter, *l_first;
int j;
j = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
loops[j] = l_iter;
verts[j] = vtable[BM_elem_index_get(l_iter->v)];
edges[j] = etable[BM_elem_index_get(l_iter->e)];
j++;
} while ((l_iter = l_iter->next) != l_first);
f_new = BM_face_create(bm_new, verts.data(), edges.data(), f->len, nullptr, BM_CREATE_SKIP_CD);
if (UNLIKELY(f_new == nullptr)) {
return nullptr;
}
/* use totface in case adding some faces fails */
BM_elem_index_set(f_new, (bm_new->totface - 1)); /* set_inline */
CustomData_bmesh_copy_block(bm_new->pdata, face_map, f->head.data, &f_new->head.data);
copy_v3_v3(f_new->no, f->no);
f_new->mat_nr = f->mat_nr;
f_new->head.hflag = f->head.hflag; /* Low level! don't do this for normal API use. */
j = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f_new);
do {
CustomData_bmesh_copy_block(bm_new->ldata, loop_map, loops[j]->head.data, &l_iter->head.data);
l_iter->head.hflag = loops[j]->head.hflag & ~BM_ELEM_SELECT;
j++;
} while ((l_iter = l_iter->next) != l_first);
return f_new;
}
static CustomData &get_bmesh_custom_data(BMesh &bm, const bke::AttrDomain domain)
{
switch (domain) {
case bke::AttrDomain::Point:
return bm.vdata;
case bke::AttrDomain::Edge:
return bm.edata;
case bke::AttrDomain::Face:
return bm.pdata;
case bke::AttrDomain::Corner:
return bm.ldata;
default:
BLI_assert_unreachable();
return bm.vdata;
}
}
void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst,
const Mesh *me_src_array[],
const int me_src_array_len,
const BMAllocTemplate *allocsize)
{
if (allocsize == nullptr) {
allocsize = &bm_mesh_allocsize_default;
}
bke::GeometrySet::GatheredAttributes attribute_info;
for (int i = 0; i < me_src_array_len; i++) {
const Mesh *me_src = me_src_array[i];
for (const bke::Attribute &attr : me_src->attribute_storage.wrap()) {
if (BM_attribute_stored_in_bmesh_builtin(attr.name())) {
continue;
}
attribute_info.add(attr.name(), {attr.domain(), attr.data_type()});
}
}
for (const int i : attribute_info.names.index_range()) {
const StringRef name = attribute_info.names[i];
const bke::AttrDomain domain = attribute_info.kinds[i].domain;
const eCustomDataType data_type = *bke::attr_type_to_custom_data_type(
attribute_info.kinds[i].data_type);
CustomData &custom_data = get_bmesh_custom_data(*bm_dst, domain);
CustomData_add_layer_named(&custom_data, data_type, CD_SET_DEFAULT, 0, name);
}
for (int i = 0; i < me_src_array_len; i++) {
const Mesh *me_src = me_src_array[i];
CustomData_merge_layout(
&me_src->vert_data, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->edge_data, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->face_data, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_merge_layout(
&me_src->corner_data, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
}
CustomData_bmesh_init_pool(&bm_dst->vdata, allocsize->totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm_dst->edata, allocsize->totedge, BM_EDGE);
CustomData_bmesh_init_pool(&bm_dst->ldata, allocsize->totloop, BM_LOOP);
CustomData_bmesh_init_pool(&bm_dst->pdata, allocsize->totface, BM_FACE);
}
void BM_mesh_copy_init_customdata_from_mesh(BMesh *bm_dst,
const Mesh *me_src,
const BMAllocTemplate *allocsize)
{
BM_mesh_copy_init_customdata_from_mesh_array(bm_dst, &me_src, 1, allocsize);
}
void BM_mesh_copy_init_customdata(BMesh *bm_dst, BMesh *bm_src, const BMAllocTemplate *allocsize)
{
if (allocsize == nullptr) {
allocsize = &bm_mesh_allocsize_default;
}
CustomData_init_layout_from(
&bm_src->vdata, &bm_dst->vdata, CD_MASK_BMESH.vmask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&bm_src->edata, &bm_dst->edata, CD_MASK_BMESH.emask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&bm_src->ldata, &bm_dst->ldata, CD_MASK_BMESH.lmask, CD_SET_DEFAULT, 0);
CustomData_init_layout_from(
&bm_src->pdata, &bm_dst->pdata, CD_MASK_BMESH.pmask, CD_SET_DEFAULT, 0);
CustomData_bmesh_init_pool(&bm_dst->vdata, allocsize->totvert, BM_VERT);
CustomData_bmesh_init_pool(&bm_dst->edata, allocsize->totedge, BM_EDGE);
CustomData_bmesh_init_pool(&bm_dst->ldata, allocsize->totloop, BM_LOOP);
CustomData_bmesh_init_pool(&bm_dst->pdata, allocsize->totface, BM_FACE);
}
void BM_mesh_copy_init_customdata_all_layers(BMesh *bm_dst,
BMesh *bm_src,
const char htype,
const BMAllocTemplate *allocsize)
{
if (allocsize == nullptr) {
allocsize = &bm_mesh_allocsize_default;
}
const char htypes[4] = {BM_VERT, BM_EDGE, BM_LOOP, BM_FACE};
BLI_assert(((&bm_dst->vdata + 1) == &bm_dst->edata) &&
((&bm_dst->vdata + 2) == &bm_dst->ldata) && ((&bm_dst->vdata + 3) == &bm_dst->pdata));
BLI_assert(((&allocsize->totvert + 1) == &allocsize->totedge) &&
((&allocsize->totvert + 2) == &allocsize->totloop) &&
((&allocsize->totvert + 3) == &allocsize->totface));
for (int i = 0; i < 4; i++) {
if (!(htypes[i] & htype)) {
continue;
}
CustomData *dst = &bm_dst->vdata + i;
CustomData *src = &bm_src->vdata + i;
const int size = *(&allocsize->totvert + i);
for (int l = 0; l < src->totlayer; l++) {
CustomData_add_layer_named(
dst, eCustomDataType(src->layers[l].type), CD_SET_DEFAULT, 0, src->layers[l].name);
/* Needed to keep this a working shape key layer (see also #customdata_merge_internal). */
dst->layers[l].uid = src->layers[l].uid;
}
CustomData_bmesh_init_pool(dst, size, htypes[i]);
}
}
BMesh *BM_mesh_copy(BMesh *bm_old)
{
BMesh *bm_new;
BMVert *v, *v_new, **vtable = nullptr;
BMEdge *e, *e_new, **etable = nullptr;
BMFace *f, *f_new, **ftable = nullptr;
BMElem **eletable;
BMIter iter;
int i;
const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_BM(bm_old);
/* allocate a bmesh */
BMeshCreateParams params{};
params.use_toolflags = bm_old->use_toolflags;
bm_new = BM_mesh_create(&allocsize, &params);
BM_mesh_copy_init_customdata(bm_new, bm_old, &allocsize);
const BMCustomDataCopyMap vert_map = CustomData_bmesh_copy_map_calc(bm_old->vdata,
bm_new->vdata);
const BMCustomDataCopyMap edge_map = CustomData_bmesh_copy_map_calc(bm_old->edata,
bm_new->edata);
const BMCustomDataCopyMap face_map = CustomData_bmesh_copy_map_calc(bm_old->pdata,
bm_new->pdata);
const BMCustomDataCopyMap loop_map = CustomData_bmesh_copy_map_calc(bm_old->ldata,
bm_new->ldata);
vtable = MEM_new_array_uninitialized<BMVert *>(bm_old->totvert, "BM_mesh_copy vtable");
etable = MEM_new_array_uninitialized<BMEdge *>(bm_old->totedge, "BM_mesh_copy etable");
ftable = MEM_new_array_uninitialized<BMFace *>(bm_old->totface, "BM_mesh_copy ftable");
BM_ITER_MESH_INDEX (v, &iter, bm_old, BM_VERTS_OF_MESH, i) {
/* copy between meshes so can't use 'example' argument */
v_new = BM_vert_create(bm_new, v->co, nullptr, BM_CREATE_SKIP_CD);
CustomData_bmesh_copy_block(bm_new->vdata, vert_map, v->head.data, &v_new->head.data);
copy_v3_v3(v_new->no, v->no);
v_new->head.hflag = v->head.hflag; /* Low level! don't do this for normal API use. */
vtable[i] = v_new;
BM_elem_index_set(v, i); /* set_inline */
BM_elem_index_set(v_new, i); /* set_inline */
}
bm_old->elem_index_dirty &= ~BM_VERT;
bm_new->elem_index_dirty &= ~BM_VERT;
/* safety check */
BLI_assert(i == bm_old->totvert);
BM_ITER_MESH_INDEX (e, &iter, bm_old, BM_EDGES_OF_MESH, i) {
e_new = BM_edge_create(bm_new,
vtable[BM_elem_index_get(e->v1)],
vtable[BM_elem_index_get(e->v2)],
e,
BM_CREATE_SKIP_CD);
CustomData_bmesh_copy_block(bm_new->edata, edge_map, e->head.data, &e_new->head.data);
e_new->head.hflag = e->head.hflag; /* Low level! don't do this for normal API use. */
etable[i] = e_new;
BM_elem_index_set(e, i); /* set_inline */
BM_elem_index_set(e_new, i); /* set_inline */
}
bm_old->elem_index_dirty &= ~BM_EDGE;
bm_new->elem_index_dirty &= ~BM_EDGE;
/* safety check */
BLI_assert(i == bm_old->totedge);
BM_ITER_MESH_INDEX (f, &iter, bm_old, BM_FACES_OF_MESH, i) {
BM_elem_index_set(f, i); /* set_inline */
f_new = bm_mesh_copy_new_face(bm_new, face_map, loop_map, vtable, etable, f);
ftable[i] = f_new;
if (f == bm_old->act_face) {
bm_new->act_face = f_new;
}
}
bm_old->elem_index_dirty &= ~BM_FACE;
bm_new->elem_index_dirty &= ~BM_FACE;
/* Low level! don't do this for normal API use. */
bm_new->totvertsel = bm_old->totvertsel;
bm_new->totedgesel = bm_old->totedgesel;
bm_new->totfacesel = bm_old->totfacesel;
/* safety check */
BLI_assert(i == bm_old->totface);
/* copy over edit selection history */
for (BMEditSelection &ese : bm_old->selected) {
BMElem *ele = nullptr;
switch (ese.htype) {
case BM_VERT:
eletable = reinterpret_cast<BMElem **>(vtable);
break;
case BM_EDGE:
eletable = reinterpret_cast<BMElem **>(etable);
break;
case BM_FACE:
eletable = reinterpret_cast<BMElem **>(ftable);
break;
default:
eletable = nullptr;
break;
}
if (eletable) {
ele = eletable[BM_elem_index_get(ese.ele)];
if (ele) {
BM_select_history_store(bm_new, ele);
}
}
}
MEM_delete(etable);
MEM_delete(vtable);
MEM_delete(ftable);
/* Copy various settings. */
bm_new->shapenr = bm_old->shapenr;
bm_new->selectmode = bm_old->selectmode;
return bm_new;
}
} // namespace blender

View File

@@ -0,0 +1,190 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "bmesh_core.hh"
namespace blender {
struct BMAllocTemplate;
struct BMCustomDataCopyMap;
struct Mesh;
/**
* Fill in a vertex array from an edge array.
*
* \returns false if any verts aren't found.
*/
bool BM_verts_from_edges(BMVert **vert_arr, BMEdge **edge_arr, int len);
/**
* Fill in an edge array from a vertex array (connected polygon loop).
*
* \returns false if any edges aren't found.
*/
bool BM_edges_from_verts(BMEdge **edge_arr, BMVert **vert_arr, int len);
/**
* Fill in an edge array from a vertex array (connected polygon loop).
* Creating edges as-needed.
*/
void BM_edges_from_verts_ensure(BMesh *bm, BMEdge **edge_arr, BMVert **vert_arr, int len);
/**
* Makes an NGon from an un-ordered set of verts.
*
* Assumes:
* - that verts are only once in the list.
* - that the verts have roughly planer bounds
* - that the verts are roughly circular
*
* There can be concave areas but overlapping folds from the center point will fail.
*
* A brief explanation of the method used
* - find the center point
* - find the normal of the vertex-cloud
* - order the verts around the face based on their angle to the normal vector at the center point.
*
* \note Since this is a vertex-cloud there is no direction.
*/
void BM_verts_sort_radial_plane(BMVert **vert_arr, int len);
/**
* \brief Make Quad/Triangle
*
* Creates a new quad or triangle from a list of 3 or 4 vertices.
* If \a no_double is true, then a check is done to see if a face
* with these vertices already exists and returns it instead.
*
* If a pointer to an example face is provided, its custom data
* and properties will be copied to the new face.
*
* \note The winding of the face is determined by the order
* of the vertices in the vertex array.
*/
BMFace *BM_face_create_quad_tri(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMVert *v3,
BMVert *v4,
const BMFace *f_example,
eBMCreateFlag create_flag);
/**
* \brief copies face loop data from shared adjacent faces.
*
* \param filter_fn: A function that filters the source loops before copying
* (don't always want to copy all).
*
* \note when a matching edge is found, both loops of that edge are copied
* this is done since the face may not be completely surrounded by faces,
* this way: a quad with 2 connected quads on either side will still get all 4 loops updated
*/
void BM_face_copy_shared(BMesh *bm, BMFace *f, BMLoopFilterFunc filter_fn, void *user_data);
/**
* \brief Make NGon
*
* Makes an ngon from an unordered list of edges.
* Verts \a v1 and \a v2 define the winding of the new face.
*
* \a edges are not required to be ordered, simply to form
* a single closed loop as a whole.
*
* \note While this function will work fine when the edges
* are already sorted, if the edges are always going to be sorted,
* #BM_face_create should be considered over this function as it
* avoids some unnecessary work.
*/
BMFace *BM_face_create_ngon(BMesh *bm,
BMVert *v1,
BMVert *v2,
BMEdge **edges,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag);
/**
* Create an ngon from an array of sorted verts
*
* Special features this has over other functions.
* - Optionally calculate winding based on surrounding edges.
* - Optionally create edges between vertices.
* - Uses verts so no need to find edges (handy when you only have verts)
*/
BMFace *BM_face_create_ngon_verts(BMesh *bm,
BMVert **vert_arr,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag,
bool calc_winding,
bool create_edges);
/**
* Copy attributes between elements with a precalculated map of copy operations. This significantly
* improves performance when copying, since all the work of finding common layers doesn't have to
* be done for every element.
*/
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMVert *src,
BMVert *dst);
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMEdge *src,
BMEdge *dst);
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMFace *src,
BMFace *dst);
void BM_elem_attrs_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_map,
const BMLoop *src,
BMLoop *dst);
/** Copy attributes between elements in the same BMesh. */
void BM_elem_attrs_copy(BMesh *bm, const BMVert *src, BMVert *dst);
void BM_elem_attrs_copy(BMesh *bm, const BMEdge *src, BMEdge *dst);
void BM_elem_attrs_copy(BMesh *bm, const BMFace *src, BMFace *dst);
void BM_elem_attrs_copy(BMesh *bm, const BMLoop *src, BMLoop *dst);
void BM_elem_select_copy(BMesh *bm_dst, void *ele_dst_v, const void *ele_src_v);
/**
* Initialize the `bm_dst` layers in preparation for populating its contents with multiple meshes.
* Typically done using multiple calls to #BM_mesh_bm_from_me with the same `bm` argument.
*
* \note While the custom-data layers of all meshes are created, the active layers are set
* by the first instance mesh containing that layer type.
* This means the first mesh should always be the main mesh (from the user perspective),
* as this is the mesh they have control over (active UV layer for rendering for example).
*/
void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst,
const Mesh *me_src_array[],
int me_src_array_len,
const BMAllocTemplate *allocsize);
void BM_mesh_copy_init_customdata_from_mesh(BMesh *bm_dst,
const Mesh *me_src,
const BMAllocTemplate *allocsize);
void BM_mesh_copy_init_customdata(BMesh *bm_dst, BMesh *bm_src, const BMAllocTemplate *allocsize);
/**
* Similar to #BM_mesh_copy_init_customdata but copies all layers ignoring
* flags like #CD_FLAG_NOCOPY.
*
* \param bm_dst: BMesh whose custom-data layers will be added.
* \param bm_src: BMesh whose custom-data layers will be copied.
* \param htype: Specifies which custom-data layers will be initiated.
* \param allocsize: Initialize the memory-pool before use (may be an estimate).
*/
void BM_mesh_copy_init_customdata_all_layers(BMesh *bm_dst,
BMesh *bm_src,
char htype,
const BMAllocTemplate *allocsize);
BMesh *BM_mesh_copy(BMesh *bm_old);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,488 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "DNA_listBase.h"
#include "BKE_customdata.hh"
#include "bmesh_class.hh"
struct BMLoopList;
namespace blender {
/**
* When copying between different BMesh objects,
* `copy_verts` & `copy_edges` should always be true.
*/
BMFace *BM_face_copy(BMesh *bm,
const BMCustomDataCopyMap &cd_face_map,
const BMCustomDataCopyMap &cd_loop_map,
BMFace *f,
bool copy_verts,
bool copy_edges);
BMFace *BM_face_copy(BMesh *bm, BMFace *f, bool copy_verts, bool copy_edges);
enum eBMCreateFlag {
BM_CREATE_NOP = 0,
/** Faces and edges only. */
BM_CREATE_NO_DOUBLE = (1 << 1),
/**
* Skip custom-data - for all element types data,
* use if we immediately write custom-data into the element so this skips copying from 'example'
* arguments or setting defaults, speeds up conversion when data is converted all at once.
*/
BM_CREATE_SKIP_CD = (1 << 2),
};
/**
* \brief Main function for creating a new vertex.
*/
BMVert *BM_vert_create(BMesh *bm,
const float co[3],
const BMVert *v_example,
eBMCreateFlag create_flag);
/**
* \brief Main function for creating a new edge.
*
* \note Duplicate edges are supported by the API however users should _never_ see them.
* so unless you need a unique edge or know the edge won't exist,
* you should call with \a no_double = true.
*/
BMEdge *BM_edge_create(
BMesh *bm, BMVert *v1, BMVert *v2, const BMEdge *e_example, eBMCreateFlag create_flag);
/**
* Main face creation function
*
* \param bm: The mesh
* \param verts: A sorted array of verts size of len
* \param edges: A sorted array of edges size of len
* \param len: Length of the face
* \param create_flag: Options for creating the face
*/
BMFace *BM_face_create(BMesh *bm,
BMVert *const *verts,
BMEdge *const *edges,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag);
/**
* Wrapper for #BM_face_create when you don't have an edge array
*/
BMFace *BM_face_create_verts(BMesh *bm,
BMVert **vert_arr,
int len,
const BMFace *f_example,
eBMCreateFlag create_flag,
bool create_edges);
/**
* Kills all edges associated with \a f, along with any other faces containing those edges.
*/
void BM_face_edges_kill(BMesh *bm, BMFace *f);
/**
* kills all verts associated with \a f, along with any other faces containing
* those vertices
*/
void BM_face_verts_kill(BMesh *bm, BMFace *f);
/**
* A version of #BM_face_kill which removes edges and verts
* which have no remaining connected geometry.
*/
void BM_face_kill_loose(BMesh *bm, BMFace *f);
/**
* Kills \a f and its loops.
*/
void BM_face_kill(BMesh *bm, BMFace *f);
/**
* Kills \a e and all faces that use it.
*/
void BM_edge_kill(BMesh *bm, BMEdge *e);
/**
* Kills \a v and all edges that use it.
*/
void BM_vert_kill(BMesh *bm, BMVert *v);
/**
* \brief Splice Edge
*
* Splice two unique edges which share the same two vertices into one edge.
* (\a e_src into \a e_dst, removing e_src).
*
* \return Success
*
* \note Edges must already have the same vertices.
*/
bool BM_edge_splice(BMesh *bm, BMEdge *e_dst, BMEdge *e_src);
/**
* \brief Splice Vert
*
* Merges two verts into one
* (\a v_src into \a v_dst, removing \a v_src).
*
* \return Success
*
* \warning This doesn't work for collapsing edges,
* where \a v and \a vtarget are connected by an edge
* (assert checks for this case).
*
* \note To check if collapsing would create duplicate geometry, see:
* - #BM_vert_splice_check_double_edge.
* - #BM_vert_splice_check_double_face.
*/
bool BM_vert_splice(BMesh *bm, BMVert *v_dst, BMVert *v_src);
/**
* Check if splicing vertices would create any double edges.
*
* \note assume caller will handle case where verts share an edge.
*/
bool BM_vert_splice_check_double_edge(BMVert *v_a, BMVert *v_b);
/**
* Check if splicing vertices would create any double faces.
*
* \return true if calling #BM_vert_splice on the vertex pair would create a duplicate face.
*/
bool BM_vert_splice_check_double_face(BMVert *v_a, BMVert *v_b);
/**
* Check if collapsing `v_collapse`.would create duplicate faces.
*
* \param v_collapse: A vertex with exactly two connected edges (see #BM_vert_is_edge_pair).
*
* \return true if calling #BM_vert_collapse on `v_collapse` would create a duplicate face.
*/
bool BM_vert_collapse_check_double_face(BMVert *v_collapse);
/**
* Check if splitting a face between `l_a->v` & `l_b->v` would create
* a duplicate face on either side of the split.
*
* \note Arguments `(l_a, l_b, f_len)` are equivalent to `(l_b, l_a, (l_a->f->len - f_len) + 2)`,
* that is to say - the side of the face checked isn't important.
*/
bool BM_face_split_check_double_face(BMLoop *l_a, BMLoop *l_b, int f_len);
/**
* Check two faces share the same vertices over a partial span of their loops,
* comparing `l_a` -> `l_a_end` against `l_b` -> `l_b_end` (inclusive).
* Both spans must have the same topological length,
* and `l_a` & `l_b` must belong to different faces.
*
* Useful to check whether collapsing, splicing or splitting would create a duplicate face.
*/
bool BM_face_pair_overlap_check_subset_same_winding(const BMLoop *l_a,
const BMLoop *l_a_end,
const BMLoop *l_b,
const BMLoop *l_b_end);
/**
* A version of #BM_face_pair_overlap_check_subset_same_winding that walks `l_b` -> `l_b_end`
* in the reverse direction (for a candidate face of opposite winding).
*/
bool BM_face_pair_overlap_check_subset_swap_winding(const BMLoop *l_a,
const BMLoop *l_a_end,
const BMLoop *l_b,
const BMLoop *l_b_end);
/**
* \brief Loop Reverse
*
* Changes the winding order of a face from CW to CCW or vice versa.
*
* \param cd_loop_mdisp_offset: Cached result of `CustomData_get_offset(&bm->ldata, CD_MDISPS)`.
* \param use_loop_mdisp_flip: When set, flip the Z-depth of the mdisp,
* (use when flipping normals, disable when mirroring, eg: symmetrize).
*/
void bmesh_kernel_loop_reverse(BMesh *bm,
BMFace *f,
int cd_loop_mdisp_offset,
bool use_loop_mdisp_flip);
/**
* Avoid calling this where possible,
* low level function so both face pointers remain intact but point to swapped data.
* \note must be from the same bmesh.
*/
void bmesh_face_swap_data(BMFace *f_a, BMFace *f_b);
/**
* \brief Join Connected Faces
*
* Joins a collected group of faces into one. Only restriction on
* the input data is that the faces must be connected to each other.
*
* \return The newly created combine BMFace.
*
* \note If a pair of faces share multiple edges,
* the pair of faces will be joined at every edge.
*
* \param bm: The bmesh.
* \param faces: An array of faces to join.
* \param totface: The length of the face array to join.
* \param do_del: if true, remove the original faces, internal edges, and internal verts such that
* they are replaced by the new face.
* \param r_double: A pointer to a BMFace* that is controls processing of doubled faces.
* - When `r_double` is nullptr:
* - If a new face would be made which would double an existing face, then instead of creating a
* new face, the existing face will be reused and returned instead.
* - The calling function must not make ANY assumption about whether the returned BMFace* is
* new, or a reused face that may already have set header flags, contain custom data, etc.
* - When `r_double` is a pointer to a BMFace*:
* - If the new join face is not a double of an existing face, then `r_double` is set to nullptr.
* - If the new join face doubles an existing face, then `r_double` is set to the existing face,
* and the return value is the newly created face. The double will NOT be removed, meaning the
* BMesh is in an invalid state, and the calling function must fix that inconsistency.
* - If an error occurs and nullptr is returned, `r_double` will be set to nullptr as well.
*
* \note this is a generic, flexible join faces function,
* almost everything uses this, including #BM_faces_join_pair
*
* \note On callers asserting when `*r_double != nullptr`.
* For some callers the existing algorithm does not check for or handle double faces.
* This can result in invalid meshes being returned.
* The returned value in `r_double` should be examined and if found,
* the algorithm should be adjusted. Until this is changed, at least warn.
* This comment can be removed when all callers handle this case.
*/
BMFace *BM_faces_join(BMesh *bm, BMFace **faces, int totface, bool do_del, BMFace **r_double);
/**
* High level function which wraps both #bmesh_kernel_vert_separate and #bmesh_kernel_edge_separate
*/
void BM_vert_separate(BMesh *bm,
BMVert *v,
BMEdge **e_in,
int e_in_len,
bool copy_select,
BMVert ***r_vout,
int *r_vout_len);
/**
* A version of #BM_vert_separate which takes a flag.
*/
void BM_vert_separate_hflag(
BMesh *bm, BMVert *v, char hflag, bool copy_select, BMVert ***r_vout, int *r_vout_len);
void BM_vert_separate_tested_edges(
BMesh *bm, BMVert *v_dst, BMVert *v_src, bool (*testfn)(BMEdge *, void *arg), void *arg);
/**
* BMesh Kernel: For modifying structure.
*
* Names are on the verbose side but these are only for low-level access.
*/
/**
* \brief Separate Vert
*
* Separates all disjoint fans that meet at a vertex, making a unique
* vertex for each region. returns an array of all resulting vertices.
*
* \note this is a low level function, bm_edge_separate needs to run on edges first
* or, the faces sharing verts must not be sharing edges for them to split at least.
*
* \return Success
*/
void bmesh_kernel_vert_separate(
BMesh *bm, BMVert *v, BMVert ***r_vout, int *r_vout_len, bool copy_select);
/**
* \brief Separate Edge
*
* Separates a single edge into two edge: the original edge and
* a new edge that has only \a l_sep in its radial.
*
* \return Success
*
* \note Does nothing if \a l_sep is already the only loop in the
* edge radial.
*/
void bmesh_kernel_edge_separate(BMesh *bm, BMEdge *e, BMLoop *l_sep, bool copy_select);
/**
* \brief Split Face Make Edge (SFME)
*
* \warning this is a low level function, most likely you want to use #BM_face_split()
*
* Takes as input two vertices in a single face.
* An edge is created which divides the original face into two distinct regions.
* One of the regions is assigned to the original face and it is closed off.
* The second region has a new face assigned to it.
*
* \par Examples:
* <pre>
* Before: After:
* +--------+ +--------+
* | | | |
* | | | f1 |
* v1 f1 v2 v1======v2
* | | | f2 |
* | | | |
* +--------+ +--------+
* </pre>
*
* \note the input vertices can be part of the same edge. This will
* result in a two edged face. This is desirable for advanced construction
* tools and particularly essential for edge bevel. Because of this it is
* up to the caller to decide what to do with the extra edge.
*
* \note If \a holes is NULL, then both faces will lose
* all holes from the original face. Also, you cannot split between
* a hole vert and a boundary vert; that case is handled by higher-
* level wrapping functions (when holes are fully implemented, anyway).
*
* \note that holes represents which holes goes to the new face, and of
* course this requires removing them from the existing face first, since
* you cannot have linked list links inside multiple lists.
*
* \return A BMFace pointer
*/
BMFace *bmesh_kernel_split_face_make_edge(BMesh *bm,
BMFace *f,
BMLoop *l_v1,
BMLoop *l_v2,
BMLoop **r_l,
#ifdef USE_BMESH_HOLES
ListBaseT<BMLoopList> *holes,
#endif
BMEdge *example,
bool no_double);
/**
* \brief Split Edge Make Vert (SEMV)
*
* Takes \a e edge and splits it into two, creating a new vert.
* \a tv should be one end of \a e : the newly created edge
* will be attached to that end and is returned in \a r_e.
*
* \par Examples:
*
* <pre>
* E
* Before: OV-------------TV
* E RE
* After: OV------NV-----TV
* </pre>
*
* \return The newly created BMVert pointer.
*/
BMVert *bmesh_kernel_split_edge_make_vert(BMesh *bm, BMVert *tv, BMEdge *e, BMEdge **r_e);
/**
* \brief Join Edge Kill Vert (JEKV)
*
* Takes an edge \a e_kill and pointer to one of its vertices \a v_kill
* and collapses the edge on that vertex.
*
* \par Examples:
*
* <pre>
* Before: e_old e_kill
* +-------+-------+
* | | |
* v_old v_kill v_target
*
* After: e_old
* +---------------+
* | |
* v_old v_target
* </pre>
*
* \par Restrictions:
* KV is a vertex that must have a valance of exactly two. Furthermore
* both edges in KV's disk cycle (OE and KE) must be unique (no double edges).
*
* \return The resulting edge, NULL for failure.
*
* \note This euler has the possibility of creating
* faces with just 2 edges. It is up to the caller to decide what to do with
* these faces.
*/
BMEdge *bmesh_kernel_join_edge_kill_vert(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
bool do_del,
bool check_edge_exists,
bool kill_degenerate_faces,
bool kill_duplicate_faces);
/**
* \brief Join Vert Kill Edge (JVKE)
*
* Collapse an edge, merging surrounding data.
*
* Unlike #BM_vert_collapse_edge & #bmesh_kernel_join_edge_kill_vert
* which only handle 2 valence verts,
* this can handle any number of connected edges/faces.
*
* <pre>
* Before: -> After:
* +-+-+-+ +-+-+-+
* | | | | | \ / |
* +-+-+-+ +--+--+
* | | | | | / \ |
* +-+-+-+ +-+-+-+
* </pre>
*/
BMVert *bmesh_kernel_join_vert_kill_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
bool do_del,
bool check_edge_exists,
bool kill_degenerate_faces);
/**
* \brief Join Face Kill Edge (JFKE)
*
* Takes two faces joined by a single 2-manifold edge and fuses them together.
* The edge shared by the faces must not be connected to any other edges which have
* Both faces in its radial cycle
*
* \par Examples:
* <pre>
* A B
* +--------+ +--------+
* | | | |
* | f1 | | f1 |
* v1========v2 = Ok! v1==V2==v3 == Wrong!
* | f2 | | f2 |
* | | | |
* +--------+ +--------+
* </pre>
*
* In the example A, faces \a f1 and \a f2 are joined by a single edge,
* and the euler can safely be used.
* In example B however, \a f1 and \a f2 are joined by multiple edges and will produce an error.
* The caller in this case should call #bmesh_kernel_join_edge_kill_vert on the extra edges
* before attempting to fuse \a f1 and \a f2.
*
* \note The order of arguments decides whether or not certain per-face attributes are present
* in the resultant face. For instance vertex winding, material index, smooth flags,
* etc are inherited from \a f1, not \a f2.
*
* \return A BMFace pointer
*/
BMFace *bmesh_kernel_join_face_kill_edge(BMesh *bm, BMFace *f1, BMFace *f2, BMEdge *e);
/**
* \brief Un-glue Region Make Vert (URMV)
*
* Disconnects a face from its vertex fan at loop \a l_sep
*
* \return The newly created BMVert
*
* \note Will be a no-op and return original vertex if only two edges at that vertex.
*/
BMVert *bmesh_kernel_unglue_region_make_vert(BMesh *bm, BMLoop *l_sep);
/**
* A version of #bmesh_kernel_unglue_region_make_vert that disconnects multiple loops at once.
* The loops must all share the same vertex, can be in any order
* and are all moved to use a single new vertex - which is returned.
*
* This function handles the details of finding fans boundaries.
*/
BMVert *bmesh_kernel_unglue_region_make_vert_multi(BMesh *bm, BMLoop **larr, int larr_len);
/**
* This function assumes l_sep is a part of a larger fan which has already been
* isolated by calling #bmesh_kernel_edge_separate to segregate it radially.
*/
BMVert *bmesh_kernel_unglue_region_make_vert_multi_isolated(BMesh *bm, BMLoop *l_sep);
} // namespace blender

View File

@@ -0,0 +1,365 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM remove functions.
*/
#include "bmesh.hh"
namespace blender {
/* BMO functions */
/* -------------------------------------------------------------------- */
/** \name BMesh Operator Delete Functions
* \{ */
/**
* Called by operators to remove elements that they have marked for
* removal.
*/
static void bmo_remove_tagged_faces(BMesh *bm, const short oflag)
{
BMFace *f, *f_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (f, f_next, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, oflag)) {
BM_face_kill(bm, f);
}
}
}
static void bmo_remove_tagged_edges(BMesh *bm, const short oflag)
{
BMEdge *e, *e_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, oflag)) {
BM_edge_kill(bm, e);
}
}
}
static void bmo_remove_tagged_verts(BMesh *bm, const short oflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, oflag)) {
BM_vert_kill(bm, v);
}
}
}
static void bmo_remove_tagged_verts_loose(BMesh *bm, const short oflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, oflag) && (v->e == nullptr)) {
BM_vert_kill(bm, v);
}
}
}
void BMO_mesh_delete_oflag_tagged(BMesh *bm, const short oflag, const char htype)
{
if (htype & BM_FACE) {
bmo_remove_tagged_faces(bm, oflag);
}
if (htype & BM_EDGE) {
bmo_remove_tagged_edges(bm, oflag);
}
if (htype & BM_VERT) {
bmo_remove_tagged_verts(bm, oflag);
}
}
void BMO_mesh_delete_oflag_context(BMesh *bm,
const short oflag,
const int type,
FunctionRef<void()> prepare_fn)
{
BMEdge *e;
BMIter eiter;
BMIter fiter;
switch (type) {
case DEL_VERTS: {
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_verts(bm, oflag);
break;
}
case DEL_EDGES: {
/* flush down to vert */
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, oflag)) {
BMO_vert_flag_enable(bm, e->v1, oflag);
BMO_vert_flag_enable(bm, e->v2, oflag);
}
}
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_edges(bm, oflag);
bmo_remove_tagged_verts_loose(bm, oflag);
break;
}
case DEL_EDGESFACES: {
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_edges(bm, oflag);
break;
}
case DEL_ONLYFACES: {
if (prepare_fn) {
prepare_fn();
}
bmo_remove_tagged_faces(bm, oflag);
break;
}
case DEL_ONLYTAGGED: {
if (prepare_fn) {
prepare_fn();
}
BMO_mesh_delete_oflag_tagged(bm, oflag, BM_ALL_NOLOOP);
break;
}
case DEL_FACES:
case DEL_FACES_KEEP_BOUNDARY: {
/* go through and mark all edges and all verts of all faces for delete */
BMFace *f;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, oflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BMO_vert_flag_enable(bm, l_iter->v, oflag);
BMO_edge_flag_enable(bm, l_iter->e, oflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* now go through and mark all remaining faces all edges for keeping */
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BMO_face_flag_test(bm, f, oflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BMO_vert_flag_disable(bm, l_iter->v, oflag);
BMO_edge_flag_disable(bm, l_iter->e, oflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* also mark all the vertices of remaining edges for keeping */
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
/* Only exception to normal 'DEL_FACES' logic. */
if (type == DEL_FACES_KEEP_BOUNDARY) {
if (BM_edge_is_boundary(e)) {
BMO_edge_flag_disable(bm, e, oflag);
}
}
if (!BMO_edge_flag_test(bm, e, oflag)) {
BMO_vert_flag_disable(bm, e->v1, oflag);
BMO_vert_flag_disable(bm, e->v2, oflag);
}
}
if (prepare_fn) {
prepare_fn();
}
/* now delete marked face */
bmo_remove_tagged_faces(bm, oflag);
/* delete marked edge */
bmo_remove_tagged_edges(bm, oflag);
/* remove loose vertices */
bmo_remove_tagged_verts(bm, oflag);
break;
}
}
}
/** \} */
/* BM functions
*
* NOTE: this is just a duplicate of the code above (bad!)
* but for now keep in sync, its less hassle than having to create bmesh operator flags,
* each time we need to remove some geometry.
*/
/* -------------------------------------------------------------------- */
/** \name BMesh Delete Functions (no oflags)
* \{ */
static void bm_remove_tagged_faces(BMesh *bm, const char hflag)
{
BMFace *f, *f_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (f, f_next, &iter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, hflag)) {
BM_face_kill(bm, f);
}
}
}
static void bm_remove_tagged_edges(BMesh *bm, const char hflag)
{
BMEdge *e, *e_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, hflag)) {
BM_edge_kill(bm, e);
}
}
}
static void bm_remove_tagged_verts(BMesh *bm, const char hflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, hflag)) {
BM_vert_kill(bm, v);
}
}
}
static void bm_remove_tagged_verts_loose(BMesh *bm, const char hflag)
{
BMVert *v, *v_next;
BMIter iter;
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (BM_elem_flag_test(v, hflag) && (v->e == nullptr)) {
BM_vert_kill(bm, v);
}
}
}
void BM_mesh_delete_hflag_tagged(BMesh *bm, const char hflag, const char htype)
{
if (htype & BM_FACE) {
bm_remove_tagged_faces(bm, hflag);
}
if (htype & BM_EDGE) {
bm_remove_tagged_edges(bm, hflag);
}
if (htype & BM_VERT) {
bm_remove_tagged_verts(bm, hflag);
}
}
void BM_mesh_delete_hflag_context(BMesh *bm, const char hflag, const int type)
{
BMIter eiter;
BMIter fiter;
switch (type) {
case DEL_VERTS: {
bm_remove_tagged_verts(bm, hflag);
break;
}
case DEL_EDGES: {
/* flush down to vert */
BMEdge *e;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BM_elem_flag_test(e, hflag)) {
BM_elem_flag_enable(e->v1, hflag);
BM_elem_flag_enable(e->v2, hflag);
}
}
bm_remove_tagged_edges(bm, hflag);
bm_remove_tagged_verts_loose(bm, hflag);
break;
}
case DEL_EDGESFACES: {
bm_remove_tagged_edges(bm, hflag);
break;
}
case DEL_ONLYFACES: {
bm_remove_tagged_faces(bm, hflag);
break;
}
case DEL_ONLYTAGGED: {
BM_mesh_delete_hflag_tagged(bm, hflag, BM_ALL_NOLOOP);
break;
}
case DEL_FACES: {
/* go through and mark all edges and all verts of all faces for delete */
BMFace *f;
BMEdge *e;
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, hflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BM_elem_flag_enable(l_iter->v, hflag);
BM_elem_flag_enable(l_iter->e, hflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* now go through and mark all remaining faces all edges for keeping */
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, hflag)) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
BM_elem_flag_disable(l_iter->v, hflag);
BM_elem_flag_disable(l_iter->e, hflag);
} while ((l_iter = l_iter->next) != l_first);
}
}
/* also mark all the vertices of remaining edges for keeping */
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (!BM_elem_flag_test(e, hflag)) {
BM_elem_flag_disable(e->v1, hflag);
BM_elem_flag_disable(e->v2, hflag);
}
}
/* now delete marked face */
bm_remove_tagged_faces(bm, hflag);
/* delete marked edge */
bm_remove_tagged_edges(bm, hflag);
/* remove loose vertices */
bm_remove_tagged_verts(bm, hflag);
break;
}
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,39 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "BLI_function_ref.hh"
namespace blender {
void BMO_mesh_delete_oflag_tagged(BMesh *bm, short oflag, char htype);
void BM_mesh_delete_hflag_tagged(BMesh *bm, char hflag, char htype);
/**
* \param oflag: Geometry tagged with this operator flag is deleted.
* This flag applies to different types in some contexts, not just the type being removed.
*
* \param prepare_fn: Optional callback that runs before deleting geometry,
* use this to execute any logic that needs to ensure references to deleted geometry
* aren't held by the caller.
*/
void BMO_mesh_delete_oflag_context(BMesh *bm,
short oflag,
int type,
FunctionRef<void()> prepare_fn);
/**
* \param hflag: Geometry tagged with this operator flag is deleted.
* This flag applies to different types in some contexts, not just the type being removed.
*/
void BM_mesh_delete_hflag_context(BMesh *bm, char hflag, int type);
} // namespace blender

View File

@@ -0,0 +1,811 @@
/* SPDX-FileCopyrightText: 2013 by Campbell Barton. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Generic utility functions for getting edge loops from a mesh.
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_mempool.h"
#include "BLI_set.hh"
#include "BLI_stack.h"
#include "BLI_utildefines_iter.h"
#include "bmesh.hh"
#include "bmesh_edgeloop.hh" /* own include */
namespace blender {
struct BMEdgeLoopStore {
BMEdgeLoopStore *next, *prev;
ListBaseT<LinkData> verts;
int flag;
int len;
/* Optional values to calculate. */
float co[3], no[3];
};
#define BM_EDGELOOP_IS_CLOSED (1 << 0)
/* Use a small value since we need normals even for very small loops. */
#define EDGELOOP_EPS 1e-10f
/* -------------------------------------------------------------------- */
/* BM_mesh_edgeloops_find & Utility Functions. */
static int bm_vert_other_tag(BMVert *v, BMVert *v_prev, BMEdge **r_e)
{
BMIter iter;
BMEdge *e, *e_next = nullptr;
uint count = 0;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG)) {
BMVert *v_other = BM_edge_other_vert(e, v);
if (v_other != v_prev) {
e_next = e;
count++;
}
}
}
*r_e = e_next;
return count;
}
/**
* \return success
*/
static bool bm_loop_build(BMEdgeLoopStore *el_store, BMVert *v_prev, BMVert *v, int dir)
{
void (*add_fn)(ListBase *, void *) = dir == 1 ? BLI_addhead : BLI_addtail;
BMEdge *e_next;
BMVert *v_next;
BMVert *v_first = v;
BLI_assert(abs(dir) == 1);
if (!BM_elem_flag_test(v, BM_ELEM_INTERNAL_TAG)) {
return true;
}
while (v) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
int count;
node->data = v;
add_fn(&el_store->verts, node);
el_store->len++;
BM_elem_flag_disable(v, BM_ELEM_INTERNAL_TAG);
count = bm_vert_other_tag(v, v_prev, &e_next);
if (count == 1) {
v_next = BM_edge_other_vert(e_next, v);
BM_elem_flag_disable(e_next, BM_ELEM_INTERNAL_TAG);
if (UNLIKELY(v_next == v_first)) {
el_store->flag |= BM_EDGELOOP_IS_CLOSED;
v_next = nullptr;
}
}
else if (count == 0) {
/* pass */
v_next = nullptr;
}
else {
v_next = nullptr;
return false;
}
v_prev = v;
v = v_next;
}
return true;
}
int BM_mesh_edgeloops_find(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data)
{
BMIter iter;
BMEdge *e;
BMVert *v;
int count = 0;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_disable(v, BM_ELEM_INTERNAL_TAG);
}
/* first flush edges to tags, and tag verts */
BLI_Stack *edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BLI_assert(!BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG));
if (test_fn(e, user_data)) {
BM_elem_flag_enable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG);
BLI_stack_push(edge_stack, static_cast<void *>(&e));
}
else {
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
}
}
const uint edges_len = BLI_stack_count(edge_stack);
BMEdge **edges = MEM_new_array_uninitialized<BMEdge *>(edges_len, __func__);
BLI_stack_pop_n_reverse(edge_stack, edges, BLI_stack_count(edge_stack));
BLI_stack_free(edge_stack);
for (uint i = 0; i < edges_len; i += 1) {
e = edges[i];
if (BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG)) {
BMEdgeLoopStore *el_store = MEM_new_zeroed<BMEdgeLoopStore>(__func__);
/* add both directions */
if (bm_loop_build(el_store, e->v1, e->v2, 1) && bm_loop_build(el_store, e->v2, e->v1, -1) &&
el_store->len > 1)
{
BLI_addtail(r_eloops, el_store);
count++;
}
else {
BM_edgeloop_free(el_store);
}
}
}
for (uint i = 0; i < edges_len; i += 1) {
e = edges[i];
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_INTERNAL_TAG);
}
MEM_delete(edges);
return count;
}
/* -------------------------------------------------------------------- */
/* BM_mesh_edgeloops_find_path & Util Functions. */
/**
* Find s single, open edge loop - given 2 vertices.
* Add to
*/
struct VertStep {
VertStep *next, *prev;
BMVert *v;
};
static void vs_add(
BLI_mempool *vs_pool, ListBaseT<VertStep> *lb, BMVert *v, BMEdge *e_prev, const int iter_tot)
{
VertStep *vs_new = static_cast<VertStep *>(BLI_mempool_alloc(vs_pool));
vs_new->v = v;
BM_elem_index_set(v, iter_tot); /* set_dirty */
/* This edge stores a direct path back to the original vertex so we can
* backtrack without having to store an array of previous verts. */
/* WARNING: Setting the edge is not common practice but currently harmless, take care. */
BLI_assert(BM_vert_in_edge(e_prev, v));
v->e = e_prev;
BLI_addtail(lb, vs_new);
}
static bool bm_loop_path_build_step(BLI_mempool *vs_pool,
ListBaseT<VertStep> *lb,
const int dir,
BMVert *v_match[2])
{
ListBaseT<VertStep> lb_tmp = {nullptr, nullptr};
VertStep *vs, *vs_next;
BLI_assert(abs(dir) == 1);
for (vs = static_cast<VertStep *>(lb->first); vs; vs = vs_next) {
BMIter iter;
BMEdge *e;
/* these values will be the same every iteration */
const int vs_iter_tot = BM_elem_index_get(vs->v);
const int vs_iter_next = vs_iter_tot + dir;
vs_next = vs->next;
BM_ITER_ELEM (e, &iter, vs->v, BM_EDGES_OF_VERT) {
if (BM_elem_flag_test(e, BM_ELEM_INTERNAL_TAG)) {
BMVert *v_next = BM_edge_other_vert(e, vs->v);
const int v_next_index = BM_elem_index_get(v_next);
/* not essential to clear flag but prevents more checking next time round */
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
if (v_next_index == 0) {
vs_add(vs_pool, &lb_tmp, v_next, e, vs_iter_next);
}
else if ((dir < 0) == (v_next_index < 0)) {
/* on the same side - do nothing */
}
else {
/* we have met out match! (vertices from different sides meet) */
if (dir == 1) {
v_match[0] = vs->v;
v_match[1] = v_next;
}
else {
v_match[0] = v_next;
v_match[1] = vs->v;
}
/* normally we would manage memory of remaining items in (lb, lb_tmp),
* but search is done, vs_pool will get destroyed immediately */
return true;
}
}
}
BLI_mempool_free(vs_pool, vs);
}
/* Commented because used in a loop, and this flag has already been set. */
// bm->elem_index_dirty |= BM_VERT;
/* `lb` is now full of freed items, overwrite. */
*lb = lb_tmp;
return (lb->is_empty() == false);
}
bool BM_mesh_edgeloops_find_path(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data,
BMVert *v_src,
BMVert *v_dst)
{
BMIter iter;
BMEdge *e;
bool found = false;
BLI_assert(v_src != v_dst);
{
BMVert *v;
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BM_elem_index_set(v, 0);
BM_elem_flag_disable(v, BM_ELEM_INTERNAL_TAG);
}
}
bm->elem_index_dirty |= BM_VERT;
/* first flush edges to tags, and tag verts */
int edges_len;
BMEdge **edges;
if (test_fn) {
BLI_Stack *edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
if (test_fn(e, user_data)) {
BM_elem_flag_enable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG);
BLI_stack_push(edge_stack, static_cast<void *>(&e));
}
else {
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
}
}
edges_len = BLI_stack_count(edge_stack);
edges = MEM_new_array_uninitialized<BMEdge *>(edges_len, __func__);
BLI_stack_pop_n_reverse(edge_stack, edges, BLI_stack_count(edge_stack));
BLI_stack_free(edge_stack);
}
else {
int i = 0;
edges_len = bm->totedge;
edges = MEM_new_array_uninitialized<BMEdge *>(edges_len, __func__);
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
BM_elem_flag_enable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_INTERNAL_TAG);
edges[i] = e;
}
}
/* prime the lists and begin search */
{
BMVert *v_match[2] = {nullptr, nullptr};
ListBaseT<VertStep> lb_src = {nullptr, nullptr};
ListBaseT<VertStep> lb_dst = {nullptr, nullptr};
BLI_mempool *vs_pool = BLI_mempool_create(sizeof(VertStep), 0, 512, BLI_MEMPOOL_NOP);
/* edge args are dummy */
vs_add(vs_pool, &lb_src, v_src, v_src->e, 1);
vs_add(vs_pool, &lb_dst, v_dst, v_dst->e, -1);
bm->elem_index_dirty |= BM_VERT;
do {
if ((bm_loop_path_build_step(vs_pool, &lb_src, 1, v_match) == false) || v_match[0]) {
break;
}
if ((bm_loop_path_build_step(vs_pool, &lb_dst, -1, v_match) == false) || v_match[0]) {
break;
}
} while (true);
BLI_mempool_destroy(vs_pool);
if (v_match[0]) {
BMEdgeLoopStore *el_store = MEM_new_zeroed<BMEdgeLoopStore>(__func__);
BMVert *v;
/* build loop from edge pointers */
v = v_match[0];
while (true) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
node->data = v;
BLI_addhead(&el_store->verts, node);
el_store->len++;
if (v == v_src) {
break;
}
v = BM_edge_other_vert(v->e, v);
}
v = v_match[1];
while (true) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
node->data = v;
BLI_addtail(&el_store->verts, node);
el_store->len++;
if (v == v_dst) {
break;
}
v = BM_edge_other_vert(v->e, v);
}
BLI_addtail(r_eloops, el_store);
found = true;
}
}
for (uint i = 0; i < edges_len; i += 1) {
e = edges[i];
BM_elem_flag_disable(e, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v1, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_INTERNAL_TAG);
}
MEM_delete(edges);
return found;
}
/* -------------------------------------------------------------------- */
/* BM_mesh_edgeloops_xxx utility function */
void BM_mesh_edgeloops_free(ListBaseT<BMEdgeLoopStore> *eloops)
{
while (BMEdgeLoopStore *el_store = static_cast<BMEdgeLoopStore *>(BLI_pophead(eloops))) {
BM_edgeloop_free(el_store);
}
}
void BM_mesh_edgeloops_calc_center(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops)
{
for (BMEdgeLoopStore &el_store : *eloops) {
BM_edgeloop_calc_center(bm, &el_store);
}
}
void BM_mesh_edgeloops_calc_normal(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops)
{
for (BMEdgeLoopStore &el_store : *eloops) {
BM_edgeloop_calc_normal(bm, &el_store);
}
}
void BM_mesh_edgeloops_calc_normal_aligned(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *eloops,
const float no_align[3])
{
for (BMEdgeLoopStore &el_store : *eloops) {
BM_edgeloop_calc_normal_aligned(bm, &el_store, no_align);
}
}
void BM_mesh_edgeloops_calc_order(BMesh * /*bm*/,
ListBaseT<BMEdgeLoopStore> *eloops,
const bool use_normals)
{
ListBaseT<BMEdgeLoopStore> eloops_ordered = {nullptr};
float cent[3];
int tot = 0;
zero_v3(cent);
/* assumes we calculated centers already */
for (BMEdgeLoopStore &el_store : *eloops) {
if (!is_finite_v3(el_store.co)) [[unlikely]] {
continue;
}
add_v3_v3(cent, el_store.co);
tot += 1;
}
if (tot > 0) {
mul_v3_fl(cent, 1.0f / float(tot));
if (!is_finite_v3(cent)) {
zero_v3(cent);
}
}
/* Find the furthest out loop. */
{
BMEdgeLoopStore *el_store_best = nullptr;
float len_best_sq = -1.0f;
for (BMEdgeLoopStore &el_store : *eloops) {
const float len_sq = len_squared_v3v3(cent, el_store.co);
/* Null check to account for non-finite distances. */
if ((len_sq > len_best_sq) || (el_store_best == nullptr)) {
len_best_sq = len_sq;
el_store_best = &el_store;
}
}
BLI_remlink(eloops, el_store_best);
BLI_addtail(&eloops_ordered, el_store_best);
}
/* not so efficient re-ordering */
while (eloops->first) {
BMEdgeLoopStore *el_store_best = nullptr;
const float *co = (static_cast<BMEdgeLoopStore *>(eloops_ordered.last))->co;
const float *no = (static_cast<BMEdgeLoopStore *>(eloops_ordered.last))->no;
float len_best_sq = FLT_MAX;
if (use_normals) {
BLI_ASSERT_UNIT_V3(no);
}
for (BMEdgeLoopStore &el_store : *eloops) {
float len_sq;
if (use_normals) {
/* Scale the length by how close the loops are to pointing at each other. */
float dir[3];
sub_v3_v3v3(dir, co, el_store.co);
len_sq = normalize_v3(dir);
len_sq = len_sq *
((1.0f - fabsf(dot_v3v3(dir, no))) + (1.0f - fabsf(dot_v3v3(dir, el_store.no))));
}
else {
len_sq = len_squared_v3v3(co, el_store.co);
}
/* Null check to account for non-finite distances. */
if ((len_sq < len_best_sq) || (el_store_best == nullptr)) {
len_best_sq = len_sq;
el_store_best = &el_store;
}
}
BLI_remlink(eloops, el_store_best);
BLI_addtail(&eloops_ordered, el_store_best);
}
*eloops = eloops_ordered;
}
/* -------------------------------------------------------------------- */
/* BM_edgeloop_*** functions */
BMEdgeLoopStore *BM_edgeloop_copy(BMEdgeLoopStore *el_store)
{
BMEdgeLoopStore *el_store_copy = MEM_new_uninitialized<BMEdgeLoopStore>(__func__);
*el_store_copy = *el_store;
BLI_duplicatelist(&el_store_copy->verts, &el_store->verts);
return el_store_copy;
}
BMEdgeLoopStore *BM_edgeloop_from_verts(BMVert **v_arr, const int v_arr_tot, bool is_closed)
{
BMEdgeLoopStore *el_store = MEM_new_zeroed<BMEdgeLoopStore>(__func__);
int i;
for (i = 0; i < v_arr_tot; i++) {
LinkData *node = MEM_new_zeroed<LinkData>(__func__);
node->data = v_arr[i];
BLI_addtail(&el_store->verts, node);
}
el_store->len = v_arr_tot;
if (is_closed) {
el_store->flag |= BM_EDGELOOP_IS_CLOSED;
}
return el_store;
}
void BM_edgeloop_free(BMEdgeLoopStore *el_store)
{
el_store->verts.free_no_destruct();
MEM_delete(el_store);
}
bool BM_edgeloop_is_closed(BMEdgeLoopStore *el_store)
{
return (el_store->flag & BM_EDGELOOP_IS_CLOSED) != 0;
}
ListBaseT<LinkData> *BM_edgeloop_verts_get(BMEdgeLoopStore *el_store)
{
return &el_store->verts;
}
int BM_edgeloop_length_get(BMEdgeLoopStore *el_store)
{
return el_store->len;
}
const float *BM_edgeloop_normal_get(BMEdgeLoopStore *el_store)
{
return el_store->no;
}
const float *BM_edgeloop_center_get(BMEdgeLoopStore *el_store)
{
return el_store->co;
}
#define NODE_AS_V(n) ((BMVert *)((LinkData *)n)->data)
#define NODE_AS_CO(n) ((BMVert *)((LinkData *)n)->data)->co
void BM_edgeloop_edges_get(BMEdgeLoopStore *el_store, BMEdge **e_arr)
{
LinkData *node;
int i = 0;
for (node = static_cast<LinkData *>(el_store->verts.first); node && node->next;
node = node->next)
{
e_arr[i++] = BM_edge_exists(NODE_AS_V(node), NODE_AS_V(node->next));
BLI_assert(e_arr[i - 1] != nullptr);
}
if (el_store->flag & BM_EDGELOOP_IS_CLOSED) {
e_arr[i] = BM_edge_exists(NODE_AS_V(el_store->verts.first), NODE_AS_V(el_store->verts.last));
BLI_assert(e_arr[i] != nullptr);
}
BLI_assert(el_store->len == i + 1);
}
void BM_edgeloop_calc_center(BMesh * /*bm*/, BMEdgeLoopStore *el_store)
{
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.last);
LinkData *node_prev = (static_cast<LinkData *>(el_store->verts.last))->prev;
LinkData *node_first = static_cast<LinkData *>(el_store->verts.first);
LinkData *node_next = node_first;
const float *v_prev = NODE_AS_CO(node_prev);
const float *v_curr = NODE_AS_CO(node_curr);
const float *v_next = NODE_AS_CO(node_next);
float totw = 0.0f;
float w_prev;
zero_v3(el_store->co);
w_prev = len_v3v3(v_prev, v_curr);
do {
const float w_curr = len_v3v3(v_curr, v_next);
const float w = (w_curr + w_prev);
madd_v3_v3fl(el_store->co, v_curr, w);
totw += w;
w_prev = w_curr;
node_prev = node_curr;
node_curr = node_next;
node_next = node_next->next;
if (node_next == nullptr) {
break;
}
v_prev = v_curr;
v_curr = v_next;
v_next = NODE_AS_CO(node_next);
} while (true);
if (totw != 0.0f) {
mul_v3_fl(el_store->co, 1.0f / totw);
}
}
bool BM_edgeloop_calc_normal(BMesh * /*bm*/, BMEdgeLoopStore *el_store)
{
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
const float *v_prev = NODE_AS_CO(el_store->verts.last);
const float *v_curr = NODE_AS_CO(node_curr);
zero_v3(el_store->no);
/* Newell's Method */
do {
add_newell_cross_v3_v3v3(el_store->no, v_prev, v_curr);
if ((node_curr = node_curr->next)) {
v_prev = v_curr;
v_curr = NODE_AS_CO(node_curr);
}
else {
break;
}
} while (true);
if (UNLIKELY(normalize_v3(el_store->no) < EDGELOOP_EPS)) {
el_store->no[2] = 1.0f; /* other axis set to 0.0 */
return false;
}
return true;
}
bool BM_edgeloop_calc_normal_aligned(BMesh * /*bm*/,
BMEdgeLoopStore *el_store,
const float no_align[3])
{
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
const float *v_prev = NODE_AS_CO(el_store->verts.last);
const float *v_curr = NODE_AS_CO(node_curr);
zero_v3(el_store->no);
/* Own Method */
do {
float cross[3], no[3], dir[3];
sub_v3_v3v3(dir, v_curr, v_prev);
cross_v3_v3v3(cross, no_align, dir);
cross_v3_v3v3(no, dir, cross);
add_v3_v3(el_store->no, no);
if ((node_curr = node_curr->next)) {
v_prev = v_curr;
v_curr = NODE_AS_CO(node_curr);
}
else {
break;
}
} while (true);
if (UNLIKELY(normalize_v3(el_store->no) < EDGELOOP_EPS)) {
el_store->no[2] = 1.0f; /* other axis set to 0.0 */
return false;
}
return true;
}
void BM_edgeloop_flip(BMesh * /*bm*/, BMEdgeLoopStore *el_store)
{
negate_v3(el_store->no);
BLI_listbase_reverse(&el_store->verts);
}
void BM_edgeloop_expand(
BMesh *bm, BMEdgeLoopStore *el_store, int el_store_len, bool split, Set<BMEdge *> *split_edges)
{
bool split_swap = true;
#define EDGE_SPLIT(node_copy, node_other) \
{ \
BMVert *v_split, *v_other = static_cast<BMVert *>((node_other)->data); \
BMEdge *e_split, \
*e_other = BM_edge_exists(static_cast<BMVert *>((node_copy)->data), v_other); \
v_split = BM_edge_split(bm, \
e_other, \
static_cast<BMVert *>(split_swap ? (node_copy)->data : v_other), \
&e_split, \
0.0f); \
v_split->e = e_split; \
BLI_assert(v_split == e_split->v2); \
split_edges->add(e_split); \
(node_copy)->data = v_split; \
} \
((void)0)
/* first double until we are more than half as big */
while ((el_store->len * 2) < el_store_len) {
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
while (node_curr) {
LinkData *node_curr_copy = MEM_dupalloc(node_curr);
if (split == false) {
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
if (node_curr->next || (el_store->flag & BM_EDGELOOP_IS_CLOSED)) {
EDGE_SPLIT(node_curr_copy,
node_curr->next ? node_curr->next : (LinkData *)el_store->verts.first);
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
EDGE_SPLIT(node_curr_copy, node_curr->prev);
BLI_insertlinkbefore(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr->next;
}
split_swap = !split_swap;
}
el_store->len++;
}
split_swap = !split_swap;
}
if (el_store->len < el_store_len) {
LinkData *node_curr = static_cast<LinkData *>(el_store->verts.first);
int iter_prev = 0;
BLI_FOREACH_SPARSE_RANGE (el_store->len, (el_store_len - el_store->len), iter) {
while (iter_prev < iter) {
node_curr = node_curr->next;
iter_prev += 1;
}
LinkData *node_curr_copy;
node_curr_copy = MEM_dupalloc(node_curr);
if (split == false) {
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
if (node_curr->next || (el_store->flag & BM_EDGELOOP_IS_CLOSED)) {
EDGE_SPLIT(node_curr_copy,
node_curr->next ? node_curr->next : (LinkData *)el_store->verts.first);
BLI_insertlinkafter(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr_copy->next;
}
else {
EDGE_SPLIT(node_curr_copy, node_curr->prev);
BLI_insertlinkbefore(&el_store->verts, node_curr, node_curr_copy);
node_curr = node_curr->next;
}
split_swap = !split_swap;
}
el_store->len++;
iter_prev += 1;
}
}
#undef BKE_FOREACH_SUBSET_OF_RANGE
#undef EDGE_SPLIT
BLI_assert(el_store->len == el_store_len);
}
bool BM_edgeloop_overlap_check(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b)
{
/* A little more efficient if 'a' as smaller. */
if (el_store_a->len > el_store_b->len) {
std::swap(el_store_a, el_store_b);
}
/* init */
for (LinkData &node : el_store_a->verts) {
BM_elem_flag_enable((BMVert *)node.data, BM_ELEM_INTERNAL_TAG);
}
for (LinkData &node : el_store_b->verts) {
BM_elem_flag_disable((BMVert *)node.data, BM_ELEM_INTERNAL_TAG);
}
/* Check 'a' (clear as we go). */
for (LinkData &node : el_store_a->verts) {
if (!BM_elem_flag_test((BMVert *)node.data, BM_ELEM_INTERNAL_TAG)) {
/* Finish clearing 'a', leave tag clean. */
LinkData *remaining_node = &node;
while ((remaining_node = remaining_node->next)) {
BM_elem_flag_disable((BMVert *)remaining_node->data, BM_ELEM_INTERNAL_TAG);
}
return true;
}
BM_elem_flag_disable((BMVert *)node.data, BM_ELEM_INTERNAL_TAG);
}
return false;
}
} // namespace blender

View File

@@ -0,0 +1,91 @@
/* SPDX-FileCopyrightText: 2013 by Campbell Barton. All rights reserved.
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "DNA_listBase.h"
#include "BLI_set.hh"
namespace blender {
struct BMEdgeLoopStore;
struct LinkData;
/* multiple edgeloops (ListBase) */
/**
* \return listbase of listbases, each linking to a vertex.
*/
int BM_mesh_edgeloops_find(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data);
bool BM_mesh_edgeloops_find_path(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *r_eloops,
bool (*test_fn)(BMEdge *, void *user_data),
void *user_data,
BMVert *v_src,
BMVert *v_dst);
void BM_mesh_edgeloops_free(ListBaseT<BMEdgeLoopStore> *eloops);
void BM_mesh_edgeloops_calc_center(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops);
void BM_mesh_edgeloops_calc_normal(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops);
void BM_mesh_edgeloops_calc_normal_aligned(BMesh *bm,
ListBaseT<BMEdgeLoopStore> *eloops,
const float no_align[3]);
void BM_mesh_edgeloops_calc_order(BMesh *bm, ListBaseT<BMEdgeLoopStore> *eloops, bool use_normals);
/**
* Copy a single edge-loop.
* \return new edge-loops.
*/
BMEdgeLoopStore *BM_edgeloop_copy(BMEdgeLoopStore *el_store);
BMEdgeLoopStore *BM_edgeloop_from_verts(BMVert **v_arr, int v_arr_tot, bool is_closed);
void BM_edgeloop_free(BMEdgeLoopStore *el_store);
bool BM_edgeloop_is_closed(BMEdgeLoopStore *el_store);
int BM_edgeloop_length_get(BMEdgeLoopStore *el_store);
ListBaseT<LinkData> *BM_edgeloop_verts_get(BMEdgeLoopStore *el_store);
const float *BM_edgeloop_normal_get(BMEdgeLoopStore *el_store);
const float *BM_edgeloop_center_get(BMEdgeLoopStore *el_store);
/**
* Edges are assigned to one vert -> the next.
*/
void BM_edgeloop_edges_get(BMEdgeLoopStore *el_store, BMEdge **e_arr);
void BM_edgeloop_calc_center(BMesh *bm, BMEdgeLoopStore *el_store);
bool BM_edgeloop_calc_normal(BMesh *bm, BMEdgeLoopStore *el_store);
/**
* For open loops that are straight lines,
* calculating the normal as if it were a polygon is meaningless.
*
* Instead use an alignment vector and calculate the normal based on that.
*/
bool BM_edgeloop_calc_normal_aligned(BMesh *bm,
BMEdgeLoopStore *el_store,
const float no_align[3]);
void BM_edgeloop_flip(BMesh *bm, BMEdgeLoopStore *el_store);
void BM_edgeloop_expand(BMesh *bm,
BMEdgeLoopStore *el_store,
int el_store_len,
bool split,
Set<BMEdge *> *split_edges);
bool BM_edgeloop_overlap_check(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b);
#define BM_EDGELINK_NEXT(el_store, elink) \
(elink)->next ? \
(elink)->next : \
(BM_edgeloop_is_closed(el_store) ? (LinkData *)BM_edgeloop_verts_get(el_store)->first : \
NULL)
#define BM_EDGELOOP_NEXT(el_store) \
(CHECK_TYPE_INLINE(el_store, BMEdgeLoopStore *), (BMEdgeLoopStore *)((LinkData *)el_store)->next)
} // namespace blender

View File

@@ -0,0 +1,97 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_operator_api.hh"
#include "bmesh_class.hh"
namespace blender {
/*----------- BMOP error system ----------*/
/**
* \note More can be added as needed.
*/
enum eBMOpErrorLevel {
/**
* Use when the operation could not succeed,
* typically from input that isn't sufficient for completing the operation.
*/
BMO_ERROR_CANCEL = 0,
/**
* Use this when one or more operations could not succeed,
* when the resulting mesh can be used (since some operations succeeded or no change was made).
* This is used by default.
*/
BMO_ERROR_WARN = 1,
/**
* The mesh resulting from this operation should not be used (where possible).
* It should not be left in a corrupt state either.
*
* See #BMBackup type & function calls.
*/
BMO_ERROR_FATAL = 2,
};
/**
* Pushes an error onto the bmesh error stack.
* if msg is null, then the default message for the `errcode` is used.
*/
void BMO_error_raise(BMesh *bm, BMOperator *owner, eBMOpErrorLevel level, const char *msg)
ATTR_NONNULL(1, 2, 4);
/**
* Gets the topmost error from the stack.
* returns error code or 0 if no error.
*/
bool BMO_error_get(BMesh *bm, const char **r_msg, BMOperator **r_op, eBMOpErrorLevel *r_level);
bool BMO_error_get_at_level(BMesh *bm,
eBMOpErrorLevel level,
const char **r_msg,
BMOperator **r_op);
bool BMO_error_occurred_at_level(BMesh *bm, eBMOpErrorLevel level);
/* Same as #BMO_error_get, only pops the error off the stack as well. */
bool BMO_error_pop(BMesh *bm, const char **r_msg, BMOperator **r_op, eBMOpErrorLevel *r_level);
void BMO_error_clear(BMesh *bm);
/* This is meant for handling errors, like self-intersection test failures.
* it's dangerous to handle errors in general though, so disabled for now. */
/* Catches an error raised by the op pointed to by catchop. */
/* Not yet implemented. */
// int BMO_error_catch_op(BMesh *bm, BMOperator *catchop, char **r_msg);
#define BM_ELEM_INDEX_VALIDATE(_bm, _msg_a, _msg_b) \
BM_mesh_elem_index_validate(_bm, __FILE__ ":" STRINGIFY(__LINE__), __func__, _msg_a, _msg_b)
/* BMESH_ASSERT */
#ifdef WITH_ASSERT_ABORT
# define _BMESH_DUMMY_ABORT abort
#else
# define _BMESH_DUMMY_ABORT() (void)0
#endif
/**
* This is meant to be higher level than BLI_assert(),
* its enabled even when in Release mode.
*/
#define BMESH_ASSERT(a) \
(void)((!(a)) ? ((fprintf(stderr, \
"BMESH_ASSERT failed: %s, %s(), %d at \'%s\'\n", \
__FILE__, \
__func__, \
__LINE__, \
STRINGIFY(a)), \
_BMESH_DUMMY_ABORT(), \
NULL)) : \
NULL)
} // namespace blender

View File

@@ -0,0 +1,133 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM Inline functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
namespace blender {
/* stuff for dealing with header flags */
#define BM_elem_flag_test(ele, hflag) _bm_elem_flag_test(&(ele)->head, hflag)
#define BM_elem_flag_test_bool(ele, hflag) _bm_elem_flag_test_bool(&(ele)->head, hflag)
#define BM_elem_flag_enable(ele, hflag) _bm_elem_flag_enable(&(ele)->head, hflag)
#define BM_elem_flag_disable(ele, hflag) _bm_elem_flag_disable(&(ele)->head, hflag)
#define BM_elem_flag_set(ele, hflag, val) _bm_elem_flag_set(&(ele)->head, hflag, val)
#define BM_elem_flag_toggle(ele, hflag) _bm_elem_flag_toggle(&(ele)->head, hflag)
#define BM_elem_flag_merge(ele_a, ele_b) _bm_elem_flag_merge(&(ele_a)->head, &(ele_b)->head)
#define BM_elem_flag_merge_ex(ele_a, ele_b, hflag_and) \
_bm_elem_flag_merge_ex(&(ele_a)->head, &(ele_b)->head, hflag_and)
#define BM_elem_flag_merge_into(ele, ele_a, ele_b) \
_bm_elem_flag_merge_into(&(ele)->head, &(ele_a)->head, &(ele_b)->head)
ATTR_WARN_UNUSED_RESULT
BLI_INLINE char _bm_elem_flag_test(const BMHeader *head, const char hflag)
{
return head->hflag & hflag;
}
ATTR_WARN_UNUSED_RESULT
BLI_INLINE bool _bm_elem_flag_test_bool(const BMHeader *head, const char hflag)
{
return (head->hflag & hflag) != 0;
}
BLI_INLINE void _bm_elem_flag_enable(BMHeader *head, const char hflag)
{
head->hflag |= hflag;
}
BLI_INLINE void _bm_elem_flag_disable(BMHeader *head, const char hflag)
{
head->hflag &= char(~hflag);
}
BLI_INLINE void _bm_elem_flag_set(BMHeader *head, const char hflag, const int val)
{
if (val) {
_bm_elem_flag_enable(head, hflag);
}
else {
_bm_elem_flag_disable(head, hflag);
}
}
BLI_INLINE void _bm_elem_flag_toggle(BMHeader *head, const char hflag)
{
head->hflag ^= hflag;
}
BLI_INLINE void _bm_elem_flag_merge(BMHeader *head_a, BMHeader *head_b)
{
head_a->hflag = head_b->hflag = head_a->hflag | head_b->hflag;
}
BLI_INLINE void _bm_elem_flag_merge_ex(BMHeader *head_a, BMHeader *head_b, const char hflag_and)
{
if (((head_a->hflag & head_b->hflag) & hflag_and) == 0) {
head_a->hflag &= ~hflag_and;
head_b->hflag &= ~hflag_and;
}
_bm_elem_flag_merge(head_a, head_b);
}
BLI_INLINE void _bm_elem_flag_merge_into(BMHeader *head,
const BMHeader *head_a,
const BMHeader *head_b)
{
head->hflag = head_a->hflag | head_b->hflag;
}
/**
* notes on #BM_elem_index_set(...) usage,
* Set index is sometimes abused as temp storage, other times we can't be
* sure if the index values are valid because certain operations have modified
* the mesh structure.
*
* To set the elements to valid indices 'BM_mesh_elem_index_ensure' should be used
* rather than adding inline loops, however there are cases where we still
* set the index directly
*
* In an attempt to manage this,
* here are 5 tags I'm adding to uses of #BM_elem_index_set
*
* - `set_inline` -- since the data is already being looped over set to a
* valid value inline.
*
* - `set_dirty!` -- intentionally sets the index to an invalid value,
* flagging `bm->elem_index_dirty` so we don't use it.
*
* - `set_ok` -- this is valid use since the part of the code is low level.
*
* - `set_ok_invalid` -- set to -1 on purpose since this should not be
* used without a full array re-index, do this on
* adding new vert/edge/faces since they may be added at
* the end of the array.
*
* - campbell */
#define BM_elem_index_get(ele) _bm_elem_index_get(&(ele)->head)
#define BM_elem_index_set(ele, index) _bm_elem_index_set(&(ele)->head, index)
BLI_INLINE void _bm_elem_index_set(BMHeader *head, const int index)
{
head->index = index;
}
ATTR_WARN_UNUSED_RESULT
BLI_INLINE int _bm_elem_index_get(const BMHeader *head)
{
return head->index;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_string_ref.hh"
#include "bmesh_class.hh"
namespace blender {
struct LinkNode;
struct MemArena;
namespace bke {
enum class AttrDomain : int8_t;
enum class AttrType : int16_t;
} // namespace bke
void BM_loop_interp_multires_ex(BMesh *bm,
BMLoop *l_dst,
const BMFace *f_src,
const float f_dst_center[3],
const float f_src_center[3],
int cd_loop_mdisp_offset);
/**
* Project the multi-resolution grid in target onto f_src's set of multi-resolution grids.
*/
void BM_loop_interp_multires(BMesh *bm, BMLoop *l_dst, const BMFace *f_src);
void BM_face_interp_multires_ex(BMesh *bm,
BMFace *f_dst,
const BMFace *f_src,
const float f_dst_center[3],
const float f_src_center[3],
int cd_loop_mdisp_offset);
void BM_face_interp_multires(BMesh *bm, BMFace *f_dst, const BMFace *f_src);
void BM_vert_interp_from_face(BMesh *bm, BMVert *v_dst, const BMFace *f_src);
/**
* \brief Data, Interpolate From Verts
*
* Interpolates per-vertex data from two sources to \a v_dst
*
* \note This is an exact match to #BM_data_interp_from_edges.
*/
void BM_data_interp_from_verts(
BMesh *bm, const BMVert *v_src_1, const BMVert *v_src_2, BMVert *v_dst, float fac);
/**
* \brief Data, Interpolate From Edges
*
* Interpolates per-edge data from two sources to \a e_dst.
*
* \note This is an exact match to #BM_data_interp_from_verts.
*/
void BM_data_interp_from_edges(
BMesh *bm, const BMEdge *e_src_1, const BMEdge *e_src_2, BMEdge *e_dst, float fac);
/**
* \brief Data Face-Vert Edge Interpolate
*
* Walks around the faces of \a e and interpolates
* the loop data between two sources.
*/
void BM_data_interp_face_vert_edge(
BMesh *bm, const BMVert *v_src_1, const BMVert *v_src_2, BMVert *v, BMEdge *e, float fac);
void BM_data_layer_add(BMesh *bm, CustomData *data, int type);
void BM_data_layer_add_named(BMesh *bm, CustomData *data, int type, StringRef name);
void BM_data_layer_ensure_named(BMesh *bm, CustomData *data, int type, StringRef name);
bool BM_data_layer_has_named(const BMesh *bm, const CustomData *data, int type, StringRef name);
void BM_data_layer_free(BMesh *bm, CustomData *data, int type);
/** Ensure the dependent boolean layers exist for all face corner #CD_PROP_FLOAT2 layers. */
void BM_uv_map_attr_pin_ensure_for_all_layers(BMesh *bm);
void BM_uv_map_attr_pin_ensure_named(BMesh *bm, StringRef uv_map_name);
bool BM_uv_map_attr_pin_exists(const BMesh *bm, StringRef uv_map_name);
/**
* Remove a named custom data layer, if it existed. Return true if the layer was removed.
*/
bool BM_data_layer_free_named(BMesh *bm, CustomData *data, StringRef name);
void BM_data_layer_free_n(BMesh *bm, CustomData *data, int type, int n);
void BM_data_layer_copy(BMesh *bm, CustomData *data, int type, int src_n, int dst_n);
/* See #BM_data_layer_lookup. */
struct BMDataLayerLookup {
const int offset = -1;
bke::AttrDomain domain;
bke::AttrType type;
const CustomDataLayer *layer = nullptr;
operator bool() const
{
return offset != -1;
}
};
/**
* Search for a named custom data layer on all attribute domains and return the domain and type.
* This is roughly analogous to #Mesh::attributes().lookup(...), but keep in mind that certain
* attributes stored on #Mesh are not stored as attributes on #BMesh.
*/
BMDataLayerLookup BM_data_layer_lookup(const BMesh &bm, const StringRef name);
float BM_elem_float_data_get(CustomData *cd, void *element, int type);
void BM_elem_float_data_set(CustomData *cd, void *element, int type, float val);
/**
* \brief Data Interpolate From Face
*
* Projects target onto source, and pulls interpolated custom-data from source.
*
* \note Only handles loop custom-data. multi-res is handled.
* \note Attributes such as selection, material & normals
* must be handled with a separate call to #BM_elem_attrs_copy.
*/
void BM_face_interp_from_face_ex(BMesh *bm,
BMFace *f_dst,
const BMFace *f_src,
bool do_vertex,
const void **blocks,
const void **blocks_v,
float (*cos_2d)[2],
float axis_mat[3][3]);
void BM_face_interp_from_face(BMesh *bm, BMFace *f_dst, const BMFace *f_src, bool do_vertex);
/**
* Projects a single loop, target, onto f_src for custom-data interpolation.
* multi-resolution is handled.
* \param do_vertex: When true the target's vert data will also get interpolated.
*/
void BM_loop_interp_from_face(
BMesh *bm, BMLoop *l_dst, const BMFace *f_src, bool do_vertex, bool do_multires);
/**
* Smooths boundaries between multi-res grids,
* including some borders in adjacent faces.
*/
void BM_face_multires_bounds_smooth(BMesh *bm, BMFace *f);
LinkNode *BM_vert_loop_groups_data_layer_create(
BMesh *bm, BMVert *v, int layer_n, const float *loop_weights, MemArena *arena);
/**
* Take existing custom data and merge each fan's data.
*/
void BM_vert_loop_groups_data_layer_merge(BMesh *bm, LinkNode *groups, int layer_n);
/**
* A version of #BM_vert_loop_groups_data_layer_merge
* that takes an array of loop-weights (aligned with #BM_LOOPS_OF_VERT iterator).
*/
void BM_vert_loop_groups_data_layer_merge_weights(BMesh *bm,
LinkNode *groups,
int layer_n,
const float *loop_weights);
} // namespace blender

View File

@@ -0,0 +1,661 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Functions to abstract looping over bmesh data structures.
*
* See: bmesh_iterators_inlin.c too, some functions are here for speed reasons.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "intern/bmesh_structure.hh"
namespace blender {
const char bm_iter_itype_htype_map[BM_ITYPE_MAX] = {
'\0',
BM_VERT, /* BM_VERTS_OF_MESH */
BM_EDGE, /* BM_EDGES_OF_MESH */
BM_FACE, /* BM_FACES_OF_MESH */
BM_EDGE, /* BM_EDGES_OF_VERT */
BM_FACE, /* BM_FACES_OF_VERT */
BM_LOOP, /* BM_LOOPS_OF_VERT */
BM_VERT, /* BM_VERTS_OF_EDGE */
BM_FACE, /* BM_FACES_OF_EDGE */
BM_VERT, /* BM_VERTS_OF_FACE */
BM_EDGE, /* BM_EDGES_OF_FACE */
BM_LOOP, /* BM_LOOPS_OF_FACE */
BM_LOOP, /* BM_LOOPS_OF_LOOP */
BM_LOOP, /* BM_LOOPS_OF_EDGE */
};
int BM_iter_mesh_count(const char itype, BMesh *bm)
{
int count;
switch (itype) {
case BM_VERTS_OF_MESH:
count = bm->totvert;
break;
case BM_EDGES_OF_MESH:
count = bm->totedge;
break;
case BM_FACES_OF_MESH:
count = bm->totface;
break;
default:
count = 0;
BLI_assert(0);
break;
}
return count;
}
void *BM_iter_at_index(BMesh *bm, const char itype, void *data, int index)
{
BMIter iter;
void *val;
int i;
/* sanity check */
if (index < 0) {
return nullptr;
}
val = BM_iter_new(&iter, bm, itype, data);
i = 0;
while (i < index) {
val = BM_iter_step(&iter);
i++;
}
return val;
}
int BM_iter_as_array(BMesh *bm, const char itype, void *data, void **array, const int len)
{
int i = 0;
/* sanity check */
if (len > 0) {
BMIter iter;
void *ele;
for (ele = BM_iter_new(&iter, bm, itype, data); ele; ele = BM_iter_step(&iter)) {
array[i] = ele;
i++;
if (i == len) {
return len;
}
}
}
return i;
}
int BMO_iter_as_array(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const char restrictmask,
void **array,
const int len)
{
int i = 0;
/* sanity check */
if (len > 0) {
BMOIter oiter;
void *ele;
for (ele = BMO_iter_new(&oiter, slot_args, slot_name, restrictmask); ele;
ele = BMO_iter_step(&oiter))
{
array[i] = ele;
i++;
if (i == len) {
return len;
}
}
}
return i;
}
void *BM_iter_as_arrayN(BMesh *bm,
const char itype,
void *data,
int *r_len,
/* optional args to avoid an alloc (normally stack array) */
void **stack_array,
int stack_array_size)
{
BMIter iter;
BLI_assert(stack_array_size == 0 || (stack_array_size && stack_array));
/* We can't rely on #BMIter.count being set. */
switch (itype) {
case BM_VERTS_OF_MESH:
iter.count = bm->totvert;
break;
case BM_EDGES_OF_MESH:
iter.count = bm->totedge;
break;
case BM_FACES_OF_MESH:
iter.count = bm->totface;
break;
default:
break;
}
if (BM_iter_init(&iter, bm, itype, data) && iter.count > 0) {
BMElem *ele;
BMElem **array = iter.count > stack_array_size ?
MEM_new_array_uninitialized<BMElem *>(iter.count, __func__) :
reinterpret_cast<BMElem **>(stack_array);
int i = 0;
*r_len = iter.count; /* set before iterating */
while ((ele = static_cast<BMElem *>(BM_iter_step(&iter)))) {
array[i++] = ele;
}
return array;
}
*r_len = 0;
return nullptr;
}
void *BMO_iter_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const char restrictmask,
int *r_len,
/* optional args to avoid an alloc (normally stack array) */
void **stack_array,
int stack_array_size)
{
BMOIter iter;
BMElem *ele;
const int slot_len = BMO_slot_buffer_len(slot_args, slot_name);
BLI_assert(stack_array_size == 0 || (stack_array_size && stack_array));
if ((ele = static_cast<BMElem *>(BMO_iter_new(&iter, slot_args, slot_name, restrictmask))) &&
slot_len > 0)
{
BMElem **array = slot_len > stack_array_size ?
MEM_new_array_uninitialized<BMElem *>(slot_len, __func__) :
reinterpret_cast<BMElem **>(stack_array);
int i = 0;
do {
array[i++] = ele;
} while ((ele = static_cast<BMElem *>(BMO_iter_step(&iter))));
BLI_assert(i <= slot_len);
if (i != slot_len) {
if (reinterpret_cast<void **>(array) != stack_array) {
array = static_cast<BMElem **>(MEM_realloc_uninitialized(array, sizeof(ele) * i));
}
}
*r_len = i;
return array;
}
*r_len = 0;
return nullptr;
}
int BM_iter_mesh_bitmap_from_filter(const char itype,
BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMElem *, void *user_data),
void *user_data)
{
BMIter iter;
BMElem *ele;
int i;
int bitmap_enabled = 0;
BM_ITER_MESH_INDEX (ele, &iter, bm, itype, i) {
if (test_fn(ele, user_data)) {
bitmap[i].set();
bitmap_enabled++;
}
else {
bitmap[i].reset();
}
}
return bitmap_enabled;
}
int BM_iter_mesh_bitmap_from_filter_tessface(BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMFace *, void *user_data),
void *user_data)
{
BMIter iter;
BMFace *f;
int i;
int j = 0;
int bitmap_enabled = 0;
BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) {
if (test_fn(f, user_data)) {
for (int tri = 2; tri < f->len; tri++) {
bitmap[j].set();
bitmap_enabled++;
j++;
}
}
else {
for (int tri = 2; tri < f->len; tri++) {
bitmap[j].reset();
j++;
}
}
}
return bitmap_enabled;
}
int BM_iter_elem_count_flag(const char itype, void *data, const char hflag, const bool value)
{
BMIter iter;
BMElem *ele;
int count = 0;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BM_elem_flag_test_bool(ele, hflag) == value) {
count++;
}
}
return count;
}
int BMO_iter_elem_count_flag(
BMesh *bm, const char itype, void *data, const short oflag, const bool value)
{
BMIter iter;
int count = 0;
/* loops have no header flags */
BLI_assert(bm_iter_itype_htype_map[itype] != BM_LOOP);
switch (bm_iter_itype_htype_map[itype]) {
case BM_VERT: {
BMVert *ele;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BMO_vert_flag_test_bool(bm, ele, oflag) == value) {
count++;
}
}
break;
}
case BM_EDGE: {
BMEdge *ele;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BMO_edge_flag_test_bool(bm, ele, oflag) == value) {
count++;
}
}
break;
}
case BM_FACE: {
BMFace *ele;
BM_ITER_ELEM (ele, &iter, data, itype) {
if (BMO_face_flag_test_bool(bm, ele, oflag) == value) {
count++;
}
}
break;
}
}
return count;
}
int BM_iter_mesh_count_flag(const char itype, BMesh *bm, const char hflag, const bool value)
{
BMIter iter;
BMElem *ele;
int count = 0;
BM_ITER_MESH (ele, &iter, bm, itype) {
if (BM_elem_flag_test_bool(ele, hflag) == value) {
count++;
}
}
return count;
}
/**
* Notes on iterator implementation:
*
* Iterators keep track of the next element in a sequence.
* When a step() callback is invoked the current value of 'next'
* is stored to be returned later and the next variable is incremented.
*
* When the end of a sequence is reached, next should always equal nullptr
*
* The 'bmiter__' prefix is used because these are used in
* bmesh_iterators_inine.c but should otherwise be seen as
* private.
*/
/*
* VERT OF MESH CALLBACKS
*/
/* see bug #36923 for why we need this,
* allow adding but not removing, this isn't _totally_ safe since
* you could add/remove within the same loop, but catches common cases
*/
#ifndef NDEBUG
# define USE_IMMUTABLE_ASSERT
#endif
void bmiter__elem_of_mesh_begin(BMIter__elem_of_mesh *iter)
{
#ifdef USE_IMMUTABLE_ASSERT
(reinterpret_cast<BMIter *>(iter))->count = BLI_mempool_len(iter->pooliter.pool);
#endif
BLI_mempool_iternew(iter->pooliter.pool, &iter->pooliter);
}
void *bmiter__elem_of_mesh_step(BMIter__elem_of_mesh *iter)
{
#ifdef USE_IMMUTABLE_ASSERT
BLI_assert(((BMIter *)iter)->count <= BLI_mempool_len(iter->pooliter.pool));
#endif
return BLI_mempool_iterstep(&iter->pooliter);
}
#ifdef USE_IMMUTABLE_ASSERT
# undef USE_IMMUTABLE_ASSERT
#endif
/*
* EDGE OF VERT CALLBACKS
*/
void bmiter__edge_of_vert_begin(BMIter__edge_of_vert *iter)
{
if (iter->vdata->e) {
iter->e_first = iter->vdata->e;
iter->e_next = iter->vdata->e;
}
else {
iter->e_first = nullptr;
iter->e_next = nullptr;
}
}
void *bmiter__edge_of_vert_step(BMIter__edge_of_vert *iter)
{
BMEdge *e_curr = iter->e_next;
if (iter->e_next) {
iter->e_next = bmesh_disk_edge_next(iter->e_next, iter->vdata);
if (iter->e_next == iter->e_first) {
iter->e_next = nullptr;
}
}
return e_curr;
}
/*
* FACE OF VERT CALLBACKS
*/
void bmiter__face_of_vert_begin(BMIter__face_of_vert *iter)
{
(reinterpret_cast<BMIter *>(iter))->count = bmesh_disk_facevert_count(iter->vdata);
if ((reinterpret_cast<BMIter *>(iter))->count) {
iter->l_first = bmesh_disk_faceloop_find_first(iter->vdata->e, iter->vdata);
iter->e_first = iter->l_first->e;
iter->e_next = iter->e_first;
iter->l_next = iter->l_first;
}
else {
iter->l_first = iter->l_next = nullptr;
iter->e_first = iter->e_next = nullptr;
}
}
void *bmiter__face_of_vert_step(BMIter__face_of_vert *iter)
{
BMLoop *l_curr = iter->l_next;
if ((reinterpret_cast<BMIter *>(iter))->count && iter->l_next) {
(reinterpret_cast<BMIter *>(iter))->count--;
iter->l_next = bmesh_radial_faceloop_find_next(iter->l_next, iter->vdata);
if (iter->l_next == iter->l_first) {
iter->e_next = bmesh_disk_faceedge_find_next(iter->e_next, iter->vdata);
iter->l_first = bmesh_radial_faceloop_find_first(iter->e_next->l, iter->vdata);
iter->l_next = iter->l_first;
}
}
if (!(reinterpret_cast<BMIter *>(iter))->count) {
iter->l_next = nullptr;
}
return l_curr ? l_curr->f : nullptr;
}
/*
* LOOP OF VERT CALLBACKS
*/
void bmiter__loop_of_vert_begin(BMIter__loop_of_vert *iter)
{
(reinterpret_cast<BMIter *>(iter))->count = bmesh_disk_facevert_count(iter->vdata);
if ((reinterpret_cast<BMIter *>(iter))->count) {
iter->l_first = bmesh_disk_faceloop_find_first(iter->vdata->e, iter->vdata);
iter->e_first = iter->l_first->e;
iter->e_next = iter->e_first;
iter->l_next = iter->l_first;
}
else {
iter->l_first = iter->l_next = nullptr;
iter->e_first = iter->e_next = nullptr;
}
}
void *bmiter__loop_of_vert_step(BMIter__loop_of_vert *iter)
{
BMLoop *l_curr = iter->l_next;
if ((reinterpret_cast<BMIter *>(iter))->count) {
(reinterpret_cast<BMIter *>(iter))->count--;
iter->l_next = bmesh_radial_faceloop_find_next(iter->l_next, iter->vdata);
if (iter->l_next == iter->l_first) {
iter->e_next = bmesh_disk_faceedge_find_next(iter->e_next, iter->vdata);
iter->l_first = bmesh_radial_faceloop_find_first(iter->e_next->l, iter->vdata);
iter->l_next = iter->l_first;
}
}
if (!(reinterpret_cast<BMIter *>(iter))->count) {
iter->l_next = nullptr;
}
/* nullptr on finish */
return l_curr;
}
/*
* LOOP OF EDGE CALLBACKS
*/
void bmiter__loop_of_edge_begin(BMIter__loop_of_edge *iter)
{
iter->l_first = iter->l_next = iter->edata->l;
}
void *bmiter__loop_of_edge_step(BMIter__loop_of_edge *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
/* nullptr on finish */
return l_curr;
}
/*
* LOOP OF LOOP CALLBACKS
*/
void bmiter__loop_of_loop_begin(BMIter__loop_of_loop *iter)
{
iter->l_first = iter->ldata;
iter->l_next = iter->l_first->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
void *bmiter__loop_of_loop_step(BMIter__loop_of_loop *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
/* nullptr on finish */
return l_curr;
}
/*
* FACE OF EDGE CALLBACKS
*/
void bmiter__face_of_edge_begin(BMIter__face_of_edge *iter)
{
iter->l_first = iter->l_next = iter->edata->l;
}
void *bmiter__face_of_edge_step(BMIter__face_of_edge *iter)
{
BMLoop *current = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->radial_next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return current ? current->f : nullptr;
}
/*
* VERTS OF EDGE CALLBACKS
*/
void bmiter__vert_of_edge_begin(BMIter__vert_of_edge *iter)
{
(reinterpret_cast<BMIter *>(iter))->count = 0;
}
void *bmiter__vert_of_edge_step(BMIter__vert_of_edge *iter)
{
switch ((reinterpret_cast<BMIter *>(iter))->count++) {
case 0:
return iter->edata->v1;
case 1:
return iter->edata->v2;
default:
return nullptr;
}
}
/*
* VERT OF FACE CALLBACKS
*/
void bmiter__vert_of_face_begin(BMIter__vert_of_face *iter)
{
iter->l_first = iter->l_next = BM_FACE_FIRST_LOOP(iter->pdata);
}
void *bmiter__vert_of_face_step(BMIter__vert_of_face *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return l_curr ? l_curr->v : nullptr;
}
/*
* EDGE OF FACE CALLBACKS
*/
void bmiter__edge_of_face_begin(BMIter__edge_of_face *iter)
{
iter->l_first = iter->l_next = BM_FACE_FIRST_LOOP(iter->pdata);
}
void *bmiter__edge_of_face_step(BMIter__edge_of_face *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return l_curr ? l_curr->e : nullptr;
}
/*
* LOOP OF FACE CALLBACKS
*/
void bmiter__loop_of_face_begin(BMIter__loop_of_face *iter)
{
iter->l_first = iter->l_next = BM_FACE_FIRST_LOOP(iter->pdata);
}
void *bmiter__loop_of_face_step(BMIter__loop_of_face *iter)
{
BMLoop *l_curr = iter->l_next;
if (iter->l_next) {
iter->l_next = iter->l_next->next;
if (iter->l_next == iter->l_first) {
iter->l_next = nullptr;
}
}
return l_curr;
}
} // namespace blender

View File

@@ -0,0 +1,295 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
/**
* \brief BMesh Iterators
*
* The functions and structures in this file
* provide a unified method for iterating over
* the elements of a mesh and answering simple
* adjacency queries. Tool authors should use
* the iterators provided in this file instead
* of inspecting the structure directly.
*/
#include "BLI_bit_span.hh"
#include "BLI_compiler_attrs.h"
#include "BLI_mempool.h"
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
/* these iterator over all elements of a specific
* type in the mesh.
*
* be sure to keep 'bm_iter_itype_htype_map' in sync with any changes
*/
namespace blender {
enum BMIterType {
BM_VERTS_OF_MESH = 1,
BM_EDGES_OF_MESH = 2,
BM_FACES_OF_MESH = 3,
/* these are topological iterators. */
BM_EDGES_OF_VERT = 4,
BM_FACES_OF_VERT = 5,
BM_LOOPS_OF_VERT = 6,
BM_VERTS_OF_EDGE = 7, /* just v1, v2: added so py can use generalized sequencer wrapper */
BM_FACES_OF_EDGE = 8,
BM_VERTS_OF_FACE = 9,
BM_EDGES_OF_FACE = 10,
BM_LOOPS_OF_FACE = 11,
/* returns elements from all boundaries, and returns
* the first element at the end to flag that we're entering
* a different face hole boundary. */
// BM_ALL_LOOPS_OF_FACE = 12,
/* iterate through loops around this loop, which are fetched
* from the other faces in the radial cycle surrounding the
* input loop's edge. */
BM_LOOPS_OF_LOOP = 12,
BM_LOOPS_OF_EDGE = 13,
};
#define BM_ITYPE_MAX 14
/* the iterator htype for each iterator */
extern const char bm_iter_itype_htype_map[BM_ITYPE_MAX];
/* -------------------------------------------------------------------- */
/** \name Defines for passing to #BM_iter_new.
*
* "OF" can be substituted for "around" so #BM_VERTS_OF_FACE means "vertices* around a face."
* \{ */
#define BM_ITER_MESH(ele, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL); ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
#define BM_ITER_MESH_INDEX(ele, iter, bm, itype, indexvar) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL), indexvar = 0; ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
/* a version of BM_ITER_MESH which keeps the next item in storage
* so we can delete the current item, see bug #36923. */
#ifndef NDEBUG
# define BM_ITER_MESH_MUTABLE(ele, ele_next, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL); \
ele ? ((void)((iter)->count = BM_iter_mesh_count(itype, bm)), \
(void)(BM_CHECK_TYPE_ELEM_ASSIGN(ele_next) = BM_iter_step(iter)), \
1) : \
0; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = ele_next)
#else
# define BM_ITER_MESH_MUTABLE(ele, ele_next, iter, bm, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, bm, itype, NULL); \
ele ? ((BM_CHECK_TYPE_ELEM_ASSIGN(ele_next) = BM_iter_step(iter)), 1) : 0; \
ele = ele_next)
#endif
#define BM_ITER_ELEM(ele, iter, data, itype) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, NULL, itype, data); ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter))
#define BM_ITER_ELEM_INDEX(ele, iter, data, itype, indexvar) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_new(iter, NULL, itype, data), indexvar = 0; ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BM_iter_step(iter), (indexvar)++)
/** \} */
/* iterator type structs */
struct BMIter__elem_of_mesh {
BLI_mempool_iter pooliter;
};
struct BMIter__edge_of_vert {
BMVert *vdata;
BMEdge *e_first, *e_next;
};
struct BMIter__face_of_vert {
BMVert *vdata;
BMLoop *l_first, *l_next;
BMEdge *e_first, *e_next;
};
struct BMIter__loop_of_vert {
BMVert *vdata;
BMLoop *l_first, *l_next;
BMEdge *e_first, *e_next;
};
struct BMIter__loop_of_edge {
BMEdge *edata;
BMLoop *l_first, *l_next;
};
struct BMIter__loop_of_loop {
BMLoop *ldata;
BMLoop *l_first, *l_next;
};
struct BMIter__face_of_edge {
BMEdge *edata;
BMLoop *l_first, *l_next;
};
struct BMIter__vert_of_edge {
BMEdge *edata;
};
struct BMIter__vert_of_face {
BMFace *pdata;
BMLoop *l_first, *l_next;
};
struct BMIter__edge_of_face {
BMFace *pdata;
BMLoop *l_first, *l_next;
};
struct BMIter__loop_of_face {
BMFace *pdata;
BMLoop *l_first, *l_next;
};
using BMIter__begin_cb = void (*)(void *);
using BMIter__step_cb = void *(*)(void *);
/* Iterator Structure */
/* NOTE: some of these vars are not used,
* so they have been commented to save stack space since this struct is used all over */
struct BMIter {
/* keep union first */
union {
BMIter__elem_of_mesh elem_of_mesh;
BMIter__edge_of_vert edge_of_vert;
BMIter__face_of_vert face_of_vert;
BMIter__loop_of_vert loop_of_vert;
BMIter__loop_of_edge loop_of_edge;
BMIter__loop_of_loop loop_of_loop;
BMIter__face_of_edge face_of_edge;
BMIter__vert_of_edge vert_of_edge;
BMIter__vert_of_face vert_of_face;
BMIter__edge_of_face edge_of_face;
BMIter__loop_of_face loop_of_face;
} data;
BMIter__begin_cb begin;
BMIter__step_cb step;
int count; /* NOTE: only some iterators set this, don't rely on it. */
char itype;
};
/**
* \note Use #BM_vert_at_index / #BM_edge_at_index / #BM_face_at_index for mesh arrays.
*/
void *BM_iter_at_index(BMesh *bm, char itype, void *data, int index) ATTR_WARN_UNUSED_RESULT;
/**
* \brief Iterator as Array
*
* Sometimes its convenient to get the iterator as an array
* to avoid multiple calls to #BM_iter_at_index.
*/
int BM_iter_as_array(BMesh *bm, char itype, void *data, void **array, int len);
/**
* \brief Iterator as Array
*
* Allocates a new array, has the advantage that you don't need to know the size ahead of time.
*
* Takes advantage of less common iterator usage to avoid counting twice,
* which you might end up doing when #BM_iter_as_array is used.
*
* Caller needs to free the array.
*/
void *BM_iter_as_arrayN(BMesh *bm,
char itype,
void *data,
int *r_len,
void **stack_array,
int stack_array_size) ATTR_WARN_UNUSED_RESULT;
/**
* \brief Operator Iterator as Array
*
* Sometimes its convenient to get the iterator as an array.
*/
int BMO_iter_as_array(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char restrictmask,
void **array,
int len);
void *BMO_iter_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char restrictmask,
int *r_len,
/* optional args to avoid an alloc (normally stack array) */
void **stack_array,
int stack_array_size);
int BM_iter_mesh_bitmap_from_filter(char itype,
BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMElem *, void *user_data),
void *user_data);
/**
* Needed when we want to check faces, but return a loop aligned array.
*/
int BM_iter_mesh_bitmap_from_filter_tessface(BMesh *bm,
MutableBitSpan bitmap,
bool (*test_fn)(BMFace *, void *user_data),
void *user_data);
/**
* \brief Elem Iter Flag Count
*
* Counts how many flagged / unflagged items are found in this element.
*/
int BM_iter_elem_count_flag(char itype, void *data, char hflag, bool value);
/**
* \brief Elem Iter Tool Flag Count
*
* Counts how many flagged / unflagged items are found in this element.
*/
int BMO_iter_elem_count_flag(BMesh *bm, char itype, void *data, short oflag, bool value);
/**
* Utility function.
*/
int BM_iter_mesh_count(char itype, BMesh *bm);
/**
* \brief Mesh Iter Flag Count
*
* Counts how many flagged / unflagged items are found in this mesh.
*/
int BM_iter_mesh_count_flag(char itype, BMesh *bm, char hflag, bool value);
/* private for bmesh_iterators_inline.c */
#define BMITER_CB_DEF(name) \
struct BMIter__##name; \
void bmiter__##name##_begin(struct BMIter__##name *iter); \
void *bmiter__##name##_step(struct BMIter__##name *iter)
BMITER_CB_DEF(elem_of_mesh);
BMITER_CB_DEF(edge_of_vert);
BMITER_CB_DEF(face_of_vert);
BMITER_CB_DEF(loop_of_vert);
BMITER_CB_DEF(loop_of_edge);
BMITER_CB_DEF(loop_of_loop);
BMITER_CB_DEF(face_of_edge);
BMITER_CB_DEF(vert_of_edge);
BMITER_CB_DEF(vert_of_face);
BMITER_CB_DEF(edge_of_face);
BMITER_CB_DEF(loop_of_face);
#undef BMITER_CB_DEF
} // namespace blender
#include "intern/bmesh_iterators_inline.hh" /* IWYU pragma: export */
#define BM_ITER_CHECK_TYPE_DATA(data) \
CHECK_TYPE_ANY(data, void *, BMFace *, BMEdge *, BMVert *, BMLoop *, BMElem *)
#define BM_iter_new(iter, bm, itype, data) \
(BM_ITER_CHECK_TYPE_DATA(data), BM_iter_new(iter, bm, itype, data))
#define BM_iter_init(iter, bm, itype, data) \
(BM_ITER_CHECK_TYPE_DATA(data), BM_iter_init(iter, bm, itype, data))

View File

@@ -0,0 +1,205 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh inline iterator functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
namespace blender {
/* inline here optimizes out the switch statement when called with
* constant values (which is very common), nicer for loop-in-loop situations */
/**
* \brief Iterator Step
*
* Calls an iterators step function to return the next element.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE void *BM_iter_step(BMIter *iter)
{
return iter->step(iter);
}
/**
* \brief Iterator Init
*
* Takes a bmesh iterator structure and fills
* it with the appropriate function pointers based
* upon its type.
*/
ATTR_NONNULL(1) BLI_INLINE bool BM_iter_init(BMIter *iter, BMesh *bm, const char itype, void *data)
{
// int argtype;
iter->itype = itype;
/* inlining optimizes out this switch when called with the defined type */
switch (BMIterType(itype)) {
case BM_VERTS_OF_MESH:
BLI_assert(bm != nullptr);
BLI_assert(data == nullptr);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__elem_of_mesh_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__elem_of_mesh_step);
iter->data.elem_of_mesh.pooliter.pool = bm->vpool;
break;
case BM_EDGES_OF_MESH:
BLI_assert(bm != nullptr);
BLI_assert(data == nullptr);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__elem_of_mesh_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__elem_of_mesh_step);
iter->data.elem_of_mesh.pooliter.pool = bm->epool;
break;
case BM_FACES_OF_MESH:
BLI_assert(bm != nullptr);
BLI_assert(data == nullptr);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__elem_of_mesh_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__elem_of_mesh_step);
iter->data.elem_of_mesh.pooliter.pool = bm->fpool;
break;
case BM_EDGES_OF_VERT:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__edge_of_vert_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__edge_of_vert_step);
iter->data.edge_of_vert.vdata = static_cast<BMVert *>(data);
break;
case BM_FACES_OF_VERT:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__face_of_vert_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__face_of_vert_step);
iter->data.face_of_vert.vdata = static_cast<BMVert *>(data);
break;
case BM_LOOPS_OF_VERT:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_VERT);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_vert_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_vert_step);
iter->data.loop_of_vert.vdata = static_cast<BMVert *>(data);
break;
case BM_VERTS_OF_EDGE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_EDGE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__vert_of_edge_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__vert_of_edge_step);
iter->data.vert_of_edge.edata = static_cast<BMEdge *>(data);
break;
case BM_FACES_OF_EDGE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_EDGE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__face_of_edge_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__face_of_edge_step);
iter->data.face_of_edge.edata = static_cast<BMEdge *>(data);
break;
case BM_VERTS_OF_FACE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_FACE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__vert_of_face_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__vert_of_face_step);
iter->data.vert_of_face.pdata = static_cast<BMFace *>(data);
break;
case BM_EDGES_OF_FACE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_FACE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__edge_of_face_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__edge_of_face_step);
iter->data.edge_of_face.pdata = static_cast<BMFace *>(data);
break;
case BM_LOOPS_OF_FACE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_FACE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_face_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_face_step);
iter->data.loop_of_face.pdata = static_cast<BMFace *>(data);
break;
case BM_LOOPS_OF_LOOP:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_LOOP);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_loop_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_loop_step);
iter->data.loop_of_loop.ldata = static_cast<BMLoop *>(data);
break;
case BM_LOOPS_OF_EDGE:
BLI_assert(data != nullptr);
BLI_assert(((BMElem *)data)->head.htype == BM_EDGE);
iter->begin = reinterpret_cast<BMIter__begin_cb>(bmiter__loop_of_edge_begin);
iter->step = reinterpret_cast<BMIter__step_cb>(bmiter__loop_of_edge_step);
iter->data.loop_of_edge.edata = static_cast<BMEdge *>(data);
break;
default:
/* should never happen */
BLI_assert(0);
return false;
break;
}
iter->begin(iter);
return true;
}
/**
* \brief Iterator New
*
* Takes a bmesh iterator structure and fills
* it with the appropriate function pointers based
* upon its type and then calls BMeshIter_step()
* to return the first element of the iterator.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void *BM_iter_new(BMIter *iter, BMesh *bm, const char itype, void *data)
{
if (LIKELY(BM_iter_init(iter, bm, itype, data))) {
return BM_iter_step(iter);
}
else {
return nullptr;
}
}
/**
* \brief Parallel (threaded) iterator,
* only available for most basic iteration-types (verts/edges/faces of mesh).
*
* Uses #BLI_task_parallel_mempool to iterate over all items of underlying matching mempool.
*
* \note You have to include BLI_task.h before BMesh includes to be able to use this function!
*/
#ifdef __BLI_TASK_H__
ATTR_NONNULL(1)
BLI_INLINE void BM_iter_parallel(BMesh *bm,
const char itype,
TaskParallelMempoolFunc func,
void *userdata,
const TaskParallelSettings *settings)
{
/* inlining optimizes out this switch when called with the defined type */
switch (BMIterType(itype)) {
case BM_VERTS_OF_MESH:
BLI_task_parallel_mempool(bm->vpool, userdata, func, settings);
break;
case BM_EDGES_OF_MESH:
BLI_task_parallel_mempool(bm->epool, userdata, func, settings);
break;
case BM_FACES_OF_MESH:
BLI_task_parallel_mempool(bm->fpool, userdata, func, settings);
break;
default:
/* should never happen */
BLI_assert(0);
break;
}
}
#endif /* __BLI_TASK_H__ */
} // namespace blender

View File

@@ -0,0 +1,890 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* The BMLog is an interface for storing undo/redo steps as a BMesh is
* modified. It only stores changes to the BMesh, not full copies.
*
* Currently it supports the following types of changes:
*
* - Adding and removing vertices
* - Adding and removing faces
* - Moving vertices
* - Setting vertex paint-mask values
* - Setting vertex hflags
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_vector.h"
#include "BLI_pool.hh"
#include "BLI_utildefines.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "bmesh_log.hh"
#include "range_tree.h"
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
struct BMLogFace;
struct BMLogVert;
struct BMLogEntry {
BMLogEntry *next, *prev;
/* The following members map from an element ID to one of the log types above. */
/** Elements that were in the previous entry, but have been deleted. */
Map<uint, BMLogVert *, 0> deleted_verts;
Map<uint, BMLogFace *, 0> deleted_faces;
/** Elements that were not in the previous entry, but are in the result of this entry. */
Map<uint, BMLogVert *, 0> added_verts;
Map<uint, BMLogFace *, 0> added_faces;
/** Vertices whose coordinates, mask value, or hflag have changed. */
Map<uint, BMLogVert *, 0> modified_verts;
Map<uint, BMLogFace *, 0> modified_faces;
Pool<BMLogVert> vert_pool;
Pool<BMLogFace> face_pool;
Vector<BMLogVert *, 0> allocated_verts;
Vector<BMLogFace *, 0> allocated_faces;
/**
* This is only needed for dropping BMLogEntries while still in
* dynamic-topology mode, as that should release vert/face IDs
* back to the BMLog but no BMLog pointer is available at that time.
*
* This field is not guaranteed to be valid, any use of it should
* check for nullptr.
*/
BMLog *log;
};
struct BMLog {
/** Tree of free IDs */
RangeTreeUInt *unused_ids;
/**
* Mapping from unique IDs to vertices and faces
*
* Each vertex and face in the log gets a unique `uint`
* assigned. That ID is taken from the set managed by the
* unused_ids range tree.
*
* The ID is needed because element pointers will change as they
* are created and deleted.
*/
Map<uint, BMElem *, 0> id_to_elem;
Map<BMElem *, uint, 0> elem_to_id;
/** All #BMLogEntrys, ordered from earliest to most recent. */
ListBaseT<BMLogEntry> entries;
/**
* The current log entry from entries list
*
* If null, then the original mesh from before any of the log
* entries is current (i.e. there is nothing left to undo.)
*
* If equal to the last entry in the entries list, then all log
* entries have been applied (i.e. there is nothing left to redo.)
*/
BMLogEntry *current_entry;
};
struct BMLogVert {
float3 position;
float3 normal;
char hflag;
float mask;
};
struct BMLogFace {
std::array<uint, 3> v_ids;
char hflag;
};
/* -------------------------------------------------------------------- */
/** \name Get/Set Element IDs
* \{ */
/* Get the vertex's unique ID from the log */
static uint bm_log_vert_id_get(BMLog *log, BMVert *v)
{
return log->elem_to_id.lookup(reinterpret_cast<BMElem *>(v));
}
/* Set the vertex's unique ID in the log */
static void bm_log_vert_id_set(BMLog *log, BMVert *v, const uint id)
{
log->id_to_elem.add_overwrite(id, reinterpret_cast<BMElem *>(v));
log->elem_to_id.add_overwrite(reinterpret_cast<BMElem *>(v), id);
}
/* Get a vertex from its unique ID */
static BMVert *bm_log_vert_from_id(BMLog *log, const uint id)
{
return reinterpret_cast<BMVert *>(log->id_to_elem.lookup(id));
}
/* Get the face's unique ID from the log */
static uint bm_log_face_id_get(BMLog *log, BMFace *f)
{
return log->elem_to_id.lookup(reinterpret_cast<BMElem *>(f));
}
/* Set the face's unique ID in the log */
static void bm_log_face_id_set(BMLog *log, BMFace *f, const uint id)
{
log->id_to_elem.add_overwrite(id, reinterpret_cast<BMElem *>(f));
log->elem_to_id.add_overwrite(reinterpret_cast<BMElem *>(f), id);
}
/* Get a face from its unique ID */
static BMFace *bm_log_face_from_id(BMLog *log, const uint id)
{
return reinterpret_cast<BMFace *>(log->id_to_elem.lookup(id));
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMLogVert / BMLogFace
* \{ */
/* Get a vertex's paint-mask value
*
* Returns zero if no paint-mask layer is present */
static float vert_mask_get(BMVert *v, const int cd_vert_mask_offset)
{
if (cd_vert_mask_offset != -1) {
return BM_ELEM_CD_GET_FLOAT(v, cd_vert_mask_offset);
}
return 0.0f;
}
/* Set a vertex's paint-mask value
*
* Has no effect is no paint-mask layer is present */
static void vert_mask_set(BMVert *v, const float new_mask, const int cd_vert_mask_offset)
{
if (cd_vert_mask_offset != -1) {
BM_ELEM_CD_SET_FLOAT(v, cd_vert_mask_offset, new_mask);
}
}
/* Update a BMLogVert with data from a BMVert */
static void bm_log_vert_bmvert_copy(BMLogVert *lv, BMVert *v, const int cd_vert_mask_offset)
{
copy_v3_v3(lv->position, v->co);
copy_v3_v3(lv->normal, v->no);
lv->mask = vert_mask_get(v, cd_vert_mask_offset);
lv->hflag = v->head.hflag;
}
/* Allocate and initialize a BMLogVert */
static BMLogVert *bm_log_vert_alloc(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
BMLogEntry *entry = log->current_entry;
BMLogVert *lv = &entry->vert_pool.construct();
entry->allocated_verts.append(lv);
bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset);
return lv;
}
/* Allocate and initialize a BMLogFace */
static BMLogFace *bm_log_face_alloc(BMLog *log, BMFace *f)
{
BMLogEntry *entry = log->current_entry;
BMLogFace *lf = &entry->face_pool.construct();
entry->allocated_faces.append(lf);
BMVert *v[3];
BLI_assert(f->len == 3);
// BM_iter_as_array(nullptr, BM_VERTS_OF_FACE, f, (void **)v, 3);
BM_face_as_array_vert_tri(f, v);
lf->v_ids[0] = bm_log_vert_id_get(log, v[0]);
lf->v_ids[1] = bm_log_vert_id_get(log, v[1]);
lf->v_ids[2] = bm_log_vert_id_get(log, v[2]);
lf->hflag = f->head.hflag;
return lf;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Helpers for Undo/Redo
* \{ */
static void bm_log_verts_unmake(BMesh *bm, BMLog *log, const Map<uint, BMLogVert *, 0> &verts)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
for (const auto item : verts.items()) {
BMVert *v = bm_log_vert_from_id(log, item.key);
/* Ensure the log has the final values of the vertex before
* deleting it */
bm_log_vert_bmvert_copy(item.value, v, cd_vert_mask_offset);
BM_vert_kill(bm, v);
}
}
static void bm_log_faces_unmake(BMesh *bm, BMLog *log, const Map<uint, BMLogFace *, 0> &faces)
{
for (const uint id : faces.keys()) {
BMFace *f = bm_log_face_from_id(log, id);
std::array<BMEdge *, 3> e_tri;
BMLoop *l_iter = BM_FACE_FIRST_LOOP(f);
for (uint i = 0; i < e_tri.size(); i++, l_iter = l_iter->next) {
e_tri[i] = l_iter->e;
}
/* Remove any unused edges */
BM_face_kill(bm, f);
for (uint i = 0; i < e_tri.size(); i++) {
if (BM_edge_is_wire(e_tri[i])) {
BM_edge_kill(bm, e_tri[i]);
}
}
}
}
static void bm_log_verts_restore(BMesh *bm, BMLog *log, const Map<uint, BMLogVert *, 0> &verts)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
for (const auto item : verts.items()) {
BMLogVert *lv = item.value;
BMVert *v = BM_vert_create(bm, lv->position, nullptr, BM_CREATE_NOP);
vert_mask_set(v, lv->mask, cd_vert_mask_offset);
v->head.hflag = lv->hflag;
copy_v3_v3(v->no, lv->normal);
bm_log_vert_id_set(log, v, item.key);
}
}
static void bm_log_faces_restore(BMesh *bm, BMLog *log, const Map<uint, BMLogFace *, 0> &faces)
{
const int cd_face_sets = CustomData_get_offset_named(
&bm->pdata, CD_PROP_INT32, ".sculpt_face_set");
for (const auto item : faces.items()) {
BMLogFace *lf = item.value;
BMVert *v[3] = {
bm_log_vert_from_id(log, lf->v_ids[0]),
bm_log_vert_from_id(log, lf->v_ids[1]),
bm_log_vert_from_id(log, lf->v_ids[2]),
};
BMFace *f = BM_face_create_verts(bm, v, 3, nullptr, BM_CREATE_NOP, true);
f->head.hflag = lf->hflag;
bm_log_face_id_set(log, f, item.key);
/* Ensure face sets have valid values. Fixes #80174. */
if (cd_face_sets != -1) {
BM_ELEM_CD_SET_INT(f, cd_face_sets, 1);
}
}
}
static void bm_log_vert_values_swap(BMesh *bm, BMLog *log, const Map<uint, BMLogVert *, 0> &verts)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
for (const auto item : verts.items()) {
BMLogVert *lv = item.value;
BMVert *v = bm_log_vert_from_id(log, item.key);
swap_v3_v3(v->co, lv->position);
swap_v3_v3(v->no, lv->normal);
std::swap(v->head.hflag, lv->hflag);
float mask = lv->mask;
lv->mask = vert_mask_get(v, cd_vert_mask_offset);
vert_mask_set(v, mask, cd_vert_mask_offset);
}
}
static void bm_log_face_values_swap(BMLog *log, const Map<uint, BMLogFace *, 0> &faces)
{
for (const auto item : faces.items()) {
BMLogFace *lf = item.value;
BMFace *f = bm_log_face_from_id(log, item.key);
std::swap(f->head.hflag, lf->hflag);
}
}
/* Assign unique IDs to all vertices and faces already in the BMesh */
static void bm_log_assign_ids(BMesh *bm, BMLog *log)
{
BMIter iter;
BMVert *v;
/* Generate vertex IDs */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
uint id = range_tree_uint_take_any(log->unused_ids);
bm_log_vert_id_set(log, v, id);
}
BMFace *f;
/* Generate face IDs */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
uint id = range_tree_uint_take_any(log->unused_ids);
bm_log_face_id_set(log, f, id);
}
}
/* Allocate an empty log entry */
static BMLogEntry *bm_log_entry_create()
{
BMLogEntry *entry = MEM_new<BMLogEntry>(__func__);
return entry;
}
/* Free the data in a log entry
*
* NOTE: does not free the log entry itself. */
static void bm_log_entry_free(BMLogEntry *entry)
{
BLI_assert(entry->vert_pool.size() == entry->allocated_verts.size());
BLI_assert(entry->face_pool.size() == entry->allocated_faces.size());
for (BMLogVert *log_vert : entry->allocated_verts) {
entry->vert_pool.destruct(*log_vert);
}
for (BMLogFace *log_face : entry->allocated_faces) {
entry->face_pool.destruct(*log_face);
}
BLI_assert(entry->vert_pool.is_empty());
BLI_assert(entry->face_pool.is_empty());
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public API
* \{ */
BMLog *BM_log_create(BMesh *bm)
{
BMLog *log = MEM_new<BMLog>(__func__);
const uint reserve_num = uint(bm->totvert + bm->totface);
log->unused_ids = range_tree_uint_alloc(0, uint(-1));
log->id_to_elem.reserve(reserve_num);
log->elem_to_id.reserve(reserve_num);
/* Assign IDs to all existing vertices and faces */
bm_log_assign_ids(bm, log);
return log;
}
void BM_log_cleanup_entry(BMLogEntry *entry)
{
BMLog *log = entry->log;
if (log) {
/* Take all used IDs */
for (const uint id : entry->deleted_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->deleted_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
/* delete entries to avoid releasing ids in node cleanup */
entry->deleted_verts.clear();
entry->deleted_faces.clear();
entry->added_verts.clear();
entry->added_faces.clear();
entry->modified_verts.clear();
/* Is this last one needed? */
entry->modified_faces.clear();
}
}
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry)
{
BMLog *log = BM_log_create(bm);
if (entry->prev) {
log->current_entry = entry;
}
else {
log->current_entry = nullptr;
}
/* Let BMLog manage the entry list again */
log->entries.first = log->entries.last = entry;
{
while (entry->prev) {
entry = entry->prev;
log->entries.first = entry;
}
entry = static_cast<BMLogEntry *>(log->entries.last);
while (entry->next) {
entry = entry->next;
log->entries.last = entry;
}
}
for (entry = static_cast<BMLogEntry *>(log->entries.first); entry; entry = entry->next) {
entry->log = log;
/* Take all used IDs */
for (const uint id : entry->deleted_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->deleted_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->added_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_verts.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
for (const uint id : entry->modified_faces.keys()) {
range_tree_uint_retake(log->unused_ids, id);
}
}
return log;
}
void BM_log_free(BMLog *log)
{
if (log->unused_ids) {
range_tree_uint_free(log->unused_ids);
}
/* Clear the BMLog references within each entry, but do not free
* the entries themselves */
for (BMLogEntry &entry : log->entries) {
entry.log = nullptr;
}
MEM_delete(log);
}
BMLogEntry *BM_log_entry_add(BMLog *log)
{
/* WARNING: Deleting any entries after the current one is now handled by the
* UndoSystem: BKE_UNDOSYS_TYPE_SCULPT freeing here causes unnecessary complications. */
/* Create and append the new entry */
BMLogEntry *entry = bm_log_entry_create();
BLI_addtail(&log->entries, entry);
entry->log = log;
log->current_entry = entry;
return entry;
}
void BM_log_entry_drop(BMLogEntry *entry)
{
BMLog *log = entry->log;
if (!log) {
/* Unlink */
BLI_assert(!(entry->prev && entry->next));
if (entry->prev) {
entry->prev->next = nullptr;
}
else if (entry->next) {
entry->next->prev = nullptr;
}
bm_log_entry_free(entry);
MEM_delete(entry);
return;
}
if (!entry->prev) {
/* Release IDs of elements that are deleted by this
* entry. Since the entry is at the beginning of the undo
* stack, and it's being deleted, those elements can never be
* restored. Their IDs can go back into the pool. */
/* This would never happen usually since first entry of log is
* usually dyntopo enable, which, when reverted will free the log
* completely. However, it is possible have a stroke instead of
* dyntopo enable as first entry if nodes have been cleaned up
* after sculpting on a different object than A, B.
*
* The steps are:
* A dyntopo enable - sculpt
* B dyntopo enable - sculpt - undo (A objects operators get cleaned up)
* A sculpt (now A's log has a sculpt operator as first entry)
*
* Causing a cleanup at this point will call the code below, however
* this will invalidate the state of the log since the deleted vertices
* have been reclaimed already on step 2 (see BM_log_cleanup_entry)
*
* Also, design wise, a first entry should not have any deleted vertices since it
* should not have anything to delete them -from-
*/
// bm_log_id_ghash_release(log, entry->deleted_faces);
// bm_log_id_ghash_release(log, entry->deleted_verts);
}
else if (!entry->next) {
/* Release IDs of elements that are added by this entry. Since
* the entry is at the end of the undo stack, and it's being
* deleted, those elements can never be restored. Their IDs
* can go back into the pool. */
for (const uint id : entry->added_faces.keys()) {
range_tree_uint_release(log->unused_ids, id);
}
for (const uint id : entry->added_verts.keys()) {
range_tree_uint_release(log->unused_ids, id);
}
}
else {
BLI_assert_msg(0, "Cannot drop BMLogEntry from middle");
}
if (log->current_entry == entry) {
log->current_entry = entry->prev;
}
bm_log_entry_free(entry);
BLI_remlink(&log->entries, entry);
MEM_delete(entry);
}
void BM_log_undo(BMesh *bm, BMLog *log)
{
BMLogEntry *entry = log->current_entry;
if (entry) {
log->current_entry = entry->prev;
/* Delete added faces and verts */
bm_log_faces_unmake(bm, log, entry->added_faces);
bm_log_verts_unmake(bm, log, entry->added_verts);
/* Restore deleted verts and faces */
bm_log_verts_restore(bm, log, entry->deleted_verts);
bm_log_faces_restore(bm, log, entry->deleted_faces);
/* Restore vertex coordinates, mask, and hflag */
bm_log_vert_values_swap(bm, log, entry->modified_verts);
bm_log_face_values_swap(log, entry->modified_faces);
}
}
void BM_log_redo(BMesh *bm, BMLog *log)
{
BMLogEntry *entry = log->current_entry;
if (!entry) {
/* Currently at the beginning of the undo stack, move to first entry */
entry = static_cast<BMLogEntry *>(log->entries.first);
}
else if (entry->next) {
/* Move to next undo entry */
entry = entry->next;
}
else {
/* Currently at the end of the undo stack, nothing left to redo */
return;
}
log->current_entry = entry;
if (entry) {
/* Re-delete previously deleted faces and verts */
bm_log_faces_unmake(bm, log, entry->deleted_faces);
bm_log_verts_unmake(bm, log, entry->deleted_verts);
/* Restore previously added verts and faces */
bm_log_verts_restore(bm, log, entry->added_verts);
bm_log_faces_restore(bm, log, entry->added_faces);
/* Restore vertex coordinates, mask, and hflag */
bm_log_vert_values_swap(bm, log, entry->modified_verts);
bm_log_face_values_swap(log, entry->modified_faces);
}
}
void BM_log_vert_before_modified(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
/* Find or create the BMLogVert entry */
if (entry->added_verts.contains(v_id)) {
bm_log_vert_bmvert_copy(entry->added_verts.lookup(v_id), v, cd_vert_mask_offset);
}
else {
entry->modified_verts.lookup_or_add_cb(
v_id, [&] { return bm_log_vert_alloc(log, v, cd_vert_mask_offset); });
}
}
void BM_log_vert_added(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
const uint v_id = range_tree_uint_take_any(log->unused_ids);
bm_log_vert_id_set(log, v, v_id);
BMLogVert *lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset);
log->current_entry->added_verts.add(v_id, lv);
}
void BM_log_face_modified(BMLog *log, BMFace *f)
{
const uint f_id = bm_log_face_id_get(log, f);
BMLogFace *lf = bm_log_face_alloc(log, f);
log->current_entry->modified_faces.add(f_id, lf);
}
void BM_log_face_added(BMLog *log, BMFace *f)
{
const uint f_id = range_tree_uint_take_any(log->unused_ids);
/* Only triangles are supported for now */
BLI_assert(f->len == 3);
bm_log_face_id_set(log, f, f_id);
BMLogFace *lf = bm_log_face_alloc(log, f);
log->current_entry->added_faces.add(f_id, lf);
}
void BM_log_vert_removed(BMLog *log, BMVert *v, const int cd_vert_mask_offset)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
BLI_assert(!entry->added_verts.contains(v_id) ||
(entry->added_verts.contains(v_id) && entry->added_verts.lookup(v_id) != nullptr));
if (entry->added_verts.remove(v_id)) {
range_tree_uint_release(log->unused_ids, v_id);
}
else {
BMLogVert *lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset);
entry->deleted_verts.add(v_id, lv);
/* If the vertex was modified before deletion, ensure that the
* original vertex values are stored */
if (std::optional<BMLogVert *> lv_mod = entry->modified_verts.lookup_try(v_id)) {
*lv = *lv_mod.value();
entry->modified_verts.remove(v_id);
}
}
}
void BM_log_face_removed(BMLog *log, BMFace *f)
{
BMLogEntry *entry = log->current_entry;
const uint f_id = bm_log_face_id_get(log, f);
BLI_assert(!entry->added_faces.contains(f_id) ||
(entry->added_faces.contains(f_id) && entry->added_faces.lookup(f_id) != nullptr));
if (entry->added_faces.remove(f_id)) {
range_tree_uint_release(log->unused_ids, f_id);
}
else {
BMLogFace *lf = bm_log_face_alloc(log, f);
entry->deleted_faces.add(f_id, lf);
}
}
void BM_log_all_added(BMesh *bm, BMLog *log)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
/* avoid unnecessary resizing on initialization */
if (log->current_entry->added_verts.is_empty()) {
log->current_entry->added_verts.reserve(bm->totvert);
}
if (log->current_entry->added_faces.is_empty()) {
log->current_entry->added_faces.reserve(bm->totface);
}
BMIter bm_iter;
BMVert *v;
/* Log all vertices as newly created */
BM_ITER_MESH (v, &bm_iter, bm, BM_VERTS_OF_MESH) {
BM_log_vert_added(log, v, cd_vert_mask_offset);
}
BMFace *f;
/* Log all faces as newly created */
BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) {
BM_log_face_added(log, f);
}
}
void BM_log_before_all_removed(BMesh *bm, BMLog *log)
{
const int cd_vert_mask_offset = CustomData_get_offset_named(
&bm->vdata, CD_PROP_FLOAT, ".sculpt_mask");
BMIter bm_iter;
BMFace *f;
/* Log deletion of all faces */
BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) {
BM_log_face_removed(log, f);
}
BMVert *v;
/* Log deletion of all vertices */
BM_ITER_MESH (v, &bm_iter, bm, BM_VERTS_OF_MESH) {
BM_log_vert_removed(log, v, cd_vert_mask_offset);
}
}
const float *BM_log_find_original_vert_co(BMLog *log, BMVert *v)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
if (std::optional<BMLogVert *> log_vert = entry->modified_verts.lookup_try(v_id)) {
return log_vert.value()->position;
}
return nullptr;
}
const float *BM_log_find_original_vert_mask(BMLog *log, BMVert *v)
{
BMLogEntry *entry = log->current_entry;
const uint v_id = bm_log_vert_id_get(log, v);
if (std::optional<BMLogVert *> log_vert = entry->modified_verts.lookup_try(v_id)) {
return &log_vert.value()->mask;
}
return nullptr;
}
void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no)
{
BMLogEntry *entry = log->current_entry;
BLI_assert(entry);
const uint v_id = bm_log_vert_id_get(log, v);
BLI_assert(entry->modified_verts.contains(v_id));
const BMLogVert *lv = entry->modified_verts.lookup(v_id);
*r_co = lv->position;
*r_no = lv->normal;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debugging and Testing
* \{ */
#ifndef NDEBUG
BMLogEntry *BM_log_current_entry(BMLog *log)
{
return log->current_entry;
}
RangeTreeUInt *BM_log_unused_ids(BMLog *log)
{
return log->unused_ids;
}
/* Print the list of entries, marking the current one
*
* Keep around for debugging */
void BM_log_print(const BMLog *log, const char *description)
{
const BMLogEntry *entry;
const char *current = " <-- current";
int i;
printf("%s:\n", description);
printf(" % 2d: [ initial ]%s\n", 0, (!log->current_entry) ? current : "");
for (entry = static_cast<const BMLogEntry *>(log->entries.first), i = 1; entry;
entry = entry->next, i++)
{
printf(" % 2d: [%p]%s\n", i, entry, (entry == log->current_entry) ? current : "");
}
}
void BM_log_print_entry(BMesh *bm, BMLogEntry *entry)
{
if (bm) {
printf("BM { totvert=%d totedge=%d totloop=%d faces_num=%d\n",
bm->totvert,
bm->totedge,
bm->totloop,
bm->totface);
if (!bm->totvert) {
printf("%s: Warning: empty bmesh\n", __func__);
}
}
else {
printf("BM { totvert=unknown totedge=unknown totloop=unknown faces_num=unknown\n");
}
printf("v | added: %d, removed: %d, modified: %d\n",
int(entry->added_verts.size()),
int(entry->deleted_verts.size()),
int(entry->modified_verts.size()));
printf("f | added: %d, removed: %d, modified: %d\n",
int(entry->added_faces.size()),
int(entry->deleted_faces.size()),
int(entry->modified_faces.size()));
printf("}\n");
}
#endif
/** \} */
} // namespace blender

View File

@@ -0,0 +1,205 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
struct RangeTreeUInt;
namespace blender {
struct BMFace;
struct BMVert;
struct BMesh;
struct BMLog;
struct BMLogEntry;
/**
* Allocate, initialize, and assign a new BMLog.
*/
BMLog *BM_log_create(BMesh *bm);
/**
* Allocate and initialize a new #BMLog using existing #BMLogEntries
*
* The unused IDs field of the log will be initialized by taking all
* keys from all Maps in the log entry.
*
* \param entry: The last entry of the prior BMLog, its `prev` pointer will be followed back to
* reconstruct the log.
*/
BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry);
/**
* Free all the data in a BMLog including the log itself.
*/
void BM_log_free(BMLog *log);
/**
* Start a new log entry and update the log entry list.
*
* If the log entry list is empty, or if the current log entry is the
* last entry, the new entry is simply appended to the end.
*
* Finally, the new entry is set as the current log entry.
*/
BMLogEntry *BM_log_entry_add(BMLog *log);
/** Mark all used ids as unused for this node */
void BM_log_cleanup_entry(BMLogEntry *entry);
/**
* Remove an entry from the log.
*
* Uses entry->log as the log. If the log is NULL, the entry will be
* freed but not removed from any list, nor shall its IDs be released.
*
* \warning This operation is only valid on the first and last entries in the log. Deleting from
* the middle will assert.
*/
void BM_log_entry_drop(BMLogEntry *entry);
/**
* Undo one #BMLogEntry.
*
* Has no effect if there's nothing left to undo.
*/
void BM_log_undo(BMesh *bm, BMLog *log);
/**
* Redo one #BMLogEntry.
*
* Has no effect if there's nothing left to redo.
*/
void BM_log_redo(BMesh *bm, BMLog *log);
/**
* Log a vertex before it is modified.
*
* Before modifying vertex coordinates, masks, or hflags, call this
* function to log its current values. This is better than logging
* after the coordinates have been modified, because only those
* vertices that are modified need to have their original values
* stored.
*
* Handles two separate cases:
*
* If the vertex was added in the current log entry, update the
* vertex in the map of added vertices.
*
* If the vertex already existed prior to the current log entry, a
* separate key/value map of modified vertices is used (using the
* vertex's ID as the key). The values stored in that case are
* the vertex's original state so that an undo can restore the
* previous state.
*
* On undo, the current vertex state will be swapped with the stored
* state so that a subsequent redo operation will restore the newer
* vertex state.
*/
void BM_log_vert_before_modified(BMLog *log, BMVert *v, int cd_vert_mask_offset);
/**
* Log a new vertex as added to the #BMesh.
*
* The new vertex gets a unique ID assigned. It is then added to a map
* of added vertices, with the key being its ID and the value
* containing everything needed to reconstruct that vertex.
*/
void BM_log_vert_added(BMLog *log, BMVert *v, int cd_vert_mask_offset);
/**
* Log a face before it is modified.
*
* This is intended to handle only header flags and we always
* assume face has been added before.
*/
void BM_log_face_modified(BMLog *log, BMFace *f);
/**
* Log a new face as added to the #BMesh.
*
* The new face gets a unique ID assigned. It is then added to a map
* of added faces, with the key being its ID and the value containing
* everything needed to reconstruct that face.
*/
void BM_log_face_added(BMLog *log, BMFace *f);
/**
* Log a vertex as removed from the #BMesh.
*
* A couple things can happen here:
*
* If the vertex was added as part of the current log entry, then it's
* deleted and forgotten about entirely. Its unique ID is returned to
* the unused pool.
*
* If the vertex was already part of the #BMesh before the current log
* entry, it is added to a map of deleted vertices, with the key being
* its ID and the value containing everything needed to reconstruct
* that vertex.
*
* If there's a move record for the vertex, that's used as the
* vertices original location, then the move record is deleted.
*/
void BM_log_vert_removed(BMLog *log, BMVert *v, int cd_vert_mask_offset);
/**
* Log a face as removed from the #BMesh.
*
* A couple things can happen here:
*
* If the face was added as part of the current log entry, then it's
* deleted and forgotten about entirely. Its unique ID is returned to
* the unused pool.
*
* If the face was already part of the #BMesh before the current log
* entry, it is added to a map of deleted faces, with the key being
* its ID and the value containing everything needed to reconstruct
* that face.
*/
void BM_log_face_removed(BMLog *log, BMFace *f);
/**
* Log all vertices/faces in the #BMesh as added.
*/
void BM_log_all_added(BMesh *bm, BMLog *log);
/** Log all vertices/faces in the #BMesh as removed. */
void BM_log_before_all_removed(BMesh *bm, BMLog *log);
/**
* Search the log for the original vertex coordinates.
*
* Does not modify the log or the vertex.
*
* \return the pointer or nullptr if the vertex isn't found.
*/
const float *BM_log_find_original_vert_co(BMLog *log, BMVert *v);
/**
* Search the log for the original vertex mask.
*
* Does not modify the log or the vertex.
*
* \return the pointer or nullptr if the vertex isn't found.
*/
const float *BM_log_find_original_vert_mask(BMLog *log, BMVert *v);
/** Get the logged data of a vertex (avoid multiple lookups). */
void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no);
#ifndef NDEBUG
/** For internal use only (unit testing). */
BMLogEntry *BM_log_current_entry(BMLog *log);
/** For internal use only (unit testing) */
struct RangeTreeUInt *BM_log_unused_ids(BMLog *log);
void BM_log_print(const BMLog *log, const char *description);
void BM_log_print_entry(BMesh *bm, BMLogEntry *entry);
#endif
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,232 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#pragma once
#include "BLI_enum_flags.hh"
#include "BLI_map.hh"
#include "bmesh_class.hh"
namespace blender {
struct BMEditSelection {
struct BMEditSelection *next, *prev;
BMElem *ele;
char htype;
};
enum class BMSelectFlushFlag : uint8_t {
None = 0,
RecalcLenVert = (1 << 0),
RecalcLenEdge = (1 << 1),
RecalcLenFace = (1 << 2),
/**
* Flush selection down, depending on the selection mode.
*
* Disabled by default as edge & face selection functions flush down:
* (functions #BM_edge_select_set & #BM_face_select_set).
* However selection logic needs to take care to perform de-selection *before* selection,
* otherwise flushing down *is* needed.
*/
Down = (1 << 3),
};
ENUM_OPERATORS(BMSelectFlushFlag)
#define BMSelectFlushFlag_All \
(BMSelectFlushFlag::RecalcLenVert | BMSelectFlushFlag::RecalcLenEdge | \
BMSelectFlushFlag::RecalcLenFace)
#define BMSelectFlushFlag_Default BMSelectFlushFlag_All
/* Geometry hiding code. */
#define BM_elem_hide_set(bm, ele, hide) _bm_elem_hide_set(bm, &(ele)->head, hide)
void _bm_elem_hide_set(BMesh *bm, BMHeader *head, bool hide);
void BM_vert_hide_set(BMVert *v, bool hide);
void BM_edge_hide_set(BMEdge *e, bool hide);
void BM_face_hide_set(BMFace *f, bool hide);
/* Selection code. */
/**
* \note use BM_elem_flag_test(ele, BM_ELEM_SELECT) to test selection
* \note by design, this will not touch the editselection history stuff
*/
void BM_elem_select_set(BMesh *bm, BMElem *ele, bool select);
void BM_mesh_elem_hflag_enable_test(
BMesh *bm, char htype, char hflag, bool respecthide, bool overwrite, char hflag_test);
void BM_mesh_elem_hflag_disable_test(
BMesh *bm, char htype, char hflag, bool respecthide, bool overwrite, char hflag_test);
void BM_mesh_elem_hflag_enable_all(BMesh *bm, char htype, char hflag, bool respecthide);
void BM_mesh_elem_hflag_disable_all(BMesh *bm, char htype, char hflag, bool respecthide);
/* Individual element select functions, #BM_elem_select_set is a shortcut for these
* that automatically detects which one to use. */
/**
* \brief Select Vert
*
* Changes selection state of a single vertex
* in a mesh
*/
void BM_vert_select_set(BMesh *bm, BMVert *v, bool select);
/**
* \brief Select Edge
*
* Changes selection state of a single edge in a mesh.
*/
void BM_edge_select_set(BMesh *bm, BMEdge *e, bool select);
/**
* \brief Select Face
*
* Changes selection state of a single
* face in a mesh.
*/
void BM_face_select_set(BMesh *bm, BMFace *f, bool select);
/* Lower level functions which don't do flushing. */
void BM_edge_select_set_noflush(BMesh *bm, BMEdge *e, bool select);
void BM_face_select_set_noflush(BMesh *bm, BMFace *f, bool select);
/**
* Return true when there are a mix of selected/unselected elements.
*/
bool BM_mesh_select_is_mixed(const BMesh *bm);
/**
* \brief Select Mode Clean
*
* Remove isolated selected elements when in a mode doesn't support them.
* eg: in edge-mode a selected vertex must be connected to a selected edge.
*
* \note this could be made a part of #BM_mesh_select_mode_flush_ex
*/
void BM_mesh_select_mode_clean_ex(BMesh *bm, short selectmode);
void BM_mesh_select_mode_clean(BMesh *bm);
/**
* Select Mode Set
*
* Sets the selection mode for the bmesh,
* updating the selection state.
*/
void BM_mesh_select_mode_set(BMesh *bm, int selectmode);
/**
* \brief Select Mode Flush
*
* Makes sure to flush selections 'upwards'
* (ie: all verts of an edge selects the edge and so on).
* This should only be called by system and not tool authors.
*
* \note Flushing down can be enabled for edge/face modes
* by enabling #BMSelectFlushFlag:Down for `flag`.
*/
void BM_mesh_select_mode_flush_ex(BMesh *bm, short selectmode, BMSelectFlushFlag flag);
void BM_mesh_select_mode_flush(BMesh *bm);
/**
* Mode independent selection/de-selection flush from vertices.
*
* \param select: When true, flush the selection state to de-selected elements,
* otherwise perform the opposite, flushing de-selection.
*/
void BM_mesh_select_flush_from_verts(BMesh *bm, bool select);
int BM_mesh_elem_hflag_count_enabled(BMesh *bm, char htype, char hflag, bool respecthide);
int BM_mesh_elem_hflag_count_disabled(BMesh *bm, char htype, char hflag, bool respecthide);
/* Edit selection stuff. */
void BM_mesh_active_face_set(BMesh *bm, BMFace *f);
int BM_mesh_active_face_index_get(BMesh *bm, bool is_sloppy, bool is_selected);
int BM_mesh_active_edge_index_get(BMesh *bm);
int BM_mesh_active_vert_index_get(BMesh *bm);
BMFace *BM_mesh_active_face_get(BMesh *bm, bool is_sloppy, bool is_selected);
BMEdge *BM_mesh_active_edge_get(BMesh *bm);
BMVert *BM_mesh_active_vert_get(BMesh *bm);
BMElem *BM_mesh_active_elem_get(BMesh *bm);
/**
* Generic way to get data from an #BMEditSelection type
* These functions were written to be used by the Modifier widget
* when in Rotate about active mode, but can be used anywhere.
*
* - #BM_editselection_center
* - #BM_editselection_normal
* - #BM_editselection_plane
*/
void BM_editselection_center(BMEditSelection *ese, float r_center[3]);
void BM_editselection_normal(BMEditSelection *ese, float r_normal[3]);
/**
* Calculate a plane that is right angles to the edge/vert/faces normal
* also make the plane run along an axis that is related to the geometry,
* because this is used for the gizmos Y axis.
*/
void BM_editselection_plane(BMEditSelection *ese, float r_plane[3]);
#define BM_select_history_check(bm, ele) _bm_select_history_check(bm, &(ele)->head)
#define BM_select_history_remove(bm, ele) _bm_select_history_remove(bm, &(ele)->head)
#define BM_select_history_store_notest(bm, ele) _bm_select_history_store_notest(bm, &(ele)->head)
#define BM_select_history_store(bm, ele) _bm_select_history_store(bm, &(ele)->head)
#define BM_select_history_store_head_notest(bm, ele) \
_bm_select_history_store_head_notest(bm, &(ele)->head)
#define BM_select_history_store_head(bm, ele) _bm_select_history_store_head(bm, &(ele)->head)
#define BM_select_history_store_after_notest(bm, ese_ref, ele) \
_bm_select_history_store_after_notest(bm, ese_ref, &(ele)->head)
#define BM_select_history_store_after(bm, ese, ese_ref) \
_bm_select_history_store_after(bm, ese_ref, &(ele)->head)
bool _bm_select_history_check(BMesh *bm, const BMHeader *ele);
bool _bm_select_history_remove(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_notest(BMesh *bm, BMHeader *ele);
void _bm_select_history_store(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_head_notest(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_head(BMesh *bm, BMHeader *ele);
void _bm_select_history_store_after(BMesh *bm, BMEditSelection *ese_ref, BMHeader *ele);
void _bm_select_history_store_after_notest(BMesh *bm, BMEditSelection *ese_ref, BMHeader *ele);
void BM_select_history_validate(BMesh *bm);
void BM_select_history_clear(BMesh *bm);
/**
* Get all element types present in a selection history.
*/
[[nodiscard]] char BM_select_history_htype_all(const BMesh *bm);
/**
* Get the active mesh element (with active-face fallback).
*/
bool BM_select_history_active_get(BMesh *bm, struct BMEditSelection *ese);
/**
* Return a map from #BMVert/#BMEdge/#BMFace -> #BMEditSelection.
*/
struct GHash *BM_select_history_map_create(BMesh *bm);
/**
* Map arguments may all be the same pointer.
*/
void BM_select_history_merge_from_targetmap(BMesh *bm,
Map<void *, void *> *vert_map,
Map<void *, void *> *edge_map,
Map<void *, void *> *face_map,
bool use_chain);
#define BM_SELECT_HISTORY_BACKUP(bm) \
{ \
ListBaseT<BMEditSelection> _bm_prev_selected = (bm)->selected; \
BLI_listbase_clear(&(bm)->selected)
#define BM_SELECT_HISTORY_RESTORE(bm) \
(bm)->selected = _bm_prev_selected; \
} \
(void)0
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_array.hh"
#include "BLI_math_matrix_types.hh"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
namespace blender {
struct BMAllocTemplate;
void BM_mesh_elem_toolflags_ensure(BMesh *bm);
void BM_mesh_elem_toolflags_clear(BMesh *bm);
struct BMeshCreateParams {
bool use_toolflags : 1;
};
/**
* \brief BMesh Make Mesh
*
* Allocates a new BMesh structure.
*
* \return The New bmesh
*
* \note ob is needed by multires
*/
BMesh *BM_mesh_create(const BMAllocTemplate *allocsize, const BMeshCreateParams *params);
/**
* \brief BMesh Free Mesh
*
* Frees a BMesh data and its structure.
*/
void BM_mesh_free(BMesh *bm);
/**
* \brief BMesh Free Mesh Data
*
* Frees a BMesh structure.
*
* \note frees mesh, but not actual BMesh struct
*/
void BM_mesh_data_free(BMesh *bm);
/**
* \brief BMesh Clear Mesh
*
* Clear all data in bm
*/
void BM_mesh_clear(BMesh *bm);
/**
* \brief BMesh Begin Edit
*
* Functions for setting up a mesh for editing and cleaning up after
* the editing operations are done. These are called by the tools/operator
* API for each time a tool is executed.
*/
void bmesh_edit_begin(BMesh *bm, BMOpTypeFlag type_flag);
/**
* \brief BMesh End Edit
*/
void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag);
void BM_mesh_elem_index_ensure_ex(BMesh *bm, char htype, int elem_offset[4]);
void BM_mesh_elem_index_ensure(BMesh *bm, char htype);
/**
* Array checking/setting macros.
*
* Currently vert/edge/loop/face index data is being abused, in a few areas of the code.
*
* To avoid correcting them afterwards, set 'bm->elem_index_dirty' however its possible
* this flag is set incorrectly which could crash blender.
*
* Functions that calls this function may depend on dirty indices on being set.
*
* This is read-only, so it can be used for assertions that don't impact behavior.
*/
void BM_mesh_elem_index_validate(
BMesh *bm, const char *location, const char *func, const char *msg_a, const char *msg_b);
#ifndef NDEBUG
/**
* \see #BM_mesh_elem_index_validate the same rationale applies to this function.
*/
bool BM_mesh_elem_table_check(BMesh *bm);
#endif
/**
* Re-allocates mesh data with/without toolflags.
*/
void BM_mesh_toolflags_set(BMesh *bm, bool use_toolflags);
void BM_mesh_elem_table_ensure(BMesh *bm, char htype);
/* use BM_mesh_elem_table_ensure where possible to avoid full rebuild */
void BM_mesh_elem_table_init(BMesh *bm, char htype);
void BM_mesh_elem_table_free(BMesh *bm, char htype);
BLI_INLINE BMVert *BM_vert_at_index(BMesh *bm, const int index)
{
BLI_assert((index >= 0) && (index < bm->totvert));
BLI_assert((bm->elem_table_dirty & BM_VERT) == 0);
return bm->vtable[index];
}
BLI_INLINE BMEdge *BM_edge_at_index(BMesh *bm, const int index)
{
BLI_assert((index >= 0) && (index < bm->totedge));
BLI_assert((bm->elem_table_dirty & BM_EDGE) == 0);
return bm->etable[index];
}
BLI_INLINE BMFace *BM_face_at_index(BMesh *bm, const int index)
{
BLI_assert((index >= 0) && (index < bm->totface));
BLI_assert((bm->elem_table_dirty & BM_FACE) == 0);
return bm->ftable[index];
}
BMVert *BM_vert_at_index_find(BMesh *bm, int index);
BMEdge *BM_edge_at_index_find(BMesh *bm, int index);
BMFace *BM_face_at_index_find(BMesh *bm, int index);
BMLoop *BM_loop_at_index_find(BMesh *bm, int index);
/**
* Use lookup table when available, else use slower find functions.
*
* \note Try to use #BM_mesh_elem_table_ensure instead.
*/
BMVert *BM_vert_at_index_find_or_table(BMesh *bm, int index);
BMEdge *BM_edge_at_index_find_or_table(BMesh *bm, int index);
BMFace *BM_face_at_index_find_or_table(BMesh *bm, int index);
// XXX
/**
* Return the amount of element of type 'type' in a given bmesh.
*/
int BM_mesh_elem_count(BMesh *bm, char htype);
/**
* Remaps the vertices, edges and/or faces of the bmesh as indicated by vert/edge/face_idx arrays
* (xxx_idx[org_index] = new_index).
*
* A NULL array means no changes.
*
* \note
* - Does not mess with indices, just sets elem_index_dirty flag.
* - For verts/edges/faces only (as loops must remain "ordered" and "aligned"
* on a per-face basis...).
*
* \warning Be careful if you keep pointers to affected BM elements,
* or arrays, when using this func!
*/
void BM_mesh_remap(BMesh *bm, const uint *vert_idx, const uint *edge_idx, const uint *face_idx);
/**
* Use new memory pools for this mesh.
*
* \note needed for re-sizing elements (adding/removing tool flags)
* but could also be used for packing fragmented bmeshes.
*/
void BM_mesh_rebuild(BMesh *bm,
const BMeshCreateParams *params,
BLI_mempool *vpool,
BLI_mempool *epool,
BLI_mempool *lpool,
BLI_mempool *fpool);
struct BMAllocTemplate {
int totvert, totedge, totloop, totface;
};
/* used as an extern, defined in bmesh.h */
extern const BMAllocTemplate bm_mesh_allocsize_default;
extern const BMAllocTemplate bm_mesh_chunksize_default;
#define BMALLOC_TEMPLATE_FROM_BM(bm) \
{(CHECK_TYPE_INLINE(bm, BMesh *), (bm)->totvert), (bm)->totedge, (bm)->totloop, (bm)->totface}
#define _VA_BMALLOC_TEMPLATE_FROM_ME_1(me) \
{ \
(CHECK_TYPE_INLINE(me, Mesh *), (me)->verts_num), \
(me)->edges_num, \
(me)->corners_num, \
(me)->faces_num, \
}
#define _VA_BMALLOC_TEMPLATE_FROM_ME_2(me_a, me_b) \
{ \
(CHECK_TYPE_INLINE(me_a, Mesh *), \
CHECK_TYPE_INLINE(me_b, Mesh *), \
(me_a)->verts_num + (me_b)->verts_num), \
(me_a)->edges_num + (me_b)->edges_num, \
(me_a)->corners_num + (me_b)->corners_num, \
(me_a)->faces_num + (me_b)->faces_num, \
}
#define BMALLOC_TEMPLATE_FROM_ME(...) \
VA_NARGS_CALL_OVERLOAD(_VA_BMALLOC_TEMPLATE_FROM_ME_, __VA_ARGS__)
void BM_mesh_vert_normals_get(BMesh *bm, MutableSpan<float3> normals);
/* Vertex coords access. */
void BM_mesh_vert_coords_get(BMesh *bm, MutableSpan<float3> positions);
Array<float3> BM_mesh_vert_coords_alloc(BMesh *bm);
void BM_mesh_vert_coords_apply(BMesh *bm, Span<float3> vert_coords);
void BM_mesh_vert_coords_apply_with_mat4(BMesh *bm,
Span<float3> vert_coords,
const float4x4 &transform);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,109 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_string_ref.hh"
#include "bmesh.hh"
namespace blender {
/**
* \return Whether attributes with the given name are stored in special flags or fields in BMesh
* rather than in the regular custom data blocks.
*/
bool BM_attribute_stored_in_bmesh_builtin(StringRef name);
struct CustomData_MeshMasks;
struct Main;
struct Mesh;
struct BMeshFromMeshParams {
bool calc_face_normal;
bool calc_vert_normal;
/* add a vertex CD_SHAPE_KEYINDEX layer */
bool add_key_index;
/* set vertex coordinates from the shapekey */
bool use_shapekey;
/* define the active shape key (index + 1) */
int active_shapekey;
struct CustomData_MeshMasks cd_mask_extra;
};
/**
* \brief Mesh -> BMesh
* \param bm: The mesh to write into, while this is typically a newly created BMesh,
* merging into existing data is supported.
* Note the custom-data layout isn't used.
* If more comprehensive merging is needed we should move this into a separate function
* since this should be kept fast for edit-mode switching and storing undo steps.
*
* \warning This function doesn't calculate face normals.
*/
void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *params)
ATTR_NONNULL(1, 3);
struct BMeshToMeshParams {
/** Update object hook indices & vertex parents. */
bool calc_object_remap;
/**
* This re-assigns shape-key indices. Only do if the BMesh will have continued use
* to update the mesh & shape key in the future.
* In the case the BMesh is freed immediately, this can be left false.
*
* This is needed when flushing changes from edit-mode into object mode,
* so a second flush or edit-mode exit doesn't run with indices
* that have become invalid from updating the shape-key, see #71865.
*/
bool update_shapekey_indices;
/**
* Instead of copying the basis shape-key into the position array,
* copy the #BMVert.co directly to the #Mesh position (used for reading undo data).
*/
bool active_shapekey_to_mvert;
struct CustomData_MeshMasks cd_mask_extra;
};
/**
* \param bmain: May be NULL in case \a calc_object_remap parameter option is not set.
*/
void BM_mesh_bm_to_me(struct Main *bmain, BMesh *bm, Mesh *mesh, const BMeshToMeshParams *params)
ATTR_NONNULL(2, 3, 4);
/**
* A version of #BM_mesh_bm_to_me intended for getting the mesh
* to pass to the modifier stack for evaluation,
* instead of mode switching (where we make sure all data is kept
* and do expensive lookups to maintain shape keys).
*
* Key differences:
*
* - Don't support merging with existing mesh.
* - Ignore shape-keys.
* - Ignore vertex-parents.
* - Ignore selection history.
* - Uses #CD_MASK_DERIVEDMESH instead of #CD_MASK_MESH.
*
* \note Was `cddm_from_bmesh_ex` in 2.7x, removed `MFace` support.
*/
void BM_mesh_bm_to_me_for_eval(BMesh &bm, Mesh &mesh, const CustomData_MeshMasks *cd_mask_extra);
/**
* A version of #BM_mesh_bm_to_me_for_eval but copying data layers and Mesh attributes is optional.
* It also allows shape-keys but don't re-assigns shape-key indices.
*
* \param mask: Custom data masks to control which layers are copied.
* If nullptr, no layer data is copied.
* \param add_mesh_attributes: If true, adds mesh attributes during the conversion.
*/
void BM_mesh_bm_to_me_compact(BMesh &bm,
Mesh &mesh,
const CustomData_MeshMasks *mask,
bool add_mesh_attributes);
} // namespace blender

View File

@@ -0,0 +1,76 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* Evaluated mesh info printing function, to help track down differences output.
*
* Output from these functions can be evaluated as Python literals.
* See `mesh_debug.cc` for the equivalent #Mesh functionality.
*/
#ifndef NDEBUG
# include <cstdio>
# include "MEM_guardedalloc.h"
# include "BKE_customdata.hh"
# include "bmesh.hh"
# include "bmesh_mesh_debug.hh"
# include "BLI_dynstr.h"
namespace blender {
char *BM_mesh_debug_info(BMesh *bm)
{
DynStr *dynstr = BLI_dynstr_new();
char *ret;
const char *indent8 = " ";
BLI_dynstr_append(dynstr, "{\n");
BLI_dynstr_appendf(dynstr, " 'ptr': '%p',\n", static_cast<void *>(bm));
BLI_dynstr_appendf(dynstr, " 'totvert': %d,\n", bm->totvert);
BLI_dynstr_appendf(dynstr, " 'totedge': %d,\n", bm->totedge);
BLI_dynstr_appendf(dynstr, " 'totface': %d,\n", bm->totface);
BLI_dynstr_append(dynstr, " 'vert_layers': (\n");
CustomData_debug_info_from_layers(&bm->vdata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, " 'edge_layers': (\n");
CustomData_debug_info_from_layers(&bm->edata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, " 'loop_layers': (\n");
CustomData_debug_info_from_layers(&bm->ldata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, " 'poly_layers': (\n");
CustomData_debug_info_from_layers(&bm->pdata, indent8, dynstr);
BLI_dynstr_append(dynstr, " ),\n");
BLI_dynstr_append(dynstr, "}\n");
ret = BLI_dynstr_get_cstring(dynstr);
BLI_dynstr_free(dynstr);
return ret;
}
void BM_mesh_debug_print(BMesh *bm)
{
char *str = BM_mesh_debug_info(bm);
puts(str);
fflush(stdout);
MEM_delete(str);
}
} // namespace blender
#endif /* !NDEBUG */

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_compiler_attrs.h"
#include "bmesh.hh"
namespace blender {
#ifndef NDEBUG
char *BM_mesh_debug_info(BMesh *bm) ATTR_NONNULL(1) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT;
void BM_mesh_debug_print(BMesh *bm) ATTR_NONNULL(1);
#endif /* !NDEBUG */
} // namespace blender

View File

@@ -0,0 +1,166 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Duplicate geometry from one mesh from another.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "bmesh.hh"
namespace blender {
static BMVert *bm_vert_copy(BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_vert_map,
BMVert *v_src)
{
BMVert *v_dst = BM_vert_create(bm_dst, v_src->co, nullptr, BM_CREATE_SKIP_CD);
if (cd_vert_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_vert_map.value(), v_src, v_dst);
}
else {
BM_elem_attrs_copy(bm_dst, v_src, v_dst);
}
return v_dst;
}
static BMEdge *bm_edge_copy_with_arrays(BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_edge_map,
BMEdge *e_src,
BMVert **verts_dst)
{
BMVert *e_dst_v1 = verts_dst[BM_elem_index_get(e_src->v1)];
BMVert *e_dst_v2 = verts_dst[BM_elem_index_get(e_src->v2)];
BMEdge *e_dst = BM_edge_create(bm_dst, e_dst_v1, e_dst_v2, nullptr, BM_CREATE_SKIP_CD);
if (cd_edge_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_edge_map.value(), e_src, e_dst);
}
else {
BM_elem_attrs_copy(bm_dst, e_src, e_dst);
}
return e_dst;
}
static BMFace *bm_face_copy_with_arrays(BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> cd_face_map,
const std::optional<BMCustomDataCopyMap> &cd_loop_map,
BMFace *f_src,
BMVert **verts_dst,
BMEdge **edges_dst)
{
BMFace *f_dst;
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vtar(f_src->len);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edar(f_src->len);
BMLoop *l_iter_src, *l_iter_dst, *l_first_src;
int i;
l_first_src = BM_FACE_FIRST_LOOP(f_src);
/* Lookup verts & edges. */
l_iter_src = l_first_src;
i = 0;
do {
vtar[i] = verts_dst[BM_elem_index_get(l_iter_src->v)];
edar[i] = edges_dst[BM_elem_index_get(l_iter_src->e)];
i++;
} while ((l_iter_src = l_iter_src->next) != l_first_src);
/* Create new face. */
f_dst = BM_face_create(bm_dst, vtar.data(), edar.data(), f_src->len, nullptr, BM_CREATE_SKIP_CD);
/* Copy attributes. */
if (cd_face_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_face_map.value(), f_src, f_dst);
}
else {
BM_elem_attrs_copy(bm_dst, f_src, f_dst);
}
/* Copy per-loop custom data. */
l_iter_src = l_first_src;
l_iter_dst = BM_FACE_FIRST_LOOP(f_dst);
do {
if (cd_loop_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_loop_map.value(), l_iter_src, l_iter_dst);
}
else {
BM_elem_attrs_copy(bm_dst, l_iter_src, l_iter_dst);
}
} while ((void)(l_iter_dst = l_iter_dst->next), (l_iter_src = l_iter_src->next) != l_first_src);
return f_dst;
}
void BM_mesh_copy_arrays(BMesh *bm_src,
BMesh *bm_dst,
BMVert **verts_src,
uint verts_src_len,
BMEdge **edges_src,
uint edges_src_len,
BMFace **faces_src,
uint faces_src_len)
{
const std::optional<BMCustomDataCopyMap> cd_vert_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->vdata, bm_dst->vdata)};
const std::optional<BMCustomDataCopyMap> cd_edge_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->edata, bm_dst->edata)};
const std::optional<BMCustomDataCopyMap> cd_face_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->pdata, bm_dst->pdata)};
const std::optional<BMCustomDataCopyMap> cd_loop_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->ldata, bm_dst->ldata)};
/* Vertices. */
BMVert **verts_dst = MEM_new_array_uninitialized<BMVert *>(verts_src_len, __func__);
for (uint i = 0; i < verts_src_len; i++) {
BMVert *v_src = verts_src[i];
BM_elem_index_set(v_src, i); /* set_dirty! */
BMVert *v_dst = bm_vert_copy(bm_dst, cd_vert_map, v_src);
BM_elem_index_set(v_dst, i); /* set_ok */
verts_dst[i] = v_dst;
}
bm_src->elem_index_dirty |= BM_VERT;
bm_dst->elem_index_dirty &= ~BM_VERT;
/* Edges. */
BMEdge **edges_dst = MEM_new_array_uninitialized<BMEdge *>(edges_src_len, __func__);
for (uint i = 0; i < edges_src_len; i++) {
BMEdge *e_src = edges_src[i];
BM_elem_index_set(e_src, i); /* set_dirty! */
BMEdge *e_dst = bm_edge_copy_with_arrays(bm_dst, cd_edge_map, e_src, verts_dst);
BM_elem_index_set(e_dst, i);
edges_dst[i] = e_dst;
}
bm_src->elem_index_dirty |= BM_EDGE;
bm_dst->elem_index_dirty &= ~BM_EDGE;
/* Faces. */
for (uint i = 0; i < faces_src_len; i++) {
BMFace *f_src = faces_src[i];
BMFace *f_dst = bm_face_copy_with_arrays(
bm_dst, cd_face_map, cd_loop_map, f_src, verts_dst, edges_dst);
BM_elem_index_set(f_dst, i);
}
bm_dst->elem_index_dirty &= ~BM_FACE;
/* Cleanup. */
MEM_delete(verts_dst);
MEM_delete(edges_dst);
}
} // namespace blender

View File

@@ -0,0 +1,27 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* Geometry must be completely isolated.
*/
void BM_mesh_copy_arrays(BMesh *bm_src,
BMesh *bm_dst,
BMVert **verts_src,
uint verts_src_len,
BMEdge **edges_src,
uint edges_src_len,
BMFace **faces_src,
uint faces_src_len);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "bmesh_class.hh"
namespace blender {
struct BMPartialUpdate;
struct BMeshNormalsUpdate_Params {
/**
* When calculating tessellation as well as normals, tessellate & calculate face normals
* for improved performance. See #BMeshCalcTessellation_Params
*/
bool face_normals;
};
/**
* \brief BMesh Compute Normals
*
* Updates the normals of a mesh.
*/
void BM_mesh_normals_update_ex(BMesh *bm, const BMeshNormalsUpdate_Params *param);
void BM_mesh_normals_update(BMesh *bm);
/**
* A version of #BM_mesh_normals_update that updates a subset of geometry,
* used to avoid the overhead of updating everything.
*/
void BM_mesh_normals_update_with_partial_ex(BMesh *bm,
const BMPartialUpdate *bmpinfo,
const BMeshNormalsUpdate_Params *param);
void BM_mesh_normals_update_with_partial(BMesh *bm, const BMPartialUpdate *bmpinfo);
/**
* \brief BMesh Compute Normals from/to external data.
*
* Computes the vertex normals of a mesh into vnos,
* using given vertex coordinates (vcos) and polygon normals (fnos).
*/
void BM_verts_calc_normal_vcos(BMesh *bm,
Span<float3> fnos,
Span<float3> vcos,
MutableSpan<float3> vnos);
/**
* \brief BMesh Compute Loop Normals from/to external data.
*
* Compute custom normals, i.e. vertex normals associated with each poly (hence 'loop normals').
* Useful to materialize sharp edges (or non-smooth faces) without actually modifying the geometry
* (splitting edges).
*/
void BM_loops_calc_normal_vcos(BMesh *bm,
Span<float3> vcos,
Span<float3> vnos,
Span<float3> fnos,
bool use_split_normals,
MutableSpan<float3> r_lnos,
MLoopNorSpaceArray *r_lnors_spacearr,
short (*clnors_data)[2],
int cd_loop_clnors_offset,
bool do_rebuild);
/**
* Check whether given loop is part of an unknown-so-far cyclic smooth fan, or not.
* Needed because cyclic smooth fans have no obvious 'entry point',
* and yet we need to walk them once, and only once.
*/
bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr);
void BM_lnorspacearr_store(BMesh *bm, MutableSpan<float3> r_lnors);
void BM_lnorspace_invalidate(BMesh *bm, bool do_invalidate_all);
void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor);
/**
* \warning This function sets #BM_ELEM_TAG on loops & edges via #bm_mesh_loops_calc_normals,
* take care to run this before setting up tags.
*/
void BM_lnorspace_update(BMesh *bm);
void BM_normals_loops_edges_tag(BMesh *bm, bool do_edges);
#ifndef NDEBUG
void BM_lnorspace_err(BMesh *bm);
#endif
/* Loop Generics */
/**
* Initialize loop data based on a type, overriding the #BMesh::selectmode of `bm`.
* This can be useful if a single types selection is preferred,
* instead of using mixed modes and the selection history.
*/
BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init_with_htype(BMesh *bm,
bool do_all_loops_of_vert,
char htype_override);
BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init(BMesh *bm, bool do_all_loops_of_vert);
void BM_loop_normal_editdata_array_free(BMLoopNorEditDataArray *lnors_ed_arr);
/**
* \warning This function sets #BM_ELEM_TAG on loops & edges via #bm_mesh_loops_calc_normals,
* take care to run this before setting up tags.
*/
bool BM_custom_loop_normals_to_vector_layer(BMesh *bm);
void BM_custom_loop_normals_from_vector_layer(BMesh *bm, bool add_sharp_edges);
/**
* Define sharp edges as needed to mimic auto-smooth from angle threshold.
*
* Used when defining an empty custom loop normals data layer,
* to keep same shading as with auto-smooth!
*/
void BM_edges_sharp_from_angle_set(BMesh *bm, float split_angle);
} // namespace blender

View File

@@ -0,0 +1,321 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Generate data needed for partially updating mesh information.
* Currently this is used for normals and tessellation.
*
* Transform is the obvious use case where there is no need to update normals or tessellation
* for geometry which has not been modified.
*
* In the future this could be integrated into GPU updates too.
*
* Kinds of Partial Geometry
* =========================
*
* All Tagged
* ----------
* Operate on everything that's tagged as well as connected geometry.
* see: #BM_mesh_partial_create_from_verts
*
* Grouped
* -------
* Operate on everything that is connected to both tagged and un-tagged.
* see: #BM_mesh_partial_create_from_verts_group_single
*
* Reduces computations when transforming isolated regions.
*
* Optionally support multiple groups since axis-mirror (for example)
* will transform vertices in different directions, as well as keeping centered vertices.
* see: #BM_mesh_partial_create_from_verts_group_multi
*
* \note Others can be added as needed.
*/
#include "MEM_guardedalloc.h"
#include "BLI_bit_vector.hh"
#include "BLI_math_base.h"
#include "bmesh.hh"
namespace blender {
BLI_INLINE bool partial_elem_vert_ensure(BMPartialUpdate *bmpinfo,
MutableBitSpan verts_tag,
BMVert *v)
{
const int i = BM_elem_index_get(v);
if (!verts_tag[i]) {
verts_tag[i].set();
bmpinfo->verts.append(v);
return true;
}
return false;
}
BLI_INLINE bool partial_elem_face_ensure(BMPartialUpdate *bmpinfo,
MutableBitSpan faces_tag,
BMFace *f)
{
const int i = BM_elem_index_get(f);
if (!faces_tag[i]) {
faces_tag[i].set();
bmpinfo->faces.append(f);
return true;
}
return false;
}
BMPartialUpdate *BM_mesh_partial_create_from_verts(BMesh &bm,
const BMPartialUpdate_Params &params,
const BitSpan verts_mask,
const int verts_mask_count)
{
/* The caller is doing something wrong if this isn't the case. */
BLI_assert(verts_mask_count <= bm.totvert);
BMPartialUpdate *bmpinfo = MEM_new<BMPartialUpdate>(__func__);
/* Reserve more edges than vertices since it's common for a grid topology
* to use around twice as many edges as vertices. */
const int default_verts_len_alloc = verts_mask_count;
const int default_faces_len_alloc = min_ii(bm.totface, verts_mask_count);
/* Allocate tags instead of using #BM_ELEM_TAG because the caller may already be using tags.
* Further, walking over all geometry to clear the tags isn't so efficient. */
BitVector<> verts_tag;
BitVector<> faces_tag;
/* Set vert inline. */
BM_mesh_elem_index_ensure(&bm, BM_FACE);
if (params.do_normals || params.do_tessellate) {
/* - Extend to all vertices connected faces:
* In the case of tessellation this is enough.
*
* In the case of vertex normal calculation,
* All the relevant connectivity data can be accessed from the faces
* (there is no advantage in storing connected edges or vertices in this pass).
*
* NOTE: In the future it may be useful to differentiate between vertices
* that are directly marked (by the filter function when looping over all vertices).
* And vertices marked from indirect connections.
* This would require an extra tag array, so avoid this unless it's needed.
*/
/* Faces. */
bmpinfo->faces.reserve(default_faces_len_alloc);
faces_tag.resize(bm.totface);
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (v, &iter, &bm, BM_VERTS_OF_MESH, i) {
BM_elem_index_set(v, i); /* set_inline */
if (!verts_mask[i]) {
continue;
}
BMEdge *e_iter = v->e;
if (e_iter != nullptr) {
/* Loop over edges. */
BMEdge *e_first = v->e;
do {
BMLoop *l_iter = e_iter->l;
if (e_iter->l != nullptr) {
BMLoop *l_first = e_iter->l;
/* Loop over radial loops. */
do {
if (l_iter->v == v) {
partial_elem_face_ensure(bmpinfo, faces_tag, l_iter->f);
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v)) != e_first);
}
}
}
if (params.do_normals) {
/* - Extend to all faces vertices:
* Any changes to the faces normal needs to update all surrounding vertices.
*
* - Extend to all these vertices connected edges:
* These and needed to access those vertices edge vectors in normal calculation logic.
*/
/* Vertices. */
bmpinfo->verts.reserve(default_verts_len_alloc);
verts_tag.resize(bm.totvert);
for (const BMFace *f : bmpinfo->faces) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
partial_elem_vert_ensure(bmpinfo, verts_tag, l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
}
bmpinfo->params = params;
return bmpinfo;
}
BMPartialUpdate *BM_mesh_partial_create_from_verts_group_single(
BMesh &bm,
const BMPartialUpdate_Params &params,
const BitSpan verts_mask,
const int verts_mask_count)
{
BMPartialUpdate *bmpinfo = MEM_new<BMPartialUpdate>(__func__);
BitVector<> verts_tag;
BitVector<> faces_tag;
int face_tag_loop_len = 0;
if (params.do_normals || params.do_tessellate) {
faces_tag.resize(bm.totface);
BMFace *f;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (f, &iter, &bm, BM_FACES_OF_MESH, i) {
enum Side { SIDE_A = (1 << 0), SIDE_B = (1 << 1) } side_flag = Side(0);
BM_elem_index_set(f, i); /* set_inline */
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const int j = BM_elem_index_get(l_iter->v);
side_flag = Side(side_flag | (verts_mask[j].test() ? SIDE_A : SIDE_B));
if (UNLIKELY(side_flag == (SIDE_A | SIDE_B))) {
partial_elem_face_ensure(bmpinfo, faces_tag, f);
face_tag_loop_len += f->len;
break;
}
} while ((l_iter = l_iter->next) != l_first);
}
}
if (params.do_normals) {
/* Extend to all faces vertices:
* Any changes to the faces normal needs to update all surrounding vertices. */
/* Over allocate using the total number of face loops. */
bmpinfo->verts.reserve(min_ii(bm.totvert, max_ii(1, face_tag_loop_len)));
verts_tag.resize(bm.totvert);
for (BMFace *f : bmpinfo->faces) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
partial_elem_vert_ensure(bmpinfo, verts_tag, l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
/* Loose vertex support, these need special handling as loose normals depend on location. */
if (bmpinfo->verts.size() < verts_mask_count) {
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (v, &iter, &bm, BM_VERTS_OF_MESH, i) {
if (verts_mask[i] && (BM_vert_find_first_loop(v) == nullptr)) {
partial_elem_vert_ensure(bmpinfo, verts_tag, v);
}
}
}
}
bmpinfo->params = params;
return bmpinfo;
}
BMPartialUpdate *BM_mesh_partial_create_from_verts_group_multi(
BMesh &bm,
const BMPartialUpdate_Params &params,
const Span<int> verts_group,
const int verts_group_count)
{
/* Provide a quick way of visualizing which faces are being manipulated. */
// #define DEBUG_MATERIAL
BMPartialUpdate *bmpinfo = MEM_new<BMPartialUpdate>(__func__);
BitVector<> verts_tag;
BitVector<> faces_tag;
int face_tag_loop_len = 0;
if (params.do_normals || params.do_tessellate) {
faces_tag.resize(bm.totface);
BMFace *f;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (f, &iter, &bm, BM_FACES_OF_MESH, i) {
BM_elem_index_set(f, i); /* set_inline */
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
const int group_test = verts_group[BM_elem_index_get(l_iter->prev->v)];
#ifdef DEBUG_MATERIAL
f->mat_nr = 0;
#endif
do {
const int group_iter = verts_group[BM_elem_index_get(l_iter->v)];
if (UNLIKELY((group_iter != group_test) || (group_iter == -1))) {
partial_elem_face_ensure(bmpinfo, faces_tag, f);
face_tag_loop_len += f->len;
#ifdef DEBUG_MATERIAL
f->mat_nr = 1;
#endif
break;
}
} while ((l_iter = l_iter->next) != l_first);
}
}
if (params.do_normals) {
/* Extend to all faces vertices:
* Any changes to the faces normal needs to update all surrounding vertices. */
/* Over allocate using the total number of face loops. */
bmpinfo->verts.reserve(min_ii(bm.totvert, max_ii(1, face_tag_loop_len)));
verts_tag.resize(bm.totvert);
for (BMFace *f : bmpinfo->faces) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
partial_elem_vert_ensure(bmpinfo, verts_tag, l_iter->v);
} while ((l_iter = l_iter->next) != l_first);
}
/* Loose vertex support, these need special handling as loose normals depend on location. */
if (bmpinfo->verts.size() < verts_group_count) {
BMVert *v;
BMIter iter;
int i;
BM_ITER_MESH_INDEX (v, &iter, &bm, BM_VERTS_OF_MESH, i) {
if ((verts_group[i] != 0) && (BM_vert_find_first_loop(v) == nullptr)) {
partial_elem_vert_ensure(bmpinfo, verts_tag, v);
}
}
}
}
bmpinfo->params = params;
return bmpinfo;
}
void BM_mesh_partial_destroy(BMPartialUpdate *bmpinfo)
{
MEM_delete(bmpinfo);
}
} // namespace blender

View File

@@ -0,0 +1,86 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_bit_span.hh"
#include "BLI_compiler_attrs.h"
#include "BLI_vector.hh"
#include "bmesh_class.hh"
namespace blender {
/**
* Parameters used to determine which kinds of data needs to be generated.
*/
struct BMPartialUpdate_Params {
bool do_normals;
bool do_tessellate;
};
/**
* Cached data to speed up partial updates.
*
* Hints:
*
* - Avoid creating this data for single updates,
* it should be created and reused across multiple updates to gain a significant benefit
* (while transforming geometry for example).
*
* - Partial normal updates use face & loop indices,
* setting them to dirty values between updates will slow down normal recalculation.
*/
struct BMPartialUpdate {
Vector<BMVert *> verts;
Vector<BMFace *> faces;
/** Store the parameters used in creation so invalid use can be asserted. */
BMPartialUpdate_Params params = {};
};
/**
* All Tagged & Connected, see: #BM_mesh_partial_create_from_verts
* Operate on everything that's tagged as well as connected geometry.
*/
[[nodiscard]] BMPartialUpdate *BM_mesh_partial_create_from_verts(
BMesh &bm, const BMPartialUpdate_Params &params, BitSpan verts_mask, int verts_mask_count);
/**
* All Connected, operate on all faces that have both tagged and un-tagged vertices.
*
* Reduces computations when transforming isolated regions.
*/
[[nodiscard]] BMPartialUpdate *BM_mesh_partial_create_from_verts_group_single(
BMesh &bm, const BMPartialUpdate_Params &params, BitSpan verts_mask, int verts_mask_count);
/**
* All Connected, operate on all faces that have vertices in the same group.
*
* Reduces computations when transforming isolated regions.
*
* This is a version of #BM_mesh_partial_create_from_verts_group_single
* that handles multiple groups instead of a bitmap mask.
*
* This is needed for example when transform has mirror enabled,
* since one side needs to have a different group to the other since a face that has vertices
* attached to both won't have an affine transformation.
*
* \param verts_group: Vertex aligned array of groups.
* Values are used as follows:
* - >0: Each face is grouped with other faces of the same group.
* - 0: Not in a group (don't handle these).
* - -1: Don't use grouping logic (include any face that contains a vertex with this group).
* \param verts_group_count: The number of non-zero values in `verts_groups`.
*/
[[nodiscard]] BMPartialUpdate *BM_mesh_partial_create_from_verts_group_multi(
BMesh &bm, const BMPartialUpdate_Params &params, Span<int> verts_group, int verts_group_count);
void BM_mesh_partial_destroy(BMPartialUpdate *bmpinfo) ATTR_NONNULL(1);
} // namespace blender

View File

@@ -0,0 +1,566 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* This file contains code for polygon tessellation
* (creating triangles from polygons).
*
* \see mesh_tessellate.cc for the #Mesh equivalent of this file.
*/
#include "BLI_heap.h"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_memarena.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "BLI_task.h"
#include "bmesh.hh"
namespace blender {
/**
* On systems with 32+ cores,
* only a very small number of faces has any advantage single threading (in the 100's).
* Note that between 500-2000 quads, the difference isn't so much
* (tessellation isn't a bottleneck in this case anyway).
* Avoid the slight overhead of using threads in this case.
*/
#define BM_FACE_TESSELLATE_THREADED_LIMIT 1024
/* -------------------------------------------------------------------- */
/** \name Default Mesh Tessellation
* \{ */
/**
* \param face_normal: This will be optimized out as a constant.
*/
BLI_INLINE void bmesh_calc_tessellation_for_face_impl(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p,
const bool face_normal)
{
#ifndef NDEBUG
/* The face normal is used for projecting faces into 2D space for tessellation.
* Invalid normals may result in invalid tessellation.
* Either `face_normal` should be true or normals should be updated first. */
BLI_assert(face_normal || BM_face_is_normal_valid(efa));
#endif
switch (efa->len) {
case 3: {
/* `0 1 2` -> `0 1 2` */
BMLoop *l;
BMLoop **l_ptr = looptris[0].data();
l_ptr[0] = l = BM_FACE_FIRST_LOOP(efa);
l_ptr[1] = l = l->next;
l_ptr[2] = l->next;
if (face_normal) {
normal_tri_v3(efa->no, l_ptr[0]->v->co, l_ptr[1]->v->co, l_ptr[2]->v->co);
}
break;
}
case 4: {
/* `0 1 2 3` -> (`0 1 2`, `0 2 3`) */
BMLoop *l;
BMLoop **l_ptr_a = looptris[0].data();
BMLoop **l_ptr_b = looptris[1].data();
(l_ptr_a[0] = l_ptr_b[0] = l = BM_FACE_FIRST_LOOP(efa));
(l_ptr_a[1] = l = l->next);
(l_ptr_a[2] = l_ptr_b[1] = l = l->next);
(l_ptr_b[2] = l->next);
if (face_normal) {
normal_quad_v3(
efa->no, l_ptr_a[0]->v->co, l_ptr_a[1]->v->co, l_ptr_a[2]->v->co, l_ptr_b[2]->v->co);
}
if (UNLIKELY(is_quad_flip_v3_first_third_fast(
l_ptr_a[0]->v->co, l_ptr_a[1]->v->co, l_ptr_a[2]->v->co, l_ptr_b[2]->v->co)))
{
/* Flip out of degenerate 0-2 state. */
l_ptr_a[2] = l_ptr_b[2];
l_ptr_b[0] = l_ptr_a[1];
}
break;
}
default: {
if (face_normal) {
BM_face_calc_normal(efa, efa->no);
}
BMLoop *l_iter, *l_first;
BMLoop **l_arr;
float axis_mat[3][3];
float (*projverts)[2];
uint(*tris)[3];
const int tris_len = efa->len - 2;
MemArena *pf_arena = *pf_arena_p;
if (UNLIKELY(pf_arena == nullptr)) {
pf_arena = *pf_arena_p = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
}
tris = static_cast<uint(*)[3]>(BLI_memarena_alloc(pf_arena, sizeof(*tris) * tris_len));
l_arr = static_cast<BMLoop **>(BLI_memarena_alloc(pf_arena, sizeof(*l_arr) * efa->len));
projverts = static_cast<float (*)[2]>(
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * efa->len));
axis_dominant_v3_to_m3_negate(axis_mat, efa->no);
int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
l_arr[i] = l_iter;
mul_v2_m3v3(projverts[i], axis_mat, l_iter->v->co);
i++;
} while ((l_iter = l_iter->next) != l_first);
BLI_polyfill_calc_arena(projverts, efa->len, 1, tris, pf_arena);
for (i = 0; i < tris_len; i++) {
BMLoop **l_ptr = looptris[i].data();
uint *tri = tris[i];
l_ptr[0] = l_arr[tri[0]];
l_ptr[1] = l_arr[tri[1]];
l_ptr[2] = l_arr[tri[2]];
}
BLI_memarena_clear(pf_arena);
break;
}
}
}
static void bmesh_calc_tessellation_for_face(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p)
{
bmesh_calc_tessellation_for_face_impl(looptris, efa, pf_arena_p, false);
}
static void bmesh_calc_tessellation_for_face_with_normal(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p)
{
bmesh_calc_tessellation_for_face_impl(looptris, efa, pf_arena_p, true);
}
/**
* \brief BM_mesh_calc_tessellation get the looptris and its number from a certain bmesh
* \param looptris:
*
* \note \a looptris Must be pre-allocated to at least the size of given by: poly_to_tri_count
*/
static void bm_mesh_calc_tessellation__single_threaded(
BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris, const char face_normals)
{
#ifndef NDEBUG
const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
#endif
BMIter iter;
BMFace *efa;
int i = 0;
MemArena *pf_arena = nullptr;
if (face_normals) {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BLI_assert(efa->len >= 3);
BM_face_calc_normal(efa, efa->no);
bmesh_calc_tessellation_for_face_with_normal(looptris.data() + i, efa, &pf_arena);
i += efa->len - 2;
}
}
else {
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BLI_assert(efa->len >= 3);
bmesh_calc_tessellation_for_face(looptris.data() + i, efa, &pf_arena);
i += efa->len - 2;
}
}
if (pf_arena) {
BLI_memarena_free(pf_arena);
pf_arena = nullptr;
}
BLI_assert(i <= looptris_tot);
}
struct TessellationUserTLS {
MemArena *pf_arena;
};
static void bmesh_calc_tessellation_for_face_fn(void *__restrict userdata,
MempoolIterData *mp_f,
const TaskParallelTLS *__restrict tls)
{
TessellationUserTLS *tls_data = static_cast<TessellationUserTLS *>(tls->userdata_chunk);
std::array<BMLoop *, 3> *looptris = static_cast<std::array<BMLoop *, 3> *>(userdata);
BMFace *f = reinterpret_cast<BMFace *>(mp_f);
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face(looptris + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_with_normals_fn(void *__restrict userdata,
MempoolIterData *mp_f,
const TaskParallelTLS *__restrict tls)
{
TessellationUserTLS *tls_data = static_cast<TessellationUserTLS *>(tls->userdata_chunk);
std::array<BMLoop *, 3> *looptris = static_cast<std::array<BMLoop *, 3> *>(userdata);
BMFace *f = reinterpret_cast<BMFace *>(mp_f);
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face_with_normal(looptris + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_free_fn(const void *__restrict /*userdata*/,
void *__restrict tls_v)
{
TessellationUserTLS *tls_data = static_cast<TessellationUserTLS *>(tls_v);
if (tls_data->pf_arena) {
BLI_memarena_free(tls_data->pf_arena);
}
}
static void bm_mesh_calc_tessellation__multi_threaded(
BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris, const char face_normals)
{
BM_mesh_elem_index_ensure(bm, BM_LOOP | BM_FACE);
TaskParallelSettings settings;
TessellationUserTLS tls_dummy = {nullptr};
BLI_parallel_mempool_settings_defaults(&settings);
settings.userdata_chunk = &tls_dummy;
settings.userdata_chunk_size = sizeof(tls_dummy);
settings.func_free = bmesh_calc_tessellation_for_face_free_fn;
BM_iter_parallel(bm,
BM_FACES_OF_MESH,
face_normals ? bmesh_calc_tessellation_for_face_with_normals_fn :
bmesh_calc_tessellation_for_face_fn,
looptris.data(),
&settings);
}
void BM_mesh_calc_tessellation_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMeshCalcTessellation_Params *params)
{
if (bm->totface < BM_FACE_TESSELLATE_THREADED_LIMIT) {
bm_mesh_calc_tessellation__single_threaded(bm, looptris, params->face_normals);
}
else {
bm_mesh_calc_tessellation__multi_threaded(bm, looptris, params->face_normals);
}
}
void BM_mesh_calc_tessellation(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris)
{
BMeshCalcTessellation_Params params{};
params.face_normals = false;
BM_mesh_calc_tessellation_ex(bm, looptris, &params);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Default Tessellation (Partial Updates)
* \{ */
struct PartialTessellationUserData {
BMFace *const *faces;
MutableSpan<std::array<BMLoop *, 3>> looptris;
};
struct PartialTessellationUserTLS {
MemArena *pf_arena;
};
static void bmesh_calc_tessellation_for_face_partial_fn(void *__restrict userdata,
const int index,
const TaskParallelTLS *__restrict tls)
{
PartialTessellationUserTLS *tls_data = static_cast<PartialTessellationUserTLS *>(
tls->userdata_chunk);
PartialTessellationUserData *data = static_cast<PartialTessellationUserData *>(userdata);
BMFace *f = data->faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face(data->looptris.data() + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_partial_with_normals_fn(
void *__restrict userdata, const int index, const TaskParallelTLS *__restrict tls)
{
PartialTessellationUserTLS *tls_data = static_cast<PartialTessellationUserTLS *>(
tls->userdata_chunk);
PartialTessellationUserData *data = static_cast<PartialTessellationUserData *>(userdata);
BMFace *f = data->faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face_with_normal(
data->looptris.data() + offset, f, &tls_data->pf_arena);
}
static void bmesh_calc_tessellation_for_face_partial_free_fn(const void *__restrict /*userdata*/,
void *__restrict tls_v)
{
PartialTessellationUserTLS *tls_data = static_cast<PartialTessellationUserTLS *>(tls_v);
if (tls_data->pf_arena) {
BLI_memarena_free(tls_data->pf_arena);
}
}
static void bm_mesh_calc_tessellation_with_partial__multi_threaded(
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params)
{
const int faces_len = bmpinfo->faces.size();
BMFace *const *faces = bmpinfo->faces.data();
PartialTessellationUserData data{};
data.faces = faces;
data.looptris = looptris;
PartialTessellationUserTLS tls_dummy = {nullptr};
TaskParallelSettings settings;
BLI_parallel_range_settings_defaults(&settings);
settings.use_threading = true;
settings.userdata_chunk = &tls_dummy;
settings.userdata_chunk_size = sizeof(tls_dummy);
settings.func_free = bmesh_calc_tessellation_for_face_partial_free_fn;
BLI_task_parallel_range(0,
faces_len,
&data,
params->face_normals ?
bmesh_calc_tessellation_for_face_partial_with_normals_fn :
bmesh_calc_tessellation_for_face_partial_fn,
&settings);
}
static void bm_mesh_calc_tessellation_with_partial__single_threaded(
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params)
{
const int faces_len = bmpinfo->faces.size();
BMFace *const *faces = bmpinfo->faces.data();
MemArena *pf_arena = nullptr;
if (params->face_normals) {
for (int index = 0; index < faces_len; index++) {
BMFace *f = faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face_with_normal(looptris.data() + offset, f, &pf_arena);
}
}
else {
for (int index = 0; index < faces_len; index++) {
BMFace *f = faces[index];
BMLoop *l = BM_FACE_FIRST_LOOP(f);
const int offset = BM_elem_index_get(l) - (BM_elem_index_get(f) * 2);
bmesh_calc_tessellation_for_face(looptris.data() + offset, f, &pf_arena);
}
}
if (pf_arena) {
BLI_memarena_free(pf_arena);
}
}
void BM_mesh_calc_tessellation_with_partial_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params)
{
BLI_assert(bmpinfo->params.do_tessellate);
/* While harmless, exit early if there is nothing to do (avoids ensuring the index). */
if (UNLIKELY(bmpinfo->faces.is_empty())) {
return;
}
BM_mesh_elem_index_ensure(bm, BM_LOOP | BM_FACE);
if (bmpinfo->faces.size() < BM_FACE_TESSELLATE_THREADED_LIMIT) {
bm_mesh_calc_tessellation_with_partial__single_threaded(looptris, bmpinfo, params);
}
else {
bm_mesh_calc_tessellation_with_partial__multi_threaded(looptris, bmpinfo, params);
}
}
void BM_mesh_calc_tessellation_with_partial(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo)
{
BM_mesh_calc_tessellation_with_partial_ex(bm, looptris, bmpinfo, nullptr);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Beauty Mesh Tessellation
*
* Avoid degenerate triangles.
* \{ */
static int bmesh_calc_tessellation_for_face_beauty(std::array<BMLoop *, 3> *looptris,
BMFace *efa,
MemArena **pf_arena_p,
Heap **pf_heap_p)
{
switch (efa->len) {
case 3: {
BMLoop *l;
BMLoop **l_ptr = looptris[0].data();
l_ptr[0] = l = BM_FACE_FIRST_LOOP(efa);
l_ptr[1] = l = l->next;
l_ptr[2] = l->next;
return 1;
}
case 4: {
BMLoop *l_v1 = BM_FACE_FIRST_LOOP(efa);
BMLoop *l_v2 = l_v1->next;
BMLoop *l_v3 = l_v2->next;
BMLoop *l_v4 = l_v1->prev;
/* #BM_verts_calc_rotate_beauty performs excessive checks we don't need!
* It's meant for rotating edges, it also calculates a new normal.
*
* Use #BLI_polyfill_beautify_quad_rotate_calc since we have the normal.
*/
#if 0
const bool split_13 = (BM_verts_calc_rotate_beauty(
l_v1->v, l_v2->v, l_v3->v, l_v4->v, 0, 0) < 0.0f);
#else
float axis_mat[3][3], v_quad[4][2];
axis_dominant_v3_to_m3(axis_mat, efa->no);
mul_v2_m3v3(v_quad[0], axis_mat, l_v1->v->co);
mul_v2_m3v3(v_quad[1], axis_mat, l_v2->v->co);
mul_v2_m3v3(v_quad[2], axis_mat, l_v3->v->co);
mul_v2_m3v3(v_quad[3], axis_mat, l_v4->v->co);
const bool split_13 = BLI_polyfill_beautify_quad_rotate_calc(
v_quad[0], v_quad[1], v_quad[2], v_quad[3]) < 0.0f;
#endif
BMLoop **l_ptr_a = looptris[0].data();
BMLoop **l_ptr_b = looptris[1].data();
if (split_13) {
l_ptr_a[0] = l_v1;
l_ptr_a[1] = l_v2;
l_ptr_a[2] = l_v3;
l_ptr_b[0] = l_v1;
l_ptr_b[1] = l_v3;
l_ptr_b[2] = l_v4;
}
else {
l_ptr_a[0] = l_v1;
l_ptr_a[1] = l_v2;
l_ptr_a[2] = l_v4;
l_ptr_b[0] = l_v2;
l_ptr_b[1] = l_v3;
l_ptr_b[2] = l_v4;
}
return 2;
}
default: {
MemArena *pf_arena = *pf_arena_p;
Heap *pf_heap = *pf_heap_p;
if (UNLIKELY(pf_arena == nullptr)) {
pf_arena = *pf_arena_p = BLI_memarena_new(BLI_MEMARENA_STD_BUFSIZE, __func__);
pf_heap = *pf_heap_p = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
}
BMLoop *l_iter, *l_first;
BMLoop **l_arr;
float axis_mat[3][3];
float (*projverts)[2];
uint(*tris)[3];
const int tris_len = efa->len - 2;
tris = static_cast<uint(*)[3]>(BLI_memarena_alloc(pf_arena, sizeof(*tris) * tris_len));
l_arr = static_cast<BMLoop **>(BLI_memarena_alloc(pf_arena, sizeof(*l_arr) * efa->len));
projverts = static_cast<float (*)[2]>(
BLI_memarena_alloc(pf_arena, sizeof(*projverts) * efa->len));
axis_dominant_v3_to_m3_negate(axis_mat, efa->no);
int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(efa);
do {
l_arr[i] = l_iter;
mul_v2_m3v3(projverts[i], axis_mat, l_iter->v->co);
i++;
} while ((l_iter = l_iter->next) != l_first);
BLI_polyfill_calc_arena(projverts, efa->len, 1, tris, pf_arena);
BLI_polyfill_beautify(projverts, efa->len, tris, pf_arena, pf_heap);
for (i = 0; i < tris_len; i++) {
BMLoop **l_ptr = looptris[i].data();
uint *tri = tris[i];
l_ptr[0] = l_arr[tri[0]];
l_ptr[1] = l_arr[tri[1]];
l_ptr[2] = l_arr[tri[2]];
}
BLI_memarena_clear(pf_arena);
return tris_len;
}
}
}
void BM_mesh_calc_tessellation_beauty(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris)
{
#ifndef NDEBUG
const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
#endif
BMIter iter;
BMFace *efa;
int i = 0;
MemArena *pf_arena = nullptr;
/* use_beauty */
Heap *pf_heap = nullptr;
BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
BLI_assert(efa->len >= 3);
i += bmesh_calc_tessellation_for_face_beauty(looptris.data() + i, efa, &pf_arena, &pf_heap);
}
if (pf_arena) {
BLI_memarena_free(pf_arena);
BLI_heap_free(pf_heap, nullptr);
}
BLI_assert(i <= looptris_tot);
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,45 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_span.hh"
#include "bmesh_class.hh"
namespace blender {
struct BMPartialUpdate;
struct BMeshCalcTessellation_Params {
/**
* When calculating normals as well as tessellation, calculate normals after tessellation
* for improved performance. See #BMeshCalcTessellation_Params
*/
bool face_normals;
};
void BM_mesh_calc_tessellation_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMeshCalcTessellation_Params *params);
void BM_mesh_calc_tessellation(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris);
/**
* A version of #BM_mesh_calc_tessellation that avoids degenerate triangles.
*/
void BM_mesh_calc_tessellation_beauty(BMesh *bm, MutableSpan<std::array<BMLoop *, 3>> looptris);
void BM_mesh_calc_tessellation_with_partial_ex(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo,
const BMeshCalcTessellation_Params *params);
void BM_mesh_calc_tessellation_with_partial(BMesh *bm,
MutableSpan<std::array<BMLoop *, 3>> looptris,
const BMPartialUpdate *bmpinfo);
} // namespace blender

View File

@@ -0,0 +1,251 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BM mesh validation function.
*/
/* debug builds only */
#ifndef NDEBUG
# include "BLI_map.hh"
# include "BLI_ordered_edge.hh"
# include "BLI_set.hh"
# include "BLI_utildefines.h"
# include "bmesh.hh"
# include "bmesh_mesh_validate.hh"
namespace blender {
/* macro which inserts the function name */
# if defined __GNUC__
# define ERRMSG(format, args...) \
{ \
fprintf(stderr, "%s: " format ", " AT "\n", __func__, ##args); \
errtot++; \
} \
(void)0
# elif defined(_MSVC_TRADITIONAL) && !_MSVC_TRADITIONAL
# define ERRMSG(format, ...) \
{ \
fprintf(stderr, "%s: " format ", " AT "\n", __func__, ##__VA_ARGS__); \
errtot++; \
} \
(void)0
# else
# define ERRMSG(format, ...) \
{ \
fprintf(stderr, "%s: " format ", " AT "\n", __func__, __VA_ARGS__); \
errtot++; \
} \
(void)0
# endif
template<> struct DefaultHash<Set<Vector<int>>> {
uint64_t operator()(const Vector<int> &value) const
{
uint64_t hash = 0;
for (const int v : value) {
hash = get_default_hash(hash, v);
}
return hash;
}
};
bool BM_mesh_is_valid(BMesh *bm)
{
Map<OrderedEdge, BMEdge *> edge_hash;
edge_hash.reserve(bm->totedge);
int errtot;
BMIter iter;
BMVert *v;
BMEdge *e;
BMFace *f;
int i, j;
errtot = -1; /* 'ERRMSG' next line will set at zero */
fprintf(stderr, "\n");
ERRMSG("This is a debugging function and not intended for general use, running slow test!");
/* force recalc, even if tagged as valid, since this mesh is suspect! */
bm->elem_index_dirty |= BM_ALL;
BM_mesh_elem_index_ensure(bm, BM_ALL);
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
if (BM_elem_flag_test(v, BM_ELEM_SELECT | BM_ELEM_HIDDEN) == (BM_ELEM_SELECT | BM_ELEM_HIDDEN))
{
ERRMSG("vert %d: is hidden and selected", i);
}
if (v->e) {
if (!BM_vert_in_edge(v->e, v)) {
ERRMSG("vert %d: is not in its referenced edge: %d", i, BM_elem_index_get(v->e));
}
}
}
/* check edges */
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
if (e->v1 == e->v2) {
ERRMSG("edge %d: duplicate index: %d", i, BM_elem_index_get(e->v1));
}
/* Build edge-hash at the same time. */
edge_hash.add_or_modify(
{BM_elem_index_get(e->v1), BM_elem_index_get(e->v2)},
[&](BMEdge **value) { *value = e; },
[&](BMEdge **value) {
ERRMSG("edge %d, %d: are duplicates", i, BM_elem_index_get(*value));
});
}
/* edge radial structure */
BM_ITER_MESH_INDEX (e, &iter, bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT | BM_ELEM_HIDDEN) == (BM_ELEM_SELECT | BM_ELEM_HIDDEN))
{
ERRMSG("edge %d: is hidden and selected", i);
}
if (e->l) {
BMLoop *l_iter;
BMLoop *l_first;
j = 0;
l_iter = l_first = e->l;
/* we could do more checks here, but save for face checks */
do {
if (l_iter->e != e) {
ERRMSG("edge %d: has invalid loop, loop is of face %d", i, BM_elem_index_get(l_iter->f));
}
else if (BM_vert_in_edge(e, l_iter->v) == false) {
ERRMSG("edge %d: has invalid loop with vert not in edge, loop is of face %d",
i,
BM_elem_index_get(l_iter->f));
}
else if (BM_vert_in_edge(e, l_iter->next->v) == false) {
ERRMSG("edge %d: has invalid loop with next vert not in edge, loop is of face %d",
i,
BM_elem_index_get(l_iter->f));
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
/* face structure */
Map<Vector<int>, int> face_map;
BM_ITER_MESH_INDEX (f, &iter, bm, BM_FACES_OF_MESH, i) {
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
if (BM_elem_flag_test(f, BM_ELEM_SELECT | BM_ELEM_HIDDEN) == (BM_ELEM_SELECT | BM_ELEM_HIDDEN))
{
ERRMSG("face %d: is hidden and selected", i);
}
j = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BM_elem_flag_disable(l_iter, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->v, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->e, BM_ELEM_INTERNAL_TAG);
j++;
} while ((l_iter = l_iter->next) != l_first);
const int f_len = j;
if (f_len != f->len) {
ERRMSG("face %d: has length of %d but should be %d", i, f->len, f_len);
}
/* Store the loop with the minimum index to create a list of vertex indices. */
BMLoop *l_vert_min = l_first;
j = 0;
l_iter = l_first;
do {
if (BM_elem_flag_test(l_iter, BM_ELEM_INTERNAL_TAG)) {
ERRMSG("face %d: has duplicate loop at corner: %d", i, j);
}
if (BM_elem_flag_test(l_iter->v, BM_ELEM_INTERNAL_TAG)) {
ERRMSG(
"face %d: has duplicate vert: %d, at corner: %d", i, BM_elem_index_get(l_iter->v), j);
}
if (BM_elem_flag_test(l_iter->e, BM_ELEM_INTERNAL_TAG)) {
ERRMSG(
"face %d: has duplicate edge: %d, at corner: %d", i, BM_elem_index_get(l_iter->e), j);
}
/* adjacent data checks */
if (l_iter->f != f) {
ERRMSG("face %d: has loop that points to face: %d at corner: %d",
i,
BM_elem_index_get(l_iter->f),
j);
}
if (l_iter != l_iter->prev->next) {
ERRMSG("face %d: has invalid 'prev/next' at corner: %d", i, j);
}
if (l_iter != l_iter->next->prev) {
ERRMSG("face %d: has invalid 'next/prev' at corner: %d", i, j);
}
if (l_iter != l_iter->radial_prev->radial_next) {
ERRMSG("face %d: has invalid 'radial_prev/radial_next' at corner: %d", i, j);
}
if (l_iter != l_iter->radial_next->radial_prev) {
ERRMSG("face %d: has invalid 'radial_next/radial_prev' at corner: %d", i, j);
}
BM_elem_flag_enable(l_iter, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(l_iter->v, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_enable(l_iter->e, BM_ELEM_INTERNAL_TAG);
if (BM_elem_index_get(l_iter->v) < BM_elem_index_get(l_vert_min->v)) {
l_vert_min = l_iter;
}
j++;
} while ((l_iter = l_iter->next) != l_first);
/* Store ordered face verts, walking over the lowest index first
* so faces with flipped winding still match. */
Vector<int> face_verts;
face_verts.reserve(f_len);
if (BM_elem_index_get(l_vert_min->next->v) < BM_elem_index_get(l_vert_min->prev->v)) {
l_iter = l_vert_min;
do {
face_verts.append_unchecked(BM_elem_index_get(l_iter->v));
} while ((l_iter = l_iter->next) != l_vert_min);
}
else {
l_iter = l_vert_min;
do {
face_verts.append_unchecked(BM_elem_index_get(l_iter->v));
} while ((l_iter = l_iter->prev) != l_vert_min);
}
face_map.add_or_modify(
std::move(face_verts),
[&](int *value) { *value = i; },
[&](const int *value) { ERRMSG("face %d: duplicate of %d", i, *value); });
/* leave elements un-tagged, not essential but nice to avoid unintended dirty tag use later. */
do {
BM_elem_flag_disable(l_iter, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->v, BM_ELEM_INTERNAL_TAG);
BM_elem_flag_disable(l_iter->e, BM_ELEM_INTERNAL_TAG);
} while ((l_iter = l_iter->next) != l_first);
}
const bool is_valid = (errtot == 0);
ERRMSG("Finished - errors %d", errtot);
return is_valid;
}
} // namespace blender
#endif

View File

@@ -0,0 +1,23 @@
/* SPDX-FileCopyrightText: 2012 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
namespace blender {
/**
* Check of this #BMesh is valid,
* this function can be slow since its intended to help with debugging.
*
* \return true when the mesh is valid.
*/
bool BM_mesh_is_valid(BMesh *bm);
} // namespace blender

View File

@@ -0,0 +1,903 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* This file contains functions for locally modifying
* the topology of existing mesh data. (split, join, flip etc).
*/
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_private.hh"
namespace blender {
bool BM_vert_dissolve(BMesh *bm, BMVert *v)
{
/* logic for 3 or more is identical */
const int len = BM_vert_edge_count_at_most(v, 3);
if (len == 1) {
BM_vert_kill(bm, v); /* will kill edges too */
return true;
}
if (!BM_vert_is_manifold(v)) {
if (!v->e) {
BM_vert_kill(bm, v);
return true;
}
if (!v->e->l) {
if (len == 2) {
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != nullptr);
}
/* used to kill the vertex here, but it may be connected to faces.
* so better do nothing */
return false;
}
return false;
}
if (len == 2 && BM_vert_face_count_is_equal(v, 1)) {
/* boundary vertex on a face */
return (BM_vert_collapse_edge(bm, v->e, v, true, true, true) != nullptr);
}
return BM_disk_dissolve(bm, v);
}
bool BM_disk_dissolve(BMesh *bm, BMVert *v)
{
BMEdge *e, *keepedge = nullptr, *baseedge = nullptr;
int len = 0;
if (!BM_vert_is_manifold(v)) {
return false;
}
if (v->e) {
/* v->e we keep, what else */
e = v->e;
do {
e = bmesh_disk_edge_next(e, v);
if (!BM_edge_share_face_check(e, v->e)) {
keepedge = e;
baseedge = v->e;
break;
}
len++;
} while (e != v->e);
}
/* this code for handling 2 and 3-valence verts
* may be totally bad */
if (keepedge == nullptr && len == 3) {
#if 0
/* handle specific case for three-valence. solve it by
* increasing valence to four. this may be hackish. */
BMLoop *l_a = BM_face_vert_share_loop(e->l->f, v);
BMLoop *l_b = (e->l->v == v) ? e->l->next : e->l;
if (!BM_face_split(bm, e->l->f, l_a, l_b, nullptr, nullptr, false)) {
return false;
}
if (!BM_disk_dissolve(bm, v)) {
return false;
}
#else
BMFace *f_double;
if (UNLIKELY(!BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double))) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
if (UNLIKELY(!BM_vert_collapse_faces(bm, v->e, v, 1.0, true, false, true, true))) {
return false;
}
#endif
return true;
}
if (keepedge == nullptr && len == 2) {
/* collapse the vertex */
e = BM_vert_collapse_faces(bm, v->e, v, 1.0, true, true, true, true);
if (!e) {
return false;
}
/* handle two-valence */
if (e->l != e->l->radial_next) {
BMFace *f_double;
if (!BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double)) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
}
return true;
}
if (keepedge) {
bool done = false;
while (!done) {
done = true;
e = v->e;
do {
BMFace *f = nullptr;
if (BM_edge_is_manifold(e) && (e != baseedge) && (e != keepedge)) {
BMFace *f_double;
f = BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double);
/* return if couldn't join faces in manifold
* conditions */
/* !disabled for testing why bad things happen */
if (!f) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
}
if (f) {
done = false;
break;
}
} while ((e = bmesh_disk_edge_next(e, v)) != v->e);
}
/* collapse the vertex */
/* NOTE: the baseedge can be a boundary of manifold, use this as join_faces arg. */
e = BM_vert_collapse_faces(
bm, baseedge, v, 1.0, true, !BM_edge_is_boundary(baseedge), true, true);
if (!e) {
return false;
}
if (e->l) {
/* get remaining two faces */
if (e->l != e->l->radial_next) {
BMFace *f_double;
/* join two remaining faces */
if (!BM_faces_join_pair(bm, e->l, e->l->radial_next, true, &f_double)) {
return false;
}
/* See #BM_faces_join note on callers asserting when `r_double` is non-null. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
}
}
}
return true;
}
BMFace *BM_faces_join_pair(
BMesh *bm, BMLoop *l_a, BMLoop *l_b, const bool do_del, BMFace **r_double)
{
BLI_assert((l_a != l_b) && (l_a->e == l_b->e));
if (l_a->v == l_b->v) {
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
bmesh_kernel_loop_reverse(bm, l_b->f, cd_loop_mdisp_offset, true);
}
BMFace *faces[2] = {l_a->f, l_b->f};
return BM_faces_join(bm, faces, 2, do_del, r_double);
}
BMFace *BM_face_split(BMesh *bm,
BMFace *f,
BMLoop *l_a,
BMLoop *l_b,
BMLoop **r_l,
BMEdge *example,
const bool no_double)
{
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
BMFace *f_new, *f_tmp;
BLI_assert(l_a != l_b);
BLI_assert(f == l_a->f && f == l_b->f);
BLI_assert(!BM_loop_is_adjacent(l_a, l_b));
/* could be an assert */
if (UNLIKELY(BM_loop_is_adjacent(l_a, l_b)) || UNLIKELY(f != l_a->f || f != l_b->f)) {
if (r_l) {
*r_l = nullptr;
}
return nullptr;
}
/* do we have a multires layer? */
if (cd_loop_mdisp_offset != -1) {
f_tmp = BM_face_copy(bm, f, false, false);
}
#ifdef USE_BMESH_HOLES
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, r_l, nullptr, example, no_double);
#else
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, r_l, example, no_double);
#endif
if (f_new) {
/* handle multires update */
if (cd_loop_mdisp_offset != -1) {
float f_dst_center[3];
float f_src_center[3];
BM_face_calc_center_median(f_tmp, f_src_center);
BM_face_calc_center_median(f, f_dst_center);
BM_face_interp_multires_ex(bm, f, f_tmp, f_dst_center, f_src_center, cd_loop_mdisp_offset);
BM_face_calc_center_median(f_new, f_dst_center);
BM_face_interp_multires_ex(
bm, f_new, f_tmp, f_dst_center, f_src_center, cd_loop_mdisp_offset);
#if 0
/* BM_face_multires_bounds_smooth doesn't flip displacement correct */
BM_face_multires_bounds_smooth(bm, f);
BM_face_multires_bounds_smooth(bm, f_new);
#endif
}
}
if (cd_loop_mdisp_offset != -1) {
BM_face_kill(bm, f_tmp);
}
return f_new;
}
BMFace *BM_face_split_n(BMesh *bm,
BMFace *f,
BMLoop *l_a,
BMLoop *l_b,
float cos[][3],
int n,
BMLoop **r_l,
BMEdge *example)
{
BMFace *f_new, *f_tmp;
BMLoop *l_new;
BMEdge *e, *e_new;
BMVert *v_new;
// BMVert *v_a = l_a->v; /* UNUSED */
BMVert *v_b = l_b->v;
int i, j;
BLI_assert(l_a != l_b);
BLI_assert(f == l_a->f && f == l_b->f);
BLI_assert(!((n == 0) && BM_loop_is_adjacent(l_a, l_b)));
/* could be an assert */
if (UNLIKELY((n == 0) && BM_loop_is_adjacent(l_a, l_b)) || UNLIKELY(l_a->f != l_b->f)) {
if (r_l) {
*r_l = nullptr;
}
return nullptr;
}
f_tmp = BM_face_copy(bm, f, true, true);
#ifdef USE_BMESH_HOLES
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, &l_new, nullptr, example, false);
#else
f_new = bmesh_kernel_split_face_make_edge(bm, f, l_a, l_b, &l_new, example, false);
#endif
/* bmesh_kernel_split_face_make_edge returns in 'l_new'
* a Loop for f_new going from 'v_a' to 'v_b'.
* The radial_next is for 'f' and goes from 'v_b' to 'v_a'. */
if (f_new) {
e = l_new->e;
for (i = 0; i < n; i++) {
v_new = bmesh_kernel_split_edge_make_vert(bm, v_b, e, &e_new);
BLI_assert(v_new != nullptr);
/* bmesh_kernel_split_edge_make_vert returns in 'e_new'
* the edge going from 'v_new' to 'v_b'. */
copy_v3_v3(v_new->co, cos[i]);
/* interpolate the loop data for the loops with (v == v_new), using orig face */
for (j = 0; j < 2; j++) {
BMEdge *e_iter = (j == 0) ? e : e_new;
BMLoop *l_iter = e_iter->l;
do {
if (l_iter->v == v_new) {
/* this interpolates both loop and vertex data */
BM_loop_interp_from_face(bm, l_iter, f_tmp, true, true);
}
} while ((l_iter = l_iter->radial_next) != e_iter->l);
}
e = e_new;
}
}
BM_face_verts_kill(bm, f_tmp);
if (r_l) {
*r_l = l_new;
}
return f_new;
}
BMEdge *BM_vert_collapse_faces(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
float fac,
const bool do_del,
const bool join_faces,
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
BMEdge *e_new = nullptr;
BMVert *tv = BM_edge_other_vert(e_kill, v_kill);
BMEdge *e2;
BMVert *tv2;
/* Only intended to be called for 2-valence vertices */
BLI_assert(bmesh_disk_count(v_kill) <= 2);
/* first modify the face loop data */
if (e_kill->l) {
BMLoop *l_iter;
const float w[2] = {1.0f - fac, fac};
l_iter = e_kill->l;
do {
if (l_iter->v == tv && l_iter->next->v == v_kill) {
const void *src[2];
BMLoop *tvloop = l_iter;
BMLoop *kvloop = l_iter->next;
src[0] = kvloop->head.data;
src[1] = tvloop->head.data;
CustomData_bmesh_interp(&bm->ldata, src, w, 2, kvloop->head.data);
}
} while ((l_iter = l_iter->radial_next) != e_kill->l);
}
/* now interpolate the vertex data */
BM_data_interp_from_verts(bm, v_kill, tv, v_kill, fac);
e2 = bmesh_disk_edge_next(e_kill, v_kill);
tv2 = BM_edge_other_vert(e2, v_kill);
if (join_faces) {
BMIter fiter;
BMFace *f;
Vector<BMFace *, BM_DEFAULT_ITER_STACK_SIZE> faces;
BM_ITER_ELEM (f, &fiter, v_kill, BM_FACES_OF_VERT) {
faces.append(f);
}
if (faces.size() >= 2) {
BMFace *f_double;
if (BMFace *f2 = BM_faces_join(bm, faces.data(), faces.size(), true, &f_double)) {
if (kill_duplicate_faces && (f_double != nullptr)) {
BM_face_kill(bm, f_double);
}
BMLoop *l_a, *l_b;
if ((l_a = BM_face_vert_share_loop(f2, tv)) && (l_b = BM_face_vert_share_loop(f2, tv2))) {
BMLoop *l_new;
if (BM_face_split(bm, f2, l_a, l_b, &l_new, nullptr, false)) {
e_new = l_new->e;
}
}
}
}
}
else {
/* single face or no faces */
/* same as BM_vert_collapse_edge() however we already
* have vars to perform this operation so don't call. */
e_new = bmesh_kernel_join_edge_kill_vert(
bm, e_kill, v_kill, do_del, true, kill_degenerate_faces, kill_duplicate_faces);
// e_new = BM_edge_exists(tv, tv2); /* Same as return above. */
}
return e_new;
}
BMEdge *BM_vert_collapse_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
const bool do_del,
const bool kill_degenerate_faces,
const bool kill_duplicate_faces)
{
/* nice example implementation but we want loops to have their customdata
* accounted for */
#if 0
BMEdge *e_new = nullptr;
/* Collapse between 2 edges */
/* in this case we want to keep all faces and not join them,
* rather just get rid of the vertex - see bug #28645. */
BMVert *tv = BM_edge_other_vert(e_kill, v_kill);
if (tv) {
BMEdge *e2 = bmesh_disk_edge_next(e_kill, v_kill);
if (e2) {
BMVert *tv2 = BM_edge_other_vert(e2, v_kill);
if (tv2) {
/* only action, other calls here only get the edge to return */
e_new = bmesh_kernel_join_edge_kill_vert(
bm, e_kill, v_kill, do_del, true, kill_degenerate_faces);
}
}
}
return e_new;
#else
/* with these args faces are never joined, same as above
* but account for loop customdata */
return BM_vert_collapse_faces(
bm, e_kill, v_kill, 1.0f, do_del, false, kill_degenerate_faces, kill_duplicate_faces);
#endif
}
#undef DO_V_INTERP
BMVert *BM_edge_collapse(
BMesh *bm, BMEdge *e_kill, BMVert *v_kill, const bool do_del, const bool kill_degenerate_faces)
{
return bmesh_kernel_join_vert_kill_edge(bm, e_kill, v_kill, do_del, true, kill_degenerate_faces);
}
BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac)
{
BMVert *v_new, *v_other;
BMEdge *e_new;
Vector<BMFace *, 32> oldfaces;
const int cd_loop_mdisp_offset = BM_edge_is_wire(e) ?
-1 :
CustomData_get_offset(&bm->ldata, CD_MDISPS);
BLI_assert(BM_vert_in_edge(e, v) == true);
/* do we have a multi-res layer? */
if (cd_loop_mdisp_offset != -1) {
BMLoop *l = e->l;
do {
oldfaces.append(l->f);
l = l->radial_next;
} while (l != e->l);
/* flag existing faces so we can differentiate oldfaces from new faces */
for (int64_t i = 0; i < oldfaces.size(); i++) {
BM_ELEM_API_FLAG_ENABLE(oldfaces[i], _FLAG_OVERLAP);
oldfaces[i] = BM_face_copy(bm, oldfaces[i], true, true);
BM_ELEM_API_FLAG_DISABLE(oldfaces[i], _FLAG_OVERLAP);
}
}
v_other = BM_edge_other_vert(e, v);
v_new = bmesh_kernel_split_edge_make_vert(bm, v, e, &e_new);
if (r_e != nullptr) {
*r_e = e_new;
}
BLI_assert(v_new != nullptr);
BLI_assert(BM_vert_in_edge(e_new, v) && BM_vert_in_edge(e_new, v_new));
BLI_assert(BM_vert_in_edge(e, v_new) && BM_vert_in_edge(e, v_other));
sub_v3_v3v3(v_new->co, v_other->co, v->co);
madd_v3_v3v3fl(v_new->co, v->co, v_new->co, fac);
e_new->head.hflag = e->head.hflag;
BM_elem_attrs_copy(bm, e, e_new);
/* v->v_new->v2 */
BM_data_interp_face_vert_edge(bm, v_other, v, v_new, e, fac);
BM_data_interp_from_verts(bm, v, v_other, v_new, fac);
if (cd_loop_mdisp_offset != -1) {
/* interpolate new/changed loop data from copied old faces */
for (BMFace *oldface : oldfaces) {
float f_center_old[3];
BM_face_calc_center_median(oldface, f_center_old);
for (int j = 0; j < 2; j++) {
BMEdge *e1 = j ? e_new : e;
BMLoop *l = e1->l;
if (UNLIKELY(!l)) {
BMESH_ASSERT(0);
break;
}
do {
/* check this is an old face */
if (BM_ELEM_API_FLAG_TEST(l->f, _FLAG_OVERLAP)) {
float f_center[3];
BM_face_calc_center_median(l->f, f_center);
BM_face_interp_multires_ex(
bm, l->f, oldface, f_center, f_center_old, cd_loop_mdisp_offset);
}
l = l->radial_next;
} while (l != e1->l);
}
}
/* destroy the old faces */
for (BMFace *oldface : oldfaces) {
BM_face_verts_kill(bm, oldface);
}
/* fix boundaries a bit, doesn't work too well quite yet */
#if 0
for (int j = 0; j < 2; j++) {
BMEdge *e1 = j ? e_new : e;
BMLoop *l, *l2;
l = e1->l;
if (UNLIKELY(!l)) {
BMESH_ASSERT(0);
break;
}
do {
BM_face_multires_bounds_smooth(bm, l->f);
l = l->radial_next;
} while (l != e1->l);
}
#endif
}
return v_new;
}
BMVert *BM_edge_split_n(BMesh *bm, BMEdge *e, int numcuts, BMVert **r_varr)
{
int i;
float percent;
BMVert *v_new = nullptr;
for (i = 0; i < numcuts; i++) {
percent = 1.0f / float(numcuts + 1 - i);
v_new = BM_edge_split(bm, e, e->v2, nullptr, percent);
if (r_varr) {
/* fill in reverse order (v1 -> v2) */
r_varr[numcuts - i - 1] = v_new;
}
}
return v_new;
}
void BM_edge_verts_swap(BMEdge *e)
{
std::swap(e->v1, e->v2);
std::swap(e->v1_disk_link, e->v2_disk_link);
}
bool BM_edge_calc_rotate(BMEdge *e, const bool ccw, BMLoop **r_l1, BMLoop **r_l2)
{
BMVert *v1, *v2;
BMFace *fa, *fb;
/* this should have already run */
BLI_assert(BM_edge_rotate_check(e) == true);
/* we know this will work */
BM_edge_face_pair(e, &fa, &fb);
/* so we can use `ccw` variable correctly,
* otherwise we could use the edges verts direct */
BM_edge_ordered_verts(e, &v1, &v2);
/* we could swap the verts _or_ the faces, swapping faces
* gives more predictable results since that way the next vert
* just stitches from face fa / fb */
if (!ccw) {
std::swap(fa, fb);
}
BMLoop *l1 = BM_face_other_vert_loop(fb, v2, v1);
BMLoop *l2 = BM_face_other_vert_loop(fa, v1, v2);
/* This occurs when faces share multiple edges next to `e`.
* While rare it's not an error, this rotation must be skipped. */
if (l1->v == l2->v) [[unlikely]] {
return false;
}
*r_l1 = l1;
*r_l2 = l2;
return true;
}
bool BM_edge_rotate_check(BMEdge *e)
{
BMFace *fa, *fb;
if (BM_edge_face_pair(e, &fa, &fb)) {
BMLoop *la, *lb;
la = BM_face_other_vert_loop(fa, e->v2, e->v1);
lb = BM_face_other_vert_loop(fb, e->v2, e->v1);
/* check that the next vert in both faces isn't the same
* (ie - the next edge doesn't share the same faces).
* since we can't rotate usefully in this case. */
if (la->v == lb->v) {
return false;
}
/* mirror of the check above but in the opposite direction */
la = BM_face_other_vert_loop(fa, e->v1, e->v2);
lb = BM_face_other_vert_loop(fb, e->v1, e->v2);
if (la->v == lb->v) {
return false;
}
return true;
}
return false;
}
bool BM_edge_rotate_check_degenerate(BMEdge *e, BMLoop *l1, BMLoop *l2)
{
/* NOTE: for these vars 'old' just means initial edge state. */
float ed_dir_old[3]; /* edge vector */
float ed_dir_new[3]; /* edge vector */
float ed_dir_new_flip[3]; /* edge vector */
float ed_dir_v1_old[3];
float ed_dir_v2_old[3];
float ed_dir_v1_new[3];
float ed_dir_v2_new[3];
float cross_old[3];
float cross_new[3];
/* original verts - these will be in the edge 'e' */
BMVert *v1_old, *v2_old;
/* verts from the loops passed */
BMVert *v1, *v2;
/* These are the opposite verts - the verts that _would_ be used if `ccw` was inverted. */
BMVert *v1_alt, *v2_alt;
/* this should have already run */
BLI_assert(BM_edge_rotate_check(e) == true);
BM_edge_ordered_verts(e, &v1_old, &v2_old);
v1 = l1->v;
v2 = l2->v;
/* get the next vert along */
v1_alt = BM_face_other_vert_loop(l1->f, v1_old, v1)->v;
v2_alt = BM_face_other_vert_loop(l2->f, v2_old, v2)->v;
/* normalize all so comparisons are scale independent */
BLI_assert(BM_edge_exists(v1_old, v1));
BLI_assert(BM_edge_exists(v1, v1_alt));
BLI_assert(BM_edge_exists(v2_old, v2));
BLI_assert(BM_edge_exists(v2, v2_alt));
/* old and new edge vecs */
sub_v3_v3v3(ed_dir_old, v1_old->co, v2_old->co);
sub_v3_v3v3(ed_dir_new, v1->co, v2->co);
normalize_v3(ed_dir_old);
normalize_v3(ed_dir_new);
/* old edge corner vecs */
sub_v3_v3v3(ed_dir_v1_old, v1_old->co, v1->co);
sub_v3_v3v3(ed_dir_v2_old, v2_old->co, v2->co);
normalize_v3(ed_dir_v1_old);
normalize_v3(ed_dir_v2_old);
/* old edge corner vecs */
sub_v3_v3v3(ed_dir_v1_new, v1->co, v1_alt->co);
sub_v3_v3v3(ed_dir_v2_new, v2->co, v2_alt->co);
normalize_v3(ed_dir_v1_new);
normalize_v3(ed_dir_v2_new);
/* compare */
cross_v3_v3v3(cross_old, ed_dir_old, ed_dir_v1_old);
cross_v3_v3v3(cross_new, ed_dir_new, ed_dir_v1_new);
if (dot_v3v3(cross_old, cross_new) < 0.0f) { /* does this flip? */
return false;
}
cross_v3_v3v3(cross_old, ed_dir_old, ed_dir_v2_old);
cross_v3_v3v3(cross_new, ed_dir_new, ed_dir_v2_new);
if (dot_v3v3(cross_old, cross_new) < 0.0f) { /* does this flip? */
return false;
}
negate_v3_v3(ed_dir_new_flip, ed_dir_new);
/* result is zero area corner */
if ((dot_v3v3(ed_dir_new, ed_dir_v1_new) > 0.999f) ||
(dot_v3v3(ed_dir_new_flip, ed_dir_v2_new) > 0.999f))
{
return false;
}
return true;
}
bool BM_edge_rotate_check_beauty(BMEdge *e, BMLoop *l1, BMLoop *l2)
{
/* Stupid check for now:
* Could compare angles of surrounding edges
* before & after, but this is OK. */
return (len_squared_v3v3(e->v1->co, e->v2->co) > len_squared_v3v3(l1->v->co, l2->v->co));
}
BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, const bool ccw, const short check_flag)
{
BMVert *v1, *v2;
BMLoop *l1, *l2;
BMFace *f;
BMEdge *e_new = nullptr;
char f_active_prev = 0;
char f_hflag_prev_1;
char f_hflag_prev_2;
if (!BM_edge_rotate_check(e)) {
return nullptr;
}
if (!BM_edge_calc_rotate(e, ccw, &l1, &l2)) {
return nullptr;
}
/* the loops will be freed so assign verts */
v1 = l1->v;
v2 = l2->v;
/* --------------------------------------- */
/* Checking Code - make sure we can rotate */
if (check_flag & BM_EDGEROT_CHECK_BEAUTY) {
if (!BM_edge_rotate_check_beauty(e, l1, l2)) {
return nullptr;
}
}
/* check before applying */
if (check_flag & BM_EDGEROT_CHECK_EXISTS) {
if (BM_edge_exists(v1, v2)) {
return nullptr;
}
}
/* slowest, check last */
if (check_flag & BM_EDGEROT_CHECK_DEGENERATE) {
if (!BM_edge_rotate_check_degenerate(e, l1, l2)) {
return nullptr;
}
}
/* Done Checking */
/* ------------- */
/* --------------- */
/* Rotate The Edge */
/* first create the new edge, this is so we can copy the customdata from the old one
* if splice if disabled, always add in a new edge even if there's one there. */
e_new = BM_edge_create(
bm, v1, v2, e, (check_flag & BM_EDGEROT_CHECK_SPLICE) ? BM_CREATE_NO_DOUBLE : BM_CREATE_NOP);
f_hflag_prev_1 = l1->f->head.hflag;
f_hflag_prev_2 = l2->f->head.hflag;
/* maintain active face */
if (bm->act_face == l1->f) {
f_active_prev = 1;
}
else if (bm->act_face == l2->f) {
f_active_prev = 2;
}
const bool is_flipped = !BM_edge_is_contiguous(e);
BMFace *f_double;
/* don't delete the edge, manually remove the edge after so we can copy its attributes */
f = BM_faces_join_pair(
bm, BM_face_edge_share_loop(l1->f, e), BM_face_edge_share_loop(l2->f, e), true, &f_double);
if (f == nullptr) {
return nullptr;
}
/* NOTE: this assumes joining the faces _didnt_ also remove the verts.
* the #BM_edge_rotate_check will ensure this, but its possibly corrupt state or future edits
* break this */
if ((l1 = BM_face_vert_share_loop(f, v1)) && (l2 = BM_face_vert_share_loop(f, v2)) &&
BM_face_split(bm, f, l1, l2, nullptr, nullptr, true))
{
/* we should really be able to know the faces some other way,
* rather than fetching them back from the edge, but this is predictable
* where using the return values from face split isn't. - campbell */
BMFace *fa, *fb;
if (BM_edge_face_pair(e_new, &fa, &fb)) {
fa->head.hflag = f_hflag_prev_1;
fb->head.hflag = f_hflag_prev_2;
if (f_active_prev == 1) {
bm->act_face = fa;
}
else if (f_active_prev == 2) {
bm->act_face = fb;
}
if (is_flipped) {
BM_face_normal_flip(bm, fb);
if (ccw) {
/* Needed otherwise `ccw` toggles direction */
e_new->l = e_new->l->radial_next;
}
}
}
}
else {
/* See #BM_faces_join note on callers asserting when `r_double` is non-null.
* Checked here because a double is acceptable as long as its temporary.
*
* TODO(@ideasman42): To properly solve we'd need to create the 2x faces with edge rotation
* then only delete the original faces once the new faces have been successfully created.
* - Worth looking into. */
BLI_assert_msg(f_double == nullptr,
"Doubled face detected at " AT ". Resulting mesh may be corrupt.");
return nullptr;
}
return e_new;
}
BMVert *BM_face_loop_separate(BMesh *bm, BMLoop *l_sep)
{
return bmesh_kernel_unglue_region_make_vert(bm, l_sep);
}
BMVert *BM_face_loop_separate_multi_isolated(BMesh *bm, BMLoop *l_sep)
{
return bmesh_kernel_unglue_region_make_vert_multi_isolated(bm, l_sep);
}
BMVert *BM_face_loop_separate_multi(BMesh *bm, BMLoop **larr, int larr_len)
{
return bmesh_kernel_unglue_region_make_vert_multi(bm, larr, larr_len);
}
} // namespace blender

View File

@@ -0,0 +1,285 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "bmesh_class.hh"
namespace blender {
/** \file
* \ingroup bmesh
*/
/**
* \brief Dissolve Vert
*
* Turns the face region surrounding a manifold vertex into a single polygon.
*
* \par Example:
* <pre>
* +---------+ +---------+
* | \ / | | |
* Before: | v | After: | |
* | / \ | | |
* +---------+ +---------+
* </pre>
*
* This function can also collapse edges too
* in cases when it can't merge into faces.
*
* \par Example:
* <pre>
* Before: +----v----+ After: +---------+
* </pre>
*
* \note dissolves vert, in more situations than BM_disk_dissolve
* (e.g. if the vert is part of a wire edge, etc).
*/
bool BM_vert_dissolve(BMesh *bm, BMVert *v);
/**
* dissolves all faces around a vert, and removes it.
*/
bool BM_disk_dissolve(BMesh *bm, BMVert *v);
/**
* \brief Faces Join Pair
*
* Joins two adjacent faces together.
*
* \note This method calls to #BM_faces_join to do its work.
* This means connected edges which also share the two faces will be joined.
*
* If the windings do not match the winding of the new face will follow
* \a l_a's winding (i.e. \a l_b will be reversed before the join).
*
* \param bm: The bmesh.
* \param l_a: First loop of an adjacent face pair that will be joined.
* \param l_b: Second loop of an adjacent face pair that will be joined.
* \param do_del: If true, remove the original faces, internal edges,
* and internal verts such that they are replaced by the new face.
* \param r_double: A pointer to a face that controls processing of doubled faces.
* See #BM_faces_join `r_double` argument for details.
*
* \return The combined face or NULL on failure.
*/
BMFace *BM_faces_join_pair(BMesh *bm, BMLoop *l_a, BMLoop *l_b, bool do_del, BMFace **r_double);
/** see: bmesh_polygon_edgenet.hh for #BM_face_split_edgenet */
/**
* \brief Face Split
*
* Split a face along two vertices. returns the newly made face, and sets
* the \a r_l member to a loop in the newly created edge.
*
* \param bm: The bmesh
* \param f: the original face
* \param l_a, l_b: Loops of this face, their vertices define
* the split edge to be created (must be differ and not can't be adjacent in the face).
* \param r_l: pointer which will receive the BMLoop for the split edge in the new face
* \param example: Edge used for attributes of splitting edge, if non-NULL
* \param no_double: Use an existing edge if found
*
* \return Pointer to the newly created face representing one side of the split
* if the split is successful (and the original face will be the other side).
* NULL if the split fails.
*/
BMFace *BM_face_split(
BMesh *bm, BMFace *f, BMLoop *l_a, BMLoop *l_b, BMLoop **r_l, BMEdge *example, bool no_double);
/**
* \brief Face Split with intermediate points
*
* Like BM_face_split, but with an edge split by \a n intermediate points with given coordinates.
*
* \param bm: The bmesh.
* \param f: the original face.
* \param l_a, l_b: Vertices which define the split edge, must be different.
* \param cos: Array of coordinates for intermediate points.
* \param n: Length of \a cos (must be > 0).
* \param r_l: pointer which will receive the BMLoop.
* for the first split edge (from \a l_a) in the new face.
* \param example: Edge used for attributes of splitting edge, if non-NULL.
*
* \return Pointer to the newly created face representing one side of the split
* if the split is successful (and the original face will be the other side).
* NULL if the split fails.
*/
BMFace *BM_face_split_n(BMesh *bm,
BMFace *f,
BMLoop *l_a,
BMLoop *l_b,
float cos[][3],
int n,
BMLoop **r_l,
BMEdge *example);
/**
* \brief Vert Collapse Faces
*
* Collapses vertex \a v_kill that has only two manifold edges
* onto a vertex it shares an edge with.
* \a fac defines the amount of interpolation for Custom Data.
*
* \note that this is not a general edge collapse function.
*
* \note this function is very close to #BM_vert_collapse_edge,
* both collapse a vertex and return a new edge.
* Except this takes a factor and merges custom data.
*
* \param bm: The bmesh
* \param e_kill: The edge to collapse
* \param v_kill: The vertex to collapse into the edge
* \param fac: The factor along the edge
* \param join_faces: When true the faces around the vertex will be joined
* otherwise collapse the vertex by merging the 2 edges this vert touches into one.
* \param kill_degenerate_faces: Removes faces with less than 3 verts after collapsing.
*
* \returns The New Edge
*/
BMEdge *BM_vert_collapse_faces(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
float fac,
bool do_del,
bool join_faces,
bool kill_degenerate_faces,
bool kill_duplicate_faces);
/**
* \brief Vert Collapse Faces
*
* Collapses a vertex onto another vertex it shares an edge with.
*
* \return The New Edge
*
* \note To check if collapsing would create duplicate geometry,
* see: #BM_vert_collapse_check_double_face.
*/
BMEdge *BM_vert_collapse_edge(BMesh *bm,
BMEdge *e_kill,
BMVert *v_kill,
bool do_del,
bool kill_degenerate_faces,
bool kill_duplicate_faces);
/**
* Collapse and edge into a single vertex.
*/
BMVert *BM_edge_collapse(
BMesh *bm, BMEdge *e_kill, BMVert *v_kill, bool do_del, bool kill_degenerate_faces);
/**
* \brief Edge Split
*
* <pre>
* Before: v
* +-----------------------------------+
* e
*
* After: v v_new (returned)
* +-----------------+-----------------+
* r_e e
* </pre>
*
* \param e: The edge to split.
* \param v: One of the vertices in \a e and defines the "from" end of the splitting operation,
* the new vertex will be \a fac of the way from \a v to the other end.
* \param r_e: The newly created edge.
* \return The new vertex.
*/
BMVert *BM_edge_split(BMesh *bm, BMEdge *e, BMVert *v, BMEdge **r_e, float fac);
/**
* \brief Split an edge multiple times evenly
*
* \param r_varr: Optional array, verts in between (v1 -> v2)
*/
BMVert *BM_edge_split_n(BMesh *bm, BMEdge *e, int numcuts, BMVert **r_varr);
/**
* Swap v1 & v2
*
* \note Typically we shouldn't care about this, however it's used when extruding wire edges.
*/
void BM_edge_verts_swap(BMEdge *e);
/**
* Calculate the 2 loops which _would_ make up the newly rotated Edge
* but don't actually change anything.
*
* Use this to further inspect if the loops to be connected have issues:
*
* Examples:
* - the newly formed edge already exists
* - the new face would be degenerate (zero area / concave / bow-tie)
* - may want to measure if the new edge gives improved results topology.
* over the old one, as with beauty fill.
*
* \note #BM_edge_rotate_check must have already run.
*/
[[nodiscard]] bool BM_edge_calc_rotate(BMEdge *e, bool ccw, BMLoop **r_l1, BMLoop **r_l2);
/**
* \brief Check if Rotate Edge is OK
*
* Quick check to see if we could rotate the edge,
* use this to avoid calling exceptions on common cases.
*
* Take care, depending on the rotation direction its possible
* the adjacent faces share multiple edges on either side.
*
* Before executing the rotation it's important to check the rotated loops
* on both faces don't reference the same vertex.
*/
bool BM_edge_rotate_check(BMEdge *e);
/**
* \brief Check if Edge Rotate Gives Degenerate Faces
*
* Check 2 cases
* 1) does the newly forms edge form a flipped face (compare with previous cross product)
* 2) does the newly formed edge cause a zero area corner (or close enough to be almost zero)
*
* \param e: The edge to test rotation.
* \param l1, l2: are the loops of the proposed verts to rotate too and should
* be the result of calling #BM_edge_calc_rotate
*/
bool BM_edge_rotate_check_degenerate(BMEdge *e, BMLoop *l1, BMLoop *l2);
bool BM_edge_rotate_check_beauty(BMEdge *e, BMLoop *l1, BMLoop *l2);
/**
* \brief Rotate Edge
*
* Spins an edge topologically,
* either counter-clockwise or clockwise depending on \a ccw.
*
* \return The spun edge, NULL on error
* (e.g., if the edge isn't surrounded by exactly two faces).
*
* \note This works by dissolving the edge then re-creating it,
* so the returned edge won't have the same pointer address as the original one.
*
* \see header definition for \a check_flag enum.
*/
BMEdge *BM_edge_rotate(BMesh *bm, BMEdge *e, bool ccw, short check_flag);
/** Flags for #BM_edge_rotate */
enum {
/** Disallow rotating when the new edge matches an existing one. */
BM_EDGEROT_CHECK_EXISTS = (1 << 0),
/** Overrides existing check, if the edge already, rotate and merge them. */
BM_EDGEROT_CHECK_SPLICE = (1 << 1),
/** Disallow creating bow-tie, concave or zero area faces */
BM_EDGEROT_CHECK_DEGENERATE = (1 << 2),
/** Disallow rotating into ugly topology. */
BM_EDGEROT_CHECK_BEAUTY = (1 << 3),
};
/**
* \brief Rip a single face from a vertex fan
*/
BMVert *BM_face_loop_separate(BMesh *bm, BMLoop *l_sep);
BMVert *BM_face_loop_separate_multi_isolated(BMesh *bm, BMLoop *l_sep);
BMVert *BM_face_loop_separate_multi(BMesh *bm, BMLoop **larr, int larr_len);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,847 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_enum_flags.hh"
#include "BLI_ghash.h"
#include <cstdarg>
#include "bmesh_class.hh"
namespace blender {
/**
* operators represent logical, executable mesh modules. all topological
* operations involving a bmesh has to go through them.
*
* operators are nested, as are tool flags, which are private to an operator
* when it's executed. tool flags are allocated in layers, one per operator
* execution, and are used for all internal flagging a tool needs to do.
*
* each operator has a series of "slots" which can be of the following types:
* - simple numerical types
* - arrays of elements (e.g. arrays of faces).
* - hash mappings.
*
* each slot is identified by a slot code, as are each operator.
* operators, and their slots, are defined in bmesh_opdefines.cc (with their
* execution functions prototyped in bmesh_operators_private.hh), with all their
* operator code and slot codes defined in bmesh_operators.hh. see
* bmesh_opdefines.cc and the BMOpDefine struct for how to define new operators.
*
* in general, operators are fed arrays of elements, created using either
* #BMO_slot_buffer_from_hflag or #BMO_slot_buffer_from_flag
* (or through one of the format specifiers in #BMO_op_callf or #BMO_op_initf).
*
* \note multiple element types (e.g. faces and edges)
* can be fed to the same slot array. Operators act on this data,
* and possibly spit out data into output slots.
*
* \note operators should never read from header flags (e.g. element->head.flag).
* For example, if you want an operator to only operate on selected faces, you
* should use #BMO_slot_buffer_from_hflag to put the selected elements into a slot.
*
* \note when you read from an element slot array or mapping, you can either tool-flag
* all the elements in it, or read them using an iterator API (which is semantically
* similar to the iterator API in bmesh_iterators.hh).
*
* \note only #BMLoop items can't be put into slots as with verts, edges & faces.
*/
struct GHashIterator;
BLI_INLINE BMFlagLayer *BMO_elem_flag_from_header(BMHeader *ele_head)
{
switch (ele_head->htype) {
case BM_VERT:
return (reinterpret_cast<BMVert_OFlag *>(ele_head))->oflags;
case BM_EDGE:
return (reinterpret_cast<BMEdge_OFlag *>(ele_head))->oflags;
default:
return (reinterpret_cast<BMFace_OFlag *>(ele_head))->oflags;
}
}
#define BMO_elem_flag_test(bm, ele, oflag) \
_bmo_elem_flag_test(bm, BMO_elem_flag_from_header(&(ele)->head), oflag)
#define BMO_elem_flag_test_bool(bm, ele, oflag) \
_bmo_elem_flag_test_bool(bm, BMO_elem_flag_from_header(&(ele)->head), oflag)
#define BMO_elem_flag_enable(bm, ele, oflag) \
_bmo_elem_flag_enable( \
bm, (BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), oflag)
#define BMO_elem_flag_disable(bm, ele, oflag) \
_bmo_elem_flag_disable( \
bm, (BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), oflag)
#define BMO_elem_flag_set(bm, ele, oflag, val) \
_bmo_elem_flag_set(bm, \
(BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), \
oflag, \
val)
#define BMO_elem_flag_toggle(bm, ele, oflag) \
_bmo_elem_flag_toggle( \
bm, (BM_CHECK_TYPE_ELEM_NONCONST(ele), BMO_elem_flag_from_header(&(ele)->head)), oflag)
/* take care not to instantiate args multiple times */
#ifdef __GNUC___
# define _BMO_CAST_V_CONST(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_VERT(_e), \
BLI_assert(((const BMHeader *)_e)->htype == BM_VERT), \
(const BMVert_OFlag *)_e); \
})
# define _BMO_CAST_E_CONST(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_EDGE(_e), \
BLI_assert(((const BMHeader *)_e)->htype == BM_EDGE), \
(const BMEdge_OFlag *)_e); \
})
# define _BMO_CAST_F_CONST(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_FACE(_e), \
BLI_assert(((const BMHeader *)_e)->htype == BM_FACE), \
(const BMFace_OFlag *)_e); \
})
# define _BMO_CAST_V(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_VERT_NONCONST(_e), \
BLI_assert(((BMHeader *)_e)->htype == BM_VERT), \
(BMVert_OFlag *)_e); \
})
# define _BMO_CAST_E(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_EDGE_NONCONST(_e), \
BLI_assert(((BMHeader *)_e)->htype == BM_EDGE), \
(BMEdge_OFlag *)_e); \
})
# define _BMO_CAST_F(e) \
({ \
typeof(e) _e = e; \
(BM_CHECK_TYPE_FACE_NONCONST(_e), \
BLI_assert(((BMHeader *)_e)->htype == BM_FACE), \
(BMFace_OFlag *)_e); \
})
#else
# define _BMO_CAST_V_CONST(e) (BM_CHECK_TYPE_VERT(e), (const BMVert_OFlag *)e)
# define _BMO_CAST_E_CONST(e) (BM_CHECK_TYPE_EDGE(e), (const BMEdge_OFlag *)e)
# define _BMO_CAST_F_CONST(e) (BM_CHECK_TYPE_FACE(e), (const BMFace_OFlag *)e)
# define _BMO_CAST_V(e) (BM_CHECK_TYPE_VERT_NONCONST(e), (BMVert_OFlag *)e)
# define _BMO_CAST_E(e) (BM_CHECK_TYPE_EDGE_NONCONST(e), (BMEdge_OFlag *)e)
# define _BMO_CAST_F(e) (BM_CHECK_TYPE_FACE_NONCONST(e), (BMFace_OFlag *)e)
#endif
#define BMO_vert_flag_test(bm, e, oflag) \
_bmo_elem_flag_test(bm, _BMO_CAST_V_CONST(e)->oflags, oflag)
#define BMO_vert_flag_test_bool(bm, e, oflag) \
_bmo_elem_flag_test_bool(bm, _BMO_CAST_V_CONST(e)->oflags, oflag)
#define BMO_vert_flag_enable(bm, e, oflag) _bmo_elem_flag_enable(bm, _BMO_CAST_V(e)->oflags, oflag)
#define BMO_vert_flag_disable(bm, e, oflag) \
_bmo_elem_flag_disable(bm, _BMO_CAST_V(e)->oflags, oflag)
#define BMO_vert_flag_set(bm, e, oflag, val) \
_bmo_elem_flag_set(bm, _BMO_CAST_V(e)->oflags, oflag, val)
#define BMO_vert_flag_toggle(bm, e, oflag) _bmo_elem_flag_toggle(bm, _BMO_CAST_V(e)->oflags, oflag)
#define BMO_edge_flag_test(bm, e, oflag) \
_bmo_elem_flag_test(bm, _BMO_CAST_E_CONST(e)->oflags, oflag)
#define BMO_edge_flag_test_bool(bm, e, oflag) \
_bmo_elem_flag_test_bool(bm, _BMO_CAST_E_CONST(e)->oflags, oflag)
#define BMO_edge_flag_enable(bm, e, oflag) _bmo_elem_flag_enable(bm, _BMO_CAST_E(e)->oflags, oflag)
#define BMO_edge_flag_disable(bm, e, oflag) \
_bmo_elem_flag_disable(bm, _BMO_CAST_E(e)->oflags, oflag)
#define BMO_edge_flag_set(bm, e, oflag, val) \
_bmo_elem_flag_set(bm, _BMO_CAST_E(e)->oflags, oflag, val)
#define BMO_edge_flag_toggle(bm, e, oflag) _bmo_elem_flag_toggle(bm, _BMO_CAST_E(e)->oflags, oflag)
#define BMO_face_flag_test(bm, e, oflag) \
_bmo_elem_flag_test(bm, _BMO_CAST_F_CONST(e)->oflags, oflag)
#define BMO_face_flag_test_bool(bm, e, oflag) \
_bmo_elem_flag_test_bool(bm, _BMO_CAST_F_CONST(e)->oflags, oflag)
#define BMO_face_flag_enable(bm, e, oflag) _bmo_elem_flag_enable(bm, _BMO_CAST_F(e)->oflags, oflag)
#define BMO_face_flag_disable(bm, e, oflag) \
_bmo_elem_flag_disable(bm, _BMO_CAST_F(e)->oflags, oflag)
#define BMO_face_flag_set(bm, e, oflag, val) \
_bmo_elem_flag_set(bm, _BMO_CAST_F(e)->oflags, oflag, val)
#define BMO_face_flag_toggle(bm, e, oflag) _bmo_elem_flag_toggle(bm, _BMO_CAST_F(e)->oflags, oflag)
BLI_INLINE short _bmo_elem_flag_test(BMesh *bm, const BMFlagLayer *oflags, short oflag);
BLI_INLINE bool _bmo_elem_flag_test_bool(BMesh *bm, const BMFlagLayer *oflags, short oflag);
BLI_INLINE void _bmo_elem_flag_enable(BMesh *bm, BMFlagLayer *oflags, short oflag);
BLI_INLINE void _bmo_elem_flag_disable(BMesh *bm, BMFlagLayer *oflags, short oflag);
BLI_INLINE void _bmo_elem_flag_set(BMesh *bm, BMFlagLayer *oflags, short oflag, int val);
BLI_INLINE void _bmo_elem_flag_toggle(BMesh *bm, BMFlagLayer *oflags, short oflag);
/* slot type arrays are terminated by the last member
* having a slot type of 0 */
enum eBMOpSlotType {
/* BMO_OP_SLOT_SENTINEL = 0, */
BMO_OP_SLOT_BOOL = 1,
BMO_OP_SLOT_INT = 2,
BMO_OP_SLOT_FLT = 3,
/* normally store pointers to object, scene,
* _never_ store arrays corresponding to mesh elements with this */
BMO_OP_SLOT_PTR = 4, /* requires subtype BMO_OP_SLOT_SUBTYPE_PTR_xxx */
BMO_OP_SLOT_MAT = 5,
BMO_OP_SLOT_VEC = 8,
/* after BMO_OP_SLOT_VEC, everything is dynamically allocated arrays.
* We leave a space in the identifiers for future growth.
*
* it's very important this remain a power of two */
BMO_OP_SLOT_ELEMENT_BUF = 9, /* list of verts/edges/faces */
BMO_OP_SLOT_MAPPING = 10 /* simple hash map, requires subtype BMO_OP_SLOT_SUBTYPE_MAP_xxx */
};
#define BMO_OP_SLOT_TOTAL_TYPES 11
/* don't overlap values to avoid confusion */
enum eBMOpSlotSubType_Elem {
/* use as flags */
BMO_OP_SLOT_SUBTYPE_ELEM_VERT = BM_VERT,
BMO_OP_SLOT_SUBTYPE_ELEM_EDGE = BM_EDGE,
BMO_OP_SLOT_SUBTYPE_ELEM_FACE = BM_FACE,
BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE = (BM_FACE << 1),
};
ENUM_OPERATORS(eBMOpSlotSubType_Elem)
enum eBMOpSlotSubType_Map {
BMO_OP_SLOT_SUBTYPE_MAP_EMPTY = 64, /* use as a set(), unused value */
BMO_OP_SLOT_SUBTYPE_MAP_ELEM = 65,
BMO_OP_SLOT_SUBTYPE_MAP_FLT = 66,
BMO_OP_SLOT_SUBTYPE_MAP_INT = 67,
BMO_OP_SLOT_SUBTYPE_MAP_BOOL = 68,
BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL = 69, /* python can't convert these */
};
enum eBMOpSlotSubType_Ptr {
BMO_OP_SLOT_SUBTYPE_PTR_BMESH = 100,
BMO_OP_SLOT_SUBTYPE_PTR_SCENE = 101,
BMO_OP_SLOT_SUBTYPE_PTR_OBJECT = 102,
BMO_OP_SLOT_SUBTYPE_PTR_MESH = 103,
BMO_OP_SLOT_SUBTYPE_PTR_STRUCT = 104,
};
enum eBMOpSlotSubType_Int {
BMO_OP_SLOT_SUBTYPE_INT_ENUM = 200,
BMO_OP_SLOT_SUBTYPE_INT_FLAG = 201,
};
union eBMOpSlotSubType_Union {
eBMOpSlotSubType_Elem elem;
eBMOpSlotSubType_Ptr ptr;
eBMOpSlotSubType_Map map;
eBMOpSlotSubType_Int intg;
};
struct BMO_FlagSet {
int value;
const char *identifier;
};
/* please ignore all these structures, don't touch them in tool code, except
* for when your defining an operator with BMOpDefine. */
struct BMOpSlot {
const char *slot_name; /* pointer to BMOpDefine.slot_args */
eBMOpSlotType slot_type;
eBMOpSlotSubType_Union slot_subtype;
int len;
// int flag; /* UNUSED */
// int index; /* index within slot array */ /* UNUSED */
union {
int i;
float f;
void *p;
float vec[3];
void **buf;
GHash *ghash;
struct {
/** Don't clobber (i) when assigning flags, see #eBMOpSlotSubType_Int. */
int _i;
BMO_FlagSet *flags;
} enum_data;
} data;
};
/* mainly for use outside bmesh internal code */
#define BMO_SLOT_AS_BOOL(slot) ((slot)->data.i)
#define BMO_SLOT_AS_INT(slot) ((slot)->data.i)
#define BMO_SLOT_AS_FLOAT(slot) ((slot)->data.f)
#define BMO_SLOT_AS_VECTOR(slot) ((slot)->data.vec)
#define BMO_SLOT_AS_MATRIX(slot) ((float (*)[4])((slot)->data.p))
#define BMO_SLOT_AS_BUFFER(slot) ((slot)->data.buf)
#define BMO_SLOT_AS_GHASH(slot) ((slot)->data.ghash)
#define BMO_ASSERT_SLOT_IN_OP(slot, op) \
BLI_assert(((slot >= (op)->slots_in) && (slot < &(op)->slots_in[BMO_OP_MAX_SLOTS])) || \
((slot >= (op)->slots_out) && (slot < &(op)->slots_out[BMO_OP_MAX_SLOTS])))
/* Limit hit, so expanded for bevel operator. Compiler complains if limit is hit. */
#define BMO_OP_MAX_SLOTS 21
/* BMOpDefine->type_flag */
enum BMOpTypeFlag {
BMO_OPTYPE_FLAG_NOP = 0,
/** Switch from multires tangent space to absolute coordinates. */
BMO_OPTYPE_FLAG_UNTAN_MULTIRES = (1 << 0),
BMO_OPTYPE_FLAG_NORMALS_CALC = (1 << 1),
BMO_OPTYPE_FLAG_SELECT_FLUSH = (1 << 2),
BMO_OPTYPE_FLAG_SELECT_VALIDATE = (1 << 3),
BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL = (1 << 4),
};
ENUM_OPERATORS(BMOpTypeFlag)
struct BMOperator {
struct BMOpSlot slots_in[BMO_OP_MAX_SLOTS];
struct BMOpSlot slots_out[BMO_OP_MAX_SLOTS];
void (*exec)(BMesh *bm, struct BMOperator *op);
struct MemArena *arena;
int type;
BMOpTypeFlag type_flag;
int flag; /* runtime options */
};
enum {
BMO_FLAG_RESPECT_HIDE = 1,
};
#define BMO_FLAG_DEFAULTS BMO_FLAG_RESPECT_HIDE
#define MAX_SLOTNAME 32
struct BMOSlotType {
char name[MAX_SLOTNAME];
eBMOpSlotType type;
eBMOpSlotSubType_Union subtype;
BMO_FlagSet *enum_flags;
};
struct BMOpDefine {
const char *opname;
BMOSlotType slot_types_in[BMO_OP_MAX_SLOTS];
BMOSlotType slot_types_out[BMO_OP_MAX_SLOTS];
/**
* Optional initialize function.
* Can be used for setting defaults.
*/
void (*init)(BMOperator *op);
void (*exec)(BMesh *bm, BMOperator *op);
BMOpTypeFlag type_flag;
};
/* -------------------------------------------------------------------- */
/** \name BMesh Operator API
*
* \note data types that use pointers (arrays, etc) must _never_ have it set directly.
* Don't #BMO_slot_ptr_set to pass in a list of edges or any arrays.
* \{ */
/**
* \brief BMESH OPSTACK INIT OP
*
* Initializes an operator structure to a certain type
*/
void BMO_op_init(BMesh *bm, BMOperator *op, int flag, const char *opname);
/**
* \brief BMESH OPSTACK EXEC OP
*
* Executes a passed in operator.
*
* This handles the allocation and freeing of temporary tool flag
* layers and starting/stopping the modeling loop.
* Can be called from other operators exec callbacks as well.
*/
void BMO_op_exec(BMesh *bm, BMOperator *op);
/**
* \brief BMESH OPSTACK FINISH OP
*
* Does housekeeping chores related to finishing up an operator.
*
* \note the operator's tool flag is removed after it finishes executing in #BMO_op_exec.
*/
void BMO_op_finish(BMesh *bm, BMOperator *op);
/**
* Count the number of elements with the specified flag enabled.
* type can be a bit-mask of #BM_FACE, #BM_EDGE, or #BM_FACE.
*/
int BMO_mesh_enabled_flag_count(BMesh *bm, char htype, short oflag);
/**
* Count the number of elements with the specified flag disabled.
* type can be a bit-mask of #BM_FACE, #BM_EDGE, or #BM_FACE.
*/
int BMO_mesh_disabled_flag_count(BMesh *bm, char htype, short oflag);
/**
* \brief BMESH OPSTACK PUSH
*
* Pushes the operator-stack down one level and allocates a new flag layer if appropriate.
*/
void BMO_push(BMesh *bm, BMOperator *op);
/**
* \brief BMESH OPSTACK POP
*
* Pops the operator-stack one level and frees a flag layer if appropriate
*
* BMESH_TODO: investigate NOT freeing flag layers.
*/
void BMO_pop(BMesh *bm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name Formatted Operator Initialization/Execution
*
* Format Strings for #BMOperator Initialization.
*
* This system is used to execute or initialize an operator,
* using a formatted-string system.
*
* The basic format for the format string is:
* `[operatorname] [slot_name]=%[code] [slot_name]=%[code]`
*
* Example:
*
* \code{.c}
* BMO_op_callf(bm, BMO_FLAG_DEFAULTS,
* "delete context=%i geom=%hv",
* DEL_ONLYFACES, BM_ELEM_SELECT);
* \endcode
* **Primitive Types**
* - `b` - boolean (same as int but 1/0 only). #BMO_OP_SLOT_BOOL
* - `i` - int. #BMO_OP_SLOT_INT
* - `f` - float. #BMO_OP_SLOT_FLT
* - `p` - pointer (normally to a Scene/Mesh/Object/BMesh). #BMO_OP_SLOT_PTR
* - `m3` - 3x3 matrix of floats. #BMO_OP_SLOT_MAT
* - `m4` - 4x4 matrix of floats. #BMO_OP_SLOT_MAT
* - `v` - 3D vector of floats. #BMO_OP_SLOT_VEC
* **Utility**
*
* Pass an existing slot which is copied to either an input or output slot.
* Taking the operator and slot-name pair of args (BMOperator *, const char *).
* - `s` - slot_in (lower case)
* - `S` - slot_out (upper case)
* **Element Buffer** (#BMO_OP_SLOT_ELEMENT_BUF)
* - `e` - single element vert/edge/face (use with #BMO_OP_SLOT_SUBTYPE_ELEM_IS_SINGLE).
* - `eb` - elem buffer, take an array and a length.
* - `av` - all verts
* - `ae` - all edges
* - `af` - all faces
* - `hv` - header flagged verts (hflag)
* - `he` - header flagged edges (hflag)
* - `hf` - header flagged faces (hflag)
* - `Hv` - header flagged verts (hflag off)
* - `He` - header flagged edges (hflag off)
* - `Hf` - header flagged faces (hflag off)
* - `fv` - flagged verts (oflag)
* - `fe` - flagged edges (oflag)
* - `ff` - flagged faces (oflag)
* - `Fv` - flagged verts (oflag off)
* - `Fe` - flagged edges (oflag off)
* - `Ff` - flagged faces (oflag off)
*
* \note The common v/e/f suffix can be mixed,
* so `avef` is can be used for all verts, edges and faces.
* Order is not important so `Hfev` is also valid (all un-flagged verts, edges and faces).
*
* \{ */
/** Executes an operator. */
bool BMO_op_callf(BMesh *bm, int flag, const char *fmt, ...);
/** A `va_list` version of #BMO_op_callf. */
bool BMO_op_vcallf(BMesh *bm, int flag, const char *fmt, va_list list);
/**
* Initializes, but doesn't execute an operator. this is so you can
* gain access to the outputs of the operator. note that you have
* to execute/finish (BMO_op_exec and BMO_op_finish) yourself.
*/
bool BMO_op_initf(BMesh *bm, BMOperator *op, int flag, const char *fmt, ...);
/**
* A `va_list` version, used to implement the above two functions,
* plus #EDBM_op_callf in editmesh_utils.cc.
*/
bool BMO_op_vinitf(BMesh *bm, BMOperator *op, int flag, const char *fmt, va_list vlist);
/** \} */
/* -------------------------------------------------------------------- */
/** \name BMesh Operator Slot Access
* \{ */
/**
* \brief BMESH OPSTACK HAS SLOT
*
* \return Success if the slot if found.
*/
bool BMO_slot_exists(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier);
/* get a pointer to a slot. this may be removed layer on from the public API. */
/**
* \brief BMESH OPSTACK GET SLOT
*
* Returns a pointer to the slot of type 'slot_code'
*/
BMOpSlot *BMO_slot_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *identifier);
/* copies the data of a slot from one operator to another. src and dst are the
* source/destination slot codes, respectively. */
#define BMO_slot_copy(op_src, slots_src, slot_name_src, op_dst, slots_dst, slot_name_dst) \
_bmo_slot_copy( \
(op_src)->slots_src, slot_name_src, (op_dst)->slots_dst, slot_name_dst, (op_dst)->arena)
/**
* \brief BMESH OPSTACK COPY SLOT
*
* define used.
* Copies data from one slot to another.
*/
void _bmo_slot_copy(BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS],
const char *slot_name_src,
BMOpSlot slot_args_dst[BMO_OP_MAX_SLOTS],
const char *slot_name_dst,
struct MemArena *arena_dst);
/** \} */
/** Delete "context" slot values, used for operator too. */
enum {
DEL_VERTS = 1,
DEL_EDGES,
DEL_ONLYFACES,
DEL_EDGESFACES,
DEL_FACES,
/* A version of 'DEL_FACES' that keeps edges on face boundaries,
* allowing the surrounding edge-loop to be kept from removed face regions. */
DEL_FACES_KEEP_BOUNDARY,
DEL_ONLYTAGGED,
};
enum BMO_SymmDirection {
BMO_SYMMETRIZE_NEGATIVE_X,
BMO_SYMMETRIZE_NEGATIVE_Y,
BMO_SYMMETRIZE_NEGATIVE_Z,
BMO_SYMMETRIZE_POSITIVE_X,
BMO_SYMMETRIZE_POSITIVE_Y,
BMO_SYMMETRIZE_POSITIVE_Z,
};
enum BMO_Delimit {
BMO_DELIM_NORMAL = 1 << 0,
BMO_DELIM_MATERIAL = 1 << 1,
BMO_DELIM_SEAM = 1 << 2,
BMO_DELIM_SHARP = 1 << 3,
BMO_DELIM_UV = 1 << 4,
};
ENUM_OPERATORS(BMO_Delimit)
void BMO_op_flag_enable(BMesh *bm, BMOperator *op, int op_flag);
void BMO_op_flag_disable(BMesh *bm, BMOperator *op, int op_flag);
/* -------------------------------------------------------------------- */
/** \name BMesh Operator Slot Get/Set
* \{ */
void BMO_slot_float_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float f);
float BMO_slot_float_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
void BMO_slot_int_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int i);
int BMO_slot_int_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
void BMO_slot_bool_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, bool i);
bool BMO_slot_bool_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/**
* Return a copy of the element buffer.
*/
void *BMO_slot_as_arrayN(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, int *len);
/**
* Don't pass in arrays that are supposed to map to elements this way.
*
* so, e.g. passing in list of floats per element in another slot is bad.
* passing in, e.g. pointer to an edit-mesh for the conversion operator is fine though.
*/
void BMO_slot_ptr_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, void *p);
void *BMO_slot_ptr_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
void BMO_slot_vec_set(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const float vec[3]);
void BMO_slot_vec_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name, float r_vec[3]);
/**
* Only supports square matrices.
* size must be 3 or 4; this API is meant only for transformation matrices.
*
* \note the matrix is stored in 4x4 form, and it's safe to call whichever function you want.
*/
void BMO_slot_mat_set(BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
const float *mat,
int size);
void BMO_slot_mat4_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
float r_mat[4][4]);
void BMO_slot_mat3_get(BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
float r_mat[3][3]);
/** \} */
void BMO_mesh_flag_disable_all(BMesh *bm, BMOperator *op, char htype, short oflag);
void BMO_mesh_selected_remap(BMesh *bm,
BMOpSlot *slot_vert_map,
BMOpSlot *slot_edge_map,
BMOpSlot *slot_face_map,
bool check_select);
/**
* Copies the values from another slot to the end of the output slot.
*/
#define BMO_slot_buffer_append( \
op_src, slots_src, slot_name_src, op_dst, slots_dst, slot_name_dst) \
_bmo_slot_buffer_append( \
(op_src)->slots_src, slot_name_src, (op_dst)->slots_dst, slot_name_dst, (op_dst)->arena)
/**
* Copies the values from another slot to the end of the output slot.
*/
void _bmo_slot_buffer_append(BMOpSlot slot_args_dst[BMO_OP_MAX_SLOTS],
const char *slot_name_dst,
BMOpSlot slot_args_src[BMO_OP_MAX_SLOTS],
const char *slot_name_src,
struct MemArena *arena_dst);
/**
* Puts every element of type 'type' (which is a bit-mask) with tool flag 'flag', into a slot.
*/
void BMO_slot_buffer_from_enabled_flag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* Puts every element of type 'type' (which is a bit-mask) without tool flag 'flag', into a slot.
*/
void BMO_slot_buffer_from_disabled_flag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* \brief BMO_FLAG_BUFFER
*
* Flags elements in a slots buffer
*/
void BMO_slot_buffer_flag_enable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* \brief BMO_FLAG_BUFFER
*
* Removes flags from elements in a slots buffer
*/
void BMO_slot_buffer_flag_disable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
/**
* \brief BMO_FLAG_BUFFER
*
* Header Flags elements in a slots buffer, automatically
* using the selection API where appropriate.
*/
void BMO_slot_buffer_hflag_enable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag,
bool do_flush);
/**
* \brief BMO_FLAG_BUFFER
*
* Removes flags from elements in a slots buffer, automatically
* using the selection API where appropriate.
*/
void BMO_slot_buffer_hflag_disable(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag,
bool do_flush);
/**
* Puts every element of type 'type' (which is a bit-mask) with header flag 'flag', into a slot.
* \note ignores hidden elements (e.g. elements with header flag BM_ELEM_HIDDEN set).
*/
void BMO_slot_buffer_from_enabled_hflag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag);
/**
* Puts every element of type 'type' (which is a bit-mask) without header flag 'flag', into a slot.
* \note ignores hidden elements (e.g. elements with header flag BM_ELEM_HIDDEN set).
*/
void BMO_slot_buffer_from_disabled_hflag(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
char hflag);
void BMO_slot_buffer_from_array(BMOperator *op,
BMOpSlot *slot,
BMHeader **ele_buffer,
int ele_buffer_len);
void BMO_slot_buffer_from_single(BMOperator *op, BMOpSlot *slot, BMHeader *ele);
void *BMO_slot_buffer_get_single(BMOpSlot *slot);
/** Return the number of elements inside a slot array. */
int BMO_slot_buffer_len(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/** Return the number of elements inside a slot map. */
int BMO_slot_map_len(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/**
* Inserts a key/value mapping into a mapping slot. note that it copies the
* value, it doesn't store a reference to it.
*/
void BMO_slot_map_insert(BMOperator *op, BMOpSlot *slot, const void *element, const void *data);
/**
* Flags all elements in a mapping.
* \note that the mapping must only have #BMesh elements in it.
*/
void BMO_slot_map_to_flag(BMesh *bm,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype,
short oflag);
void *BMO_slot_buffer_alloc(BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
int len);
/**
* \brief BMO_ALL_TO_SLOT
*
* Copies all elements of a certain type into an operator slot.
*/
void BMO_slot_buffer_from_all(BMesh *bm,
BMOperator *op,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char htype);
/**
* This part of the API is used to iterate over element buffer or
* mapping slots.
*
* for example, iterating over the faces in a slot is:
*
* \code{.c}
*
* BMOIter oiter;
* BMFace *f;
*
* f = BMO_iter_new(&oiter, some_operator, "slot_name", BM_FACE);
* for (; f; f = BMO_iter_step(&oiter)) {
* // do something with the face
* }
*
* another example, iterating over a mapping:
* BMOIter oiter;
* void *key;
* void *val;
*
* key = BMO_iter_new(&oiter, bm, some_operator, "slot_name", 0);
* for (; key; key = BMO_iter_step(&oiter)) {
* val = BMO_iter_map_value(&oiter);
* //do something with the key/val pair
* //note that val is a pointer to the val data,
* //whether it's a float, pointer, whatever.
* //
* // so to get a pointer, for example, use:
* // *((void **)BMO_iter_map_value(&oiter));
* //or something like that.
* }
* \endcode
*/
/* contents of this structure are private,
* don't directly access. */
struct BMOIter {
BMOpSlot *slot;
int cur; // for arrays
GHashIterator giter;
void **val;
/** Bit-wise '&' with #BMHeader.htype */
char restrictmask;
};
void *BMO_slot_buffer_get_first(BMOpSlot slot_args[BMO_OP_MAX_SLOTS], const char *slot_name);
/**
* \brief New Iterator
*
* \param restrictmask: restricts the iteration to certain element types
* (e.g. combination of BM_VERT, BM_EDGE, BM_FACE), if iterating
* over an element buffer (not a mapping). */
void *BMO_iter_new(BMOIter *iter,
BMOpSlot slot_args[BMO_OP_MAX_SLOTS],
const char *slot_name,
char restrictmask);
void *BMO_iter_step(BMOIter *iter);
/**
* Returns a pointer to the key-value when iterating over mappings.
* remember for pointer maps this will be a pointer to a pointer.
*/
void **BMO_iter_map_value_p(BMOIter *iter);
void *BMO_iter_map_value_ptr(BMOIter *iter);
float BMO_iter_map_value_float(BMOIter *iter);
int BMO_iter_map_value_int(BMOIter *iter);
bool BMO_iter_map_value_bool(BMOIter *iter);
#define BMO_ITER(ele, iter, slot_args, slot_name, restrict_flag) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_new(iter, slot_args, slot_name, restrict_flag); \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_step(iter))
#define BMO_ITER_INDEX(ele, iter, slot_args, slot_name, restrict_flag, i_) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_new(iter, slot_args, slot_name, restrict_flag), \
i_ = 0; \
ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMO_iter_step(iter), i_++)
/* operator slot type information - size of one element of the type given. */
extern const int BMO_OPSLOT_TYPEINFO[BMO_OP_SLOT_TOTAL_TYPES];
int BMO_opcode_from_opname(const char *opname);
} // namespace blender

View File

@@ -0,0 +1,227 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh inline operator functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
#include "intern/bmesh_operator_api.hh"
namespace blender {
struct BMOperator;
/* Tool Flag API: Tool code must never put junk in header flags (#BMHeader.hflag)
* instead, use this API to set flags.
* If you need to store a value per element, use a #GHash or a mapping slot to do it. */
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
short _bmo_elem_flag_test(BMesh *bm, const BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
return oflags[bm->toolflag_index].f & oflag;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
bool _bmo_elem_flag_test_bool(BMesh *bm, const BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
return (oflags[bm->toolflag_index].f & oflag) != 0;
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_enable(BMesh *bm, BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
oflags[bm->toolflag_index].f |= oflag;
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_disable(BMesh *bm, BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
oflags[bm->toolflag_index].f &= short(~oflag);
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_set(BMesh *bm, BMFlagLayer *oflags, const short oflag, int val)
{
BLI_assert(bm->use_toolflags);
if (val) {
oflags[bm->toolflag_index].f |= oflag;
}
else {
oflags[bm->toolflag_index].f &= short(~oflag);
}
}
ATTR_NONNULL(1, 2)
BLI_INLINE void _bmo_elem_flag_toggle(BMesh *bm, BMFlagLayer *oflags, const short oflag)
{
BLI_assert(bm->use_toolflags);
oflags[bm->toolflag_index].f ^= oflag;
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_int_insert(BMOperator *op,
BMOpSlot *slot,
void *element,
const int val)
{
union {
void *ptr;
int val;
} t = {nullptr};
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INT);
BMO_slot_map_insert(op, slot, element, ((void)(t.val = val), t.ptr));
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_bool_insert(BMOperator *op,
BMOpSlot *slot,
void *element,
const bool val)
{
union {
void *ptr;
bool val;
} t = {nullptr};
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_BOOL);
BMO_slot_map_insert(op, slot, element, ((void)(t.val = val), t.ptr));
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_float_insert(BMOperator *op,
BMOpSlot *slot,
void *element,
const float val)
{
union {
void *ptr;
float val;
} t = {nullptr};
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_FLT);
BMO_slot_map_insert(op, slot, element, ((void)(t.val = val), t.ptr));
}
/* pointer versions of BMO_slot_map_float_get and BMO_slot_map_float_insert.
*
* do NOT use these for non-operator-api-allocated memory! instead
* use BMO_slot_map_data_get and BMO_slot_map_insert, which copies the data. */
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_ptr_insert(BMOperator *op,
BMOpSlot *slot,
const void *element,
void *val)
{
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL);
BMO_slot_map_insert(op, slot, element, val);
}
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_elem_insert(BMOperator *op,
BMOpSlot *slot,
const void *element,
void *val)
{
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_ELEM);
BMO_slot_map_insert(op, slot, element, val);
}
/* no values */
ATTR_NONNULL(1, 2)
BLI_INLINE void BMO_slot_map_empty_insert(BMOperator *op, BMOpSlot *slot, const void *element)
{
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_EMPTY);
BMO_slot_map_insert(op, slot, element, nullptr);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
bool BMO_slot_map_contains(BMOpSlot *slot, const void *element)
{
BLI_assert(slot->slot_type == BMO_OP_SLOT_MAPPING);
return BLI_ghash_haskey(slot->data.ghash, element);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void **BMO_slot_map_data_get(BMOpSlot *slot, const void *element)
{
return BLI_ghash_lookup_p(slot->data.ghash, element);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
float BMO_slot_map_float_get(BMOpSlot *slot, const void *element)
{
void **data;
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_FLT);
data = BMO_slot_map_data_get(slot, element);
if (data) {
return *reinterpret_cast<float *>(data);
}
return 0.0f;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
int BMO_slot_map_int_get(BMOpSlot *slot, const void *element)
{
void **data;
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INT);
data = BMO_slot_map_data_get(slot, element);
if (data) {
return *reinterpret_cast<int *>(data);
}
return 0;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
bool BMO_slot_map_bool_get(BMOpSlot *slot, const void *element)
{
void **data;
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_BOOL);
data = BMO_slot_map_data_get(slot, element);
if (data) {
return *reinterpret_cast<bool *>(data);
}
return false;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void *BMO_slot_map_ptr_get(BMOpSlot *slot, const void *element)
{
void **val = BMO_slot_map_data_get(slot, element);
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_INTERNAL);
if (val) {
return *val;
}
return nullptr;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
void *BMO_slot_map_elem_get(BMOpSlot *slot, const void *element)
{
void **val = static_cast<void **>(BMO_slot_map_data_get(slot, element));
BLI_assert(slot->slot_subtype.map == BMO_OP_SLOT_SUBTYPE_MAP_ELEM);
if (val) {
return *val;
}
return nullptr;
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,241 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "intern/bmesh_operator_api.hh"
namespace blender {
/* See comments in `intern/bmesh_opdefines.cc` for documentation of specific operators. */
/*--------defines/enumerations for specific operators-------*/
/* Quad `innervert` values. */
enum {
SUBD_CORNER_INNERVERT,
SUBD_CORNER_PATH,
SUBD_CORNER_FAN,
SUBD_CORNER_STRAIGHT_CUT,
};
/* aligned with PROP_SMOOTH and friends */
enum {
SUBD_FALLOFF_SMOOTH = 0,
SUBD_FALLOFF_SPHERE,
SUBD_FALLOFF_ROOT,
SUBD_FALLOFF_SHARP,
SUBD_FALLOFF_LIN,
SUBD_FALLOFF_INVSQUARE = 7, /* matching PROP_INVSQUARE */
};
enum {
SUBDIV_SELECT_NONE,
SUBDIV_SELECT_ORIG,
SUBDIV_SELECT_INNER,
SUBDIV_SELECT_LOOPCUT,
};
/* subdivide_edgering */
enum {
/* just subdiv */
SUBD_RING_INTERP_LINEAR,
/* single bezier spline - curve follows bezier rotation */
SUBD_RING_INTERP_PATH,
/* beziers based on adjacent faces (fallback to tangent) */
SUBD_RING_INTERP_SURF,
};
/* similar face selection slot values */
enum {
SIMFACE_MATERIAL = 201,
SIMFACE_AREA,
SIMFACE_SIDES,
SIMFACE_PERIMETER,
SIMFACE_NORMAL,
SIMFACE_COPLANAR,
SIMFACE_SMOOTH,
SIMFACE_FREESTYLE,
};
/* similar edge selection slot values */
enum {
SIMEDGE_LENGTH = 101,
SIMEDGE_DIR,
SIMEDGE_FACE,
SIMEDGE_FACE_ANGLE,
SIMEDGE_CREASE,
SIMEDGE_BEVEL,
SIMEDGE_SEAM,
SIMEDGE_SHARP,
SIMEDGE_FREESTYLE,
};
/* similar vertex selection slot values */
enum {
SIMVERT_NORMAL = 0,
SIMVERT_FACE,
SIMVERT_VGROUP,
SIMVERT_EDGE,
SIMVERT_CREASE,
};
/* Poke face center calculation */
enum {
BMOP_POKE_MEDIAN_WEIGHTED = 0,
BMOP_POKE_MEDIAN,
BMOP_POKE_BOUNDS,
};
/* Bevel offset_type slot values */
enum {
BEVEL_AMT_OFFSET,
BEVEL_AMT_WIDTH,
BEVEL_AMT_DEPTH,
BEVEL_AMT_PERCENT,
BEVEL_AMT_ABSOLUTE,
};
/* Bevel profile type */
enum {
BEVEL_PROFILE_SUPERELLIPSE,
BEVEL_PROFILE_CUSTOM,
};
/* Bevel face_strength_mode values: should match face_str mode enum in DNA_modifier_types.h */
enum {
BEVEL_FACE_STRENGTH_NONE,
BEVEL_FACE_STRENGTH_NEW,
BEVEL_FACE_STRENGTH_AFFECTED,
BEVEL_FACE_STRENGTH_ALL,
};
/* Bevel miter slot values */
enum {
BEVEL_MITER_SHARP,
BEVEL_MITER_PATCH,
BEVEL_MITER_ARC,
};
/* Bevel vertex mesh creation methods */
enum {
BEVEL_VMESH_ADJ,
BEVEL_VMESH_CUTOFF,
};
/* Bevel affect option. */
enum {
BEVEL_AFFECT_VERTICES = 0,
BEVEL_AFFECT_EDGES = 1,
};
/* Normal Face Strength values */
enum {
FACE_STRENGTH_WEAK = -16384,
FACE_STRENGTH_MEDIUM = 0,
FACE_STRENGTH_STRONG = 16384,
};
/** Interpolation method used for spacing vertices. */
enum SpaceInterpolationMethod {
SPACE_EDGE_LOOPS_EVENLY_INTERP_CUBIC = 0,
SPACE_EDGE_LOOPS_EVENLY_INTERP_LINEAR = 1,
};
/**
* Methods for determining the orientation of flattening the plane.
*/
enum FlattenMethod {
FLATTEN_BEST_FIT = 0,
FLATTEN_NORMAL = 1,
FLATTEN_VIEW = 2,
};
extern const BMOpDefine *bmo_opdefines[];
extern const int bmo_opdefines_total;
/*------specific operator helper functions-------*/
void BM_mesh_esubdivide(BMesh *bm,
char edge_hflag,
float smooth,
short smooth_falloff,
bool use_smooth_even,
float fractal,
float along_normal,
int numcuts,
int seltype,
int cornertype,
short use_single_edge,
short use_grid_fill,
short use_only_quads,
int seed);
/**
* Fills first available UV-map with grid-like UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on
* \param x_segments: The x-resolution of the grid
* \param y_segments: The y-resolution of the grid
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_grid(
BMesh *bm, uint x_segments, uint y_segments, short oflag, int cd_loop_uv_offset);
/**
* Fills first available UV-map with spherical projected UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_sphere(BMesh *bm, short oflag, int cd_loop_uv_offset);
/**
* Fills first available UV-map with 2D projected UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on.
* \param mat: The transform matrix applied to the created circle.
* \param radius: The size of the circle.
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_circle(
BMesh *bm, float mat[4][4], float radius, short oflag, int cd_loop_uv_offset);
/**
* Fills first available UV-map with cylinder/cone-like UVs for all faces with `oflag` set.
*
* \param bm: The BMesh to operate on.
* \param mat: The transform matrix applied to the created cone/cylinder.
* \param radius_top: The size of the top end of the cone/cylinder.
* \param radius_bottom: The size of the bottom end of the cone/cylinder.
* \param segments: The number of subdivisions in the sides of the cone/cylinder.
* \param cap_ends: Whether the ends of the cone/cylinder are filled or not.
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_cone(BMesh *bm,
float mat[4][4],
float radius_top,
float radius_bottom,
int segments,
bool cap_ends,
short oflag,
int cd_loop_uv_offset);
/**
* Fills first available UV-map with cube-like UVs for all faces with `oflag` set.
*
* \note Expects tagged faces to be six quads.
* \note Caller must order faces for correct alignment.
*
* \param bm: The BMesh to operate on.
* \param oflag: The flag to check faces with.
*/
void BM_mesh_calc_uvs_cube(BMesh *bm, short oflag);
} // namespace blender
#include "intern/bmesh_operator_api_inline.hh" /* IWYU pragma: export */

View File

@@ -0,0 +1,101 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
namespace blender {
struct BMOperator;
struct BMesh;
void bmo_average_vert_facedata_exec(BMesh *bm, BMOperator *op);
void bmo_beautify_fill_exec(BMesh *bm, BMOperator *op);
void bmo_bevel_exec(BMesh *bm, BMOperator *op);
void bmo_bisect_edges_exec(BMesh *bm, BMOperator *op);
void bmo_bisect_plane_exec(BMesh *bm, BMOperator *op);
void bmo_bmesh_to_mesh_exec(BMesh *bm, BMOperator *op);
void bmo_bridge_loops_exec(BMesh *bm, BMOperator *op);
void bmo_collapse_exec(BMesh *bm, BMOperator *op);
void bmo_collapse_uvs_exec(BMesh *bm, BMOperator *op);
void bmo_connect_verts_exec(BMesh *bm, BMOperator *op);
void bmo_connect_verts_concave_exec(BMesh *bm, BMOperator *op);
void bmo_connect_verts_nonplanar_exec(BMesh *bm, BMOperator *op);
void bmo_connect_vert_pair_exec(BMesh *bm, BMOperator *op);
void bmo_contextual_create_exec(BMesh *bm, BMOperator *op);
void bmo_convex_hull_exec(BMesh *bm, BMOperator *op);
void bmo_circularize_exec(BMesh *bm, BMOperator *op);
void bmo_create_circle_exec(BMesh *bm, BMOperator *op);
void bmo_create_cone_exec(BMesh *bm, BMOperator *op);
void bmo_create_cube_exec(BMesh *bm, BMOperator *op);
void bmo_create_grid_exec(BMesh *bm, BMOperator *op);
void bmo_create_icosphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_monkey_exec(BMesh *bm, BMOperator *op);
void bmo_create_uvsphere_exec(BMesh *bm, BMOperator *op);
void bmo_create_vert_exec(BMesh *bm, BMOperator *op);
void bmo_delete_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_edges_init(BMOperator *op);
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op);
void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op);
void bmo_duplicate_exec(BMesh *bm, BMOperator *op);
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op);
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op);
void bmo_holes_fill_exec(BMesh *bm, BMOperator *op);
void bmo_edgenet_fill_exec(BMesh *bm, BMOperator *op);
void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_discrete_faces_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_face_region_exec(BMesh *bm, BMOperator *op);
void bmo_extrude_vert_indiv_exec(BMesh *bm, BMOperator *op);
void bmo_find_doubles_exec(BMesh *bm, BMOperator *op);
void bmo_flatten_exec(BMesh *bm, BMOperator *op);
void bmo_grid_fill_exec(BMesh *bm, BMOperator *op);
void bmo_inset_individual_exec(BMesh *bm, BMOperator *op);
void bmo_inset_region_exec(BMesh *bm, BMOperator *op);
void bmo_join_triangles_exec(BMesh *bm, BMOperator *op);
void bmo_mesh_to_bmesh_exec(BMesh *bm, BMOperator *op);
void bmo_mirror_exec(BMesh *bm, BMOperator *op);
void bmo_object_load_bmesh_exec(BMesh *bm, BMOperator *op);
void bmo_pointmerge_exec(BMesh *bm, BMOperator *op);
void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op);
void bmo_recalc_face_normals_exec(BMesh *bm, BMOperator *op);
void bmo_poke_exec(BMesh *bm, BMOperator *op);
void bmo_offset_edgeloops_exec(BMesh *bm, BMOperator *op);
void bmo_planar_faces_exec(BMesh *bm, BMOperator *op);
void bmo_region_extend_exec(BMesh *bm, BMOperator *op);
void bmo_remove_doubles_exec(BMesh *bm, BMOperator *op);
void bmo_reverse_colors_exec(BMesh *bm, BMOperator *op);
void bmo_reverse_faces_exec(BMesh *bm, BMOperator *op);
void bmo_reverse_uvs_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_colors_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_edges_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_exec(BMesh *bm, BMOperator *op);
void bmo_rotate_uvs_exec(BMesh *bm, BMOperator *op);
void bmo_scale_exec(BMesh *bm, BMOperator *op);
void bmo_smooth_vert_exec(BMesh *bm, BMOperator *op);
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op);
void bmo_solidify_face_region_exec(BMesh *bm, BMOperator *op);
void bmo_space_edge_loops_evenly_exec(BMesh *bm, BMOperator *op);
void bmo_spin_exec(BMesh *bm, BMOperator *op);
void bmo_split_edges_exec(BMesh *bm, BMOperator *op);
void bmo_split_exec(BMesh *bm, BMOperator *op);
void bmo_subdivide_edges_exec(BMesh *bm, BMOperator *op);
void bmo_subdivide_edgering_exec(BMesh *bm, BMOperator *op);
void bmo_symmetrize_exec(BMesh *bm, BMOperator *op);
void bmo_transform_exec(BMesh *bm, BMOperator *op);
void bmo_translate_exec(BMesh *bm, BMOperator *op);
void bmo_triangle_fill_exec(BMesh *bm, BMOperator *op);
void bmo_triangulate_exec(BMesh *bm, BMOperator *op);
void bmo_unsubdivide_exec(BMesh *bm, BMOperator *op);
void bmo_weld_verts_exec(BMesh *bm, BMOperator *op);
void bmo_wireframe_exec(BMesh *bm, BMOperator *op);
void bmo_flip_quad_tessellation_exec(BMesh *bm, BMOperator *op);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,306 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
namespace blender {
struct Heap;
#include "BLI_compiler_attrs.h"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "bmesh_class.hh"
/**
* For tools that insist on using triangles, ideally we would cache this data.
*
* \param use_fixed_quad: When true,
* always split quad along (0 -> 2) regardless of concave corners,
* (as done in #BM_mesh_calc_tessellation).
* \param r_loops: Store face loop pointers, (f->len)
* \param r_index: Store triangle triples, indices into \a r_loops, `((f->len - 2) * 3)`
*/
void BM_face_calc_tessellation(const BMFace *f,
bool use_fixed_quad,
BMLoop **r_loops,
uint (*r_index)[3]);
/**
* Return a point inside the face.
*/
void BM_face_calc_point_in_face(const BMFace *f, float r_co[3]);
/**
* \brief BMESH UPDATE FACE NORMAL
*
* Updates the stored normal for the
* given face. Requires that a buffer
* of sufficient length to store projected
* coordinates for all of the face's vertices
* is passed in as well.
*/
float BM_face_calc_normal(const BMFace *f, float r_no[3]) ATTR_NONNULL();
/* exact same as 'BM_face_calc_normal' but accepts vertex coords */
float BM_face_calc_normal_vcos(const BMesh *bm,
const BMFace *f,
float r_no[3],
Span<float3> vertexCos) ATTR_NONNULL();
/**
* Calculate a normal from a vertex cloud.
*/
void BM_verts_calc_normal_from_cloud_ex(
BMVert **varr, int varr_len, float r_normal[3], float r_center[3], int *r_index_tangent);
void BM_verts_calc_normal_from_cloud(BMVert **varr, int varr_len, float r_normal[3]);
/**
* Calculates the face subset normal.
*/
float BM_face_calc_normal_subset(const BMLoop *l_first, const BMLoop *l_last, float r_no[3])
ATTR_NONNULL();
/**
* get the area of the face
*/
float BM_face_calc_area(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get the area of the face in world space.
*/
float BM_face_calc_area_with_mat3(const BMFace *f, const float mat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Calculate the signed area of UV face.
*/
float BM_face_calc_area_uv_signed(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Calculate the area of UV face.
*/
float BM_face_calc_area_uv(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* compute the perimeter of an ngon
*/
float BM_face_calc_perimeter(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Calculate the perimeter of a ngon in world space.
*/
float BM_face_calc_perimeter_with_mat3(const BMFace *f,
const float mat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Compute the tangent of the face, using the longest edge.
*/
void BM_face_calc_tangent_from_edge(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
void BM_face_calc_tangent_pair_from_edge(const BMFace *f,
float r_tangent_a[3],
float r_tangent_b[3]);
/**
* Compute the tangent of the face, using the two longest disconnected edges.
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_face_calc_tangent_from_edge_pair(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* Compute the tangent of the face, using the edge farthest away from any vertex in the face.
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_face_calc_tangent_from_edge_diagonal(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* Compute the tangent of the face, using longest distance between vertices on the face.
*
* \note The logic is almost identical to #BM_face_calc_tangent_edge_diagonal
*/
void BM_face_calc_tangent_from_vert_diagonal(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* Compute a meaningful direction along the face (use for gizmo axis).
*
* \note Callers shouldn't depend on the *exact* method used here.
*/
void BM_face_calc_tangent_auto(const BMFace *f, float r_tangent[3]) ATTR_NONNULL();
/**
* A version of BM_face_calc_tangent_auto that calculates two tangents.
* Useful when one may not be usable.
*/
void BM_face_calc_tangent_pair_auto(const BMFace *f, float r_tangent_a[3], float r_tangent_b[3])
ATTR_NONNULL();
/**
* computes center of face in 3d. uses center of bounding box.
*/
void BM_face_calc_center_bounds(const BMFace *f, float r_cent[3]) ATTR_NONNULL();
/**
* computes center of face in 3d. uses center of bounding box.
*/
void BM_face_calc_center_bounds_vcos(const BMesh *bm,
const BMFace *f,
float r_center[3],
Span<float3> vert_positions) ATTR_NONNULL();
/**
* computes the center of a face, using the mean average
*/
void BM_face_calc_center_median(const BMFace *f, float r_center[3]) ATTR_NONNULL();
/* exact same as 'BM_face_calc_normal' but accepts vertex coords */
void BM_face_calc_center_median_vcos(const BMesh *bm,
const BMFace *f,
float r_center[3],
const Span<float3> vert_positions) ATTR_NONNULL();
/**
* computes the center of a face, using the mean average
* weighted by edge length
*/
void BM_face_calc_center_median_weighted(const BMFace *f, float r_cent[3]) ATTR_NONNULL();
/**
* expands bounds (min/max must be initialized).
*/
void BM_face_calc_bounds_expand(const BMFace *f, float min[3], float max[3]);
void BM_face_normal_update(BMFace *f) ATTR_NONNULL();
/**
* updates face and vertex normals incident on an edge
*/
void BM_edge_normals_update(BMEdge *e) ATTR_NONNULL();
bool BM_vert_calc_normal_ex(const BMVert *v, char hflag, float r_no[3]);
bool BM_vert_calc_normal(const BMVert *v, float r_no[3]);
/**
* update a vert normal (but not the faces incident on it)
*/
void BM_vert_normal_update(BMVert *v) ATTR_NONNULL();
void BM_vert_normal_update_all(BMVert *v) ATTR_NONNULL();
/**
* \brief Face Flip Normal
*
* Reverses the winding of a face.
* \note This updates the calculated normal.
*/
void BM_face_normal_flip_ex(BMesh *bm,
BMFace *f,
int cd_loop_mdisp_offset,
bool use_loop_mdisp_flip) ATTR_NONNULL();
void BM_face_normal_flip(BMesh *bm, BMFace *f) ATTR_NONNULL();
/**
* BM POINT IN FACE
*
* Projects co onto face f, and returns true if it is inside
* the face bounds.
*
* \note this uses a best-axis projection test,
* instead of projecting co directly into f's orientation space,
* so there might be accuracy issues.
*/
bool BM_face_point_inside_test(const BMFace *f, const float co[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief BMESH TRIANGULATE FACE
*
* Breaks all quads and ngons down to triangles.
* It uses poly-fill for the ngons splitting, and
* the beautify operator when use_beauty is true.
*
* \param r_faces_new: if non-null, must be an array of BMFace pointers,
* with a length equal to (f->len - 3). It will be filled with the new
* triangles (not including the original triangle).
*
* \param r_faces_double: When newly created faces are duplicates of existing faces,
* they're added to this list. Caller must handle de-duplication.
* This is done because its possible _all_ faces exist already,
* and in that case we would have to remove all faces including the one passed,
* which causes complications adding/removing faces while looking over them.
*
* \note The number of faces is _almost_ always (f->len - 3),
* However there may be faces that already occupying the
* triangles we would make, so the caller must check \a r_faces_new_tot.
*
* \note use_tag tags new flags and edges.
*/
void BM_face_triangulate(BMesh *bm,
BMFace *f,
BMFace **r_faces_new,
int *r_faces_new_tot,
BMEdge **r_edges_new,
int *r_edges_new_tot,
struct LinkNode **r_faces_double,
int quad_method,
int ngon_method,
bool use_tag,
struct MemArena *pf_arena,
struct Heap *pf_heap) ATTR_NONNULL(1, 2);
/**
* each pair of loops defines a new edge, a split. this function goes
* through and sets pairs that are geometrically invalid to null. a
* split is invalid, if it forms a concave angle or it intersects other
* edges in the face, or it intersects another split. in the case of
* intersecting splits, only the first of the set of intersecting
* splits survives
*/
void BM_face_splits_check_legal(BMesh *bm, BMFace *f, BMLoop *(*loops)[2], int len) ATTR_NONNULL();
/**
* This simply checks that the verts don't connect faces which would have more optimal splits.
* but _not_ check for correctness.
*/
void BM_face_splits_check_optimal(BMFace *f, BMLoop *(*loops)[2], int len) ATTR_NONNULL();
/**
* Small utility functions for fast access
*
* faster alternative to:
* BM_iter_as_array(bm, BM_VERTS_OF_FACE, f, (void **)v, 3);
*/
void BM_face_as_array_vert_tri(BMFace *f, BMVert *r_verts[3]) ATTR_NONNULL();
/**
* faster alternative to:
* BM_iter_as_array(bm, BM_VERTS_OF_FACE, f, (void **)v, 4);
*/
void BM_face_as_array_vert_quad(BMFace *f, BMVert *r_verts[4]) ATTR_NONNULL();
/**
* Small utility functions for fast access
*
* faster alternative to:
* BM_iter_as_array(bm, BM_LOOPS_OF_FACE, f, (void **)l, 3);
*/
void BM_face_as_array_loop_tri(BMFace *f, BMLoop *r_loops[3]) ATTR_NONNULL();
/**
* faster alternative to:
* BM_iter_as_array(bm, BM_LOOPS_OF_FACE, f, (void **)l, 4);
*/
void BM_face_as_array_loop_quad(BMFace *f, BMLoop *r_loops[4]) ATTR_NONNULL();
/**
* Calculate a tangent from any 3 vertices.
*
* The tangent aligns to the most *unique* edge
* (the edge most unlike the other two).
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_vert_tri_calc_tangent_from_edge(BMVert *verts[3], float r_tangent[3]);
void BM_vert_tri_calc_tangent_pair_from_edge(BMVert *verts[3],
float r_tangent_a[3],
float r_tangent_b[3]);
/**
* Calculate a tangent from any 3 vertices,
*
* The tangent follows the center-line formed by the most unique edges center
* and the opposite vertex.
*
* \param r_tangent: Calculated unit length tangent (return value).
*/
void BM_vert_tri_calc_tangent_edge_pair(BMVert *verts[3], float r_tangent[3]);
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "BLI_vector.hh"
#include "bmesh_class.hh"
namespace blender {
/** \file
* \ingroup bmesh
*/
/**
* Splits a face into many smaller faces defined by an edge-net.
* handle customdata and degenerate cases.
*
* - Isolated holes or unsupported face configurations, will be ignored.
* - Customdata calculations aren't efficient
* (need to calculate weights for each vert).
*/
bool BM_face_split_edgenet(
BMesh *bm, BMFace *f, BMEdge **edge_net, int edge_net_len, Vector<BMFace *> *r_face_arr);
/**
* For when the edge-net has holes in it-this connects them.
*
* \param use_partial_connect: Support for handling islands connected by only a single edge,
* \note that this is quite slow so avoid using where possible.
* \param mem_arena: Avoids many small allocations & should be cleared after each use.
* take care since \a edge_net_new is stored in \a r_edge_net_new.
*/
bool BM_face_split_edgenet_connect_islands(BMesh *bm,
BMFace *f,
BMEdge **edge_net_init,
uint edge_net_init_len,
bool use_partial_connect,
struct MemArena *mem_arena,
BMEdge ***r_edge_net_new,
uint *r_edge_net_new_len) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 6, 7, 8);
} // namespace blender

View File

@@ -0,0 +1,95 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* Private function prototypes for bmesh public API.
* This file is a grab-bag of functions from various
* parts of the bmesh internals.
*/
#include "bmesh_class.hh"
namespace blender {
/* returns positive nonzero on error */
#ifdef NDEBUG
/* No error checking for release,
* it can take most of the CPU time when running some tools. */
# define BM_CHECK_ELEMENT(el) (void)(el)
#else
/**
* Check the element is valid.
*
* BMESH_TODO, when this raises an error the output is incredibly confusing.
* need to have some nice way to print/debug what the heck's going on.
*/
int bmesh_elem_check(void *element, char htype);
# define BM_CHECK_ELEMENT(el) \
{ \
if (bmesh_elem_check(el, ((BMHeader *)el)->htype)) { \
printf( \
"check_element failure, with code %i on line %i in file\n" \
" \"%s\"\n\n", \
bmesh_elem_check(el, ((BMHeader *)el)->htype), \
__LINE__, \
__FILE__); \
} \
} \
((void)0)
#endif
int bmesh_radial_length(const BMLoop *l);
int bmesh_disk_count_at_most(const BMVert *v, int count_max);
int bmesh_disk_count(const BMVert *v);
/**
* Internal BMHeader.api_flag
* \note Ensure different parts of the API do not conflict
* on using these internal flags!
*/
enum {
_FLAG_JF = (1 << 0), /* Join faces. */
_FLAG_MF = (1 << 1), /* Make face. */
_FLAG_MV = (1 << 1), /* Make face, vertex. */
_FLAG_OVERLAP = (1 << 2), /* General overlap flag. */
_FLAG_WALK = (1 << 3), /* General walk flag (keep clean). */
_FLAG_WALK_ALT = (1 << 4), /* Same as #_FLAG_WALK, for when a second tag is needed. */
_FLAG_ELEM_CHECK = (1 << 7), /* Reserved for bmesh_elem_check. */
};
#define BM_ELEM_API_FLAG_ENABLE(element, f) \
{ \
((element)->head.api_flag |= (f)); \
} \
(void)0
#define BM_ELEM_API_FLAG_DISABLE(element, f) \
{ \
((element)->head.api_flag &= (uchar) ~(f)); \
} \
(void)0
#define BM_ELEM_API_FLAG_TEST(element, f) ((element)->head.api_flag & (f))
#define BM_ELEM_API_FLAG_CLEAR(element) \
{ \
((element)->head.api_flag = 0); \
} \
(void)0
/**
* \brief POLY ROTATE PLANE
*
* Rotates a polygon so that its
* normal is pointing towards the mesh Z axis
*/
void poly_rotate_plane(const float normal[3], float (*verts)[3], uint nverts);
} // namespace blender
/* include the rest of our private declarations */
#include "bmesh_structure.hh" /* IWYU pragma: export */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,799 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
/**
* Returns true if the vertex is used in a given face.
*/
namespace blender {
bool BM_vert_in_face(BMVert *v, BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Compares the number of vertices in an array
* that appear in a given face
*/
int BM_verts_in_face_count(BMVert **varr, int len, BMFace *f) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Return true if all verts are in the face.
*/
bool BM_verts_in_face(BMVert **varr, int len, BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns whether or not a given edge is part of a given face.
*/
bool BM_edge_in_face(const BMEdge *e, const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_in_loop(const BMEdge *e, const BMLoop *l) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE bool BM_vert_in_edge(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE bool BM_verts_in_edge(const BMVert *v1,
const BMVert *v2,
const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns edge length
*/
float BM_edge_calc_length(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns edge length squared (for comparisons)
*/
float BM_edge_calc_length_squared(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Utility function, since enough times we have an edge
* and want to access 2 connected faces.
*
* \return true when only 2 faces are found.
*/
bool BM_edge_face_pair(BMEdge *e, BMFace **r_fa, BMFace **r_fb) ATTR_NONNULL();
/**
* Utility function, since enough times we have an edge
* and want to access 2 connected loops.
*
* \return true when only 2 faces are found.
*/
bool BM_edge_loop_pair(BMEdge *e, BMLoop **r_la, BMLoop **r_lb) ATTR_NONNULL();
BLI_INLINE BMVert *BM_edge_other_vert(BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Given a edge and a loop (assumes the edge is manifold). returns
* the other faces loop, sharing the same vertex.
*
* <pre>
* +-------------------+
* | |
* | |
* |l_other <-- return |
* +-------------------+ <-- A manifold edge between 2 faces
* |l e <-- edge |
* |^ <-------- loop |
* | |
* +-------------------+
* </pre>
*/
BMLoop *BM_edge_other_loop(BMEdge *e, BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Other Loop in Face Sharing an Edge
*
* Finds the other loop that shares \a v with \a e loop in \a f.
* <pre>
* +----------+
* | |
* | f |
* | |
* +----------+ <-- return the face loop of this vertex.
* v --> e
* ^ ^ <------- These vert args define direction
* in the face to check.
* The faces loop direction is ignored.
* </pre>
*
* \note caller must ensure \a e is used in \a f
*/
BMLoop *BM_face_other_edge_loop(BMFace *f, BMEdge *e, BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* See #BM_face_other_edge_loop This is the same functionality
* to be used when the edges loop is already known.
*/
BMLoop *BM_loop_other_edge_loop(BMLoop *l, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Other Loop in Face Sharing a Vertex
*
* Finds the other loop in a face.
*
* This function returns a loop in \a f that shares an edge with \a v
* The direction is defined by \a v_prev, where the return value is
* the loop of what would be 'v_next'
* <pre>
* +----------+ <-- return the face loop of this vertex.
* | |
* | f |
* | |
* +----------+
* v_prev --> v
* ^^^^^^ ^ <-- These vert args define direction
* in the face to check.
* The faces loop direction is ignored.
* </pre>
*
* \note \a v_prev and \a v _implicitly_ define an edge.
*/
BMLoop *BM_face_other_vert_loop(BMFace *f, BMVert *v_prev, BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Return the other loop that uses this edge.
*
* In this case the loop defines the vertex,
* the edge passed in defines the direction to step.
*
* <pre>
* +----------+ <-- Return the face-loop of this vertex.
* | |
* | e | <-- This edge defines the direction.
* | |
* +----------+ <-- This loop defines the face and vertex..
* l
* </pre>
*/
BMLoop *BM_loop_other_vert_loop_by_edge(BMLoop *l, BMEdge *e) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief Other Loop in Face Sharing a Vert
*
* Finds the other loop that shares \a v with \a e loop in \a f.
* <pre>
* +----------+ <-- return the face loop of this vertex.
* | |
* | |
* | |
* +----------+ <-- This vertex defines the direction.
* l v
* ^ <------- This loop defines both the face to search
* and the edge, in combination with 'v'
* The faces loop direction is ignored.
* </pre>
*/
BMLoop *BM_loop_other_vert_loop(BMLoop *l, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Utility function to step around a fan of loops,
* using an edge to mark the previous side.
*
* \note all edges must be manifold,
* once a non manifold edge is hit, return NULL.
*
* \code{.unparsed}
* ,.,-->|
* _,-' |
* ,' | (notice how 'e_step'
* / | and 'l' define the
* / | direction the arrow
* | return | points).
* | loop --> |
* ---------------------+---------------------
* ^ l --> |
* | |
* assign e_step |
* |
* begin e_step ----> |
* |
* \endcode
*/
BMLoop *BM_vert_step_fan_loop(BMLoop *l, BMEdge **e_step) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Get the first loop of a vert. Uses the same initialization code for the first loop of the
* iterator API
*/
BMLoop *BM_vert_find_first_loop(BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A version of #BM_vert_find_first_loop that ignores hidden loops.
*/
BMLoop *BM_vert_find_first_loop_visible(BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Only #BMEdge.l access us needed, however when we want the first visible loop,
* a utility function is needed.
*/
BMLoop *BM_edge_find_first_loop_visible(BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if verts share a face.
*/
bool BM_vert_pair_share_face_check(BMVert *v_a, BMVert *v_b) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_vert_pair_share_face_check_cb(BMVert *v_a,
BMVert *v_b,
bool (*test_fn)(BMFace *f, void *user_data),
void *user_data) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3);
BMFace *BM_vert_pair_shared_face_cb(BMVert *v_a,
BMVert *v_b,
bool allow_adjacent,
bool (*callback)(BMFace *, BMLoop *, BMLoop *, void *userdata),
void *user_data,
BMLoop **r_l_a,
BMLoop **r_l_b) ATTR_NONNULL(1, 2, 4, 6, 7);
/**
* Given 2 verts, find the smallest face they share and give back both loops.
*/
BMFace *BM_vert_pair_share_face_by_len(
BMVert *v_a, BMVert *v_b, BMLoop **r_l_a, BMLoop **r_l_b, bool allow_adjacent) ATTR_NONNULL();
/**
* Given 2 verts,
* find a face they share that has the lowest angle across these verts and give back both loops.
*
* This can be better than #BM_vert_pair_share_face_by_len
* because concave splits are ranked lowest.
*/
BMFace *BM_vert_pair_share_face_by_angle(
BMVert *v_a, BMVert *v_b, BMLoop **r_l_a, BMLoop **r_l_b, bool allow_adjacent) ATTR_NONNULL();
BMFace *BM_edge_pair_share_face_by_len(
BMEdge *e_a, BMEdge *e_b, BMLoop **r_l_a, BMLoop **r_l_b, bool allow_adjacent) ATTR_NONNULL();
int BM_vert_edge_count_nonwire(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#define BM_vert_edge_count_is_equal(v, n) (BM_vert_edge_count_at_most(v, (n) + 1) == n)
#define BM_vert_edge_count_is_over(v, n) (BM_vert_edge_count_at_most(v, (n) + 1) == (n) + 1)
int BM_vert_edge_count_at_most(const BMVert *v, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the number of edges around this vertex.
*/
int BM_vert_edge_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#define BM_edge_face_count_is_equal(e, n) (BM_edge_face_count_at_most(e, (n) + 1) == n)
#define BM_edge_face_count_is_over(e, n) (BM_edge_face_count_at_most(e, (n) + 1) == (n) + 1)
int BM_edge_face_count_at_most(const BMEdge *e, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the number of faces around this edge
*/
int BM_edge_face_count(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
#define BM_vert_face_count_is_equal(v, n) (BM_vert_face_count_at_most(v, (n) + 1) == n)
#define BM_vert_face_count_is_over(v, n) (BM_vert_face_count_at_most(v, (n) + 1) == (n) + 1)
int BM_vert_face_count_at_most(const BMVert *v, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the number of faces around this vert
* length matches #BM_LOOPS_OF_VERT iterator
*/
int BM_vert_face_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* The function takes a vertex at the center of a fan and returns the opposite edge in the fan.
* All edges in the fan must be manifold, otherwise return NULL.
*
* \note This could (probably) be done more efficiently.
*/
BMEdge *BM_vert_other_disk_edge(BMVert *v, BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Fast alternative to `(BM_vert_edge_count(v) == 2)`.
*/
bool BM_vert_is_edge_pair(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Fast alternative to `(BM_vert_edge_count(v) == 2)`
* that checks both edges connect to the same faces.
*/
bool BM_vert_is_edge_pair_manifold(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Access a verts 2 connected edges.
*
* \return true when only 2 verts are found.
*/
bool BM_vert_edge_pair(const BMVert *v, BMEdge **r_e_a, BMEdge **r_e_b);
/**
* Return true if the vertex is connected to _any_ faces.
*
* same as `BM_vert_face_count(v) != 0` or `BM_vert_find_first_loop(v) == NULL`.
*/
bool BM_vert_face_check(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Tests whether or not the vertex is part of a wire edge.
* (ie: has no faces attached to it)
*/
bool BM_vert_is_wire(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_wire(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A vertex is non-manifold if it meets the following conditions:
* 1: Loose - (has no edges/faces incident upon it).
* 2: Joins two distinct regions - (two pyramids joined at the tip).
* 3: Is part of an edge with more than 2 faces.
* 4: Is part of a wire edge.
*/
bool BM_vert_is_manifold(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A version of #BM_vert_is_manifold
* which only checks if we're connected to multiple isolated regions.
*/
bool BM_vert_is_manifold_region(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_manifold(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BM_vert_is_boundary(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_boundary(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_edge_is_contiguous(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if the edge is convex or concave
* (depends on face winding)
*/
bool BM_edge_is_convex(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \return true when loop customdata is contiguous.
*/
bool BM_edge_is_contiguous_loop_cd(const BMEdge *e,
int cd_loop_type,
int cd_loop_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* The number of loops connected to this loop (not including disconnected regions).
*/
int BM_loop_region_loops_count_at_most(BMLoop *l, int *r_loop_total) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1);
int BM_loop_region_loops_count(BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1);
/**
* Check if the loop is convex or concave
* (depends on face normal)
*/
bool BM_loop_is_convex(const BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BLI_INLINE bool BM_loop_is_adjacent(const BMLoop *l_a, const BMLoop *l_b) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Check if a point is inside the corner defined by a loop
* (within the 2 planes defined by the loops corner & face normal).
*
* \return signed, squared distance to the loops planes, less than 0.0 when outside.
*/
float BM_loop_point_side_of_loop_test(const BMLoop *l, const float co[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Check if a point is inside the edge defined by a loop
* (within the plane defined by the loops edge & face normal).
*
* \return signed, squared distance to the edge plane, less than 0.0 when outside.
*/
float BM_loop_point_side_of_edge_test(const BMLoop *l, const float co[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \return The previous loop, over \a eps_sq distance from \a l (or \a NULL if l_stop is reached).
*/
BMLoop *BM_loop_find_prev_nodouble(BMLoop *l, BMLoop *l_stop, float eps_sq);
/**
* \return The next loop, over \a eps_sq distance from \a l (or \a NULL if l_stop is reached).
*/
BMLoop *BM_loop_find_next_nodouble(BMLoop *l, BMLoop *l_stop, float eps_sq);
/**
* Calculates the angle between the previous and next loops
* (angle at this loops face corner).
*
* \return angle in radians
*/
float BM_loop_calc_face_angle(const BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BM_loop_calc_face_normal
*
* Calculate the normal at this loop corner or fall back to the face normal on straight lines.
*
* \param l: The loop to calculate the normal at
* \param r_normal: Resulting normal
* \return The length of the cross product (double the area).
*/
float BM_loop_calc_face_normal(const BMLoop *l, float r_normal[3]) ATTR_NONNULL();
/**
* #BM_loop_calc_face_normal_safe_ex with predefined sane epsilon.
*
* Since this doesn't scale based on triangle size, fixed value works well.
*/
float BM_loop_calc_face_normal_safe(const BMLoop *l, float r_normal[3]) ATTR_NONNULL();
/**
* \brief BM_loop_calc_face_normal
*
* Calculate the normal at this loop corner or fall back to the face normal on straight lines.
*
* \param l: The loop to calculate the normal at.
* \param epsilon_sq: Value to avoid numeric errors (1e-5f works well).
* \param r_normal: Resulting normal.
*/
float BM_loop_calc_face_normal_safe_ex(const BMLoop *l, float epsilon_sq, float r_normal[3])
ATTR_NONNULL();
/**
* A version of BM_loop_calc_face_normal_safe_ex which takes vertex coordinates.
*/
float BM_loop_calc_face_normal_safe_vcos_ex(const BMLoop *l,
const float normal_fallback[3],
float const (*vertexCos)[3],
float epsilon_sq,
float r_normal[3]) ATTR_NONNULL();
float BM_loop_calc_face_normal_safe_vcos(const BMLoop *l,
const float normal_fallback[3],
float const (*vertexCos)[3],
float r_normal[3]) ATTR_NONNULL();
/**
* \brief BM_loop_calc_face_direction
*
* Calculate the direction a loop is pointing.
*
* \param l: The loop to calculate the direction at
* \param r_dir: Resulting direction
*/
void BM_loop_calc_face_direction(const BMLoop *l, float r_dir[3]);
/**
* \brief BM_loop_calc_face_tangent
*
* Calculate the tangent at this loop corner or fall back to the face normal on straight lines.
* This vector always points inward into the face.
*
* \param l: The loop to calculate the tangent at
* \param r_tangent: Resulting tangent
*/
void BM_loop_calc_face_tangent(const BMLoop *l, float r_tangent[3]);
/**
* \brief BMESH EDGE/FACE ANGLE
*
* Calculates the angle between two faces.
* Assumes the face normals are correct.
*
* \return angle in radians
*/
float BM_edge_calc_face_angle_ex(const BMEdge *e, float fallback) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
float BM_edge_calc_face_angle(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BMESH EDGE/FACE ANGLE
*
* Calculates the angle between two faces.
* Assumes the face normals are correct.
*
* \return angle in radians
*/
float BM_edge_calc_face_angle_signed_ex(const BMEdge *e, float fallback) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief BMESH EDGE/FACE ANGLE
*
* Calculates the angle between two faces in world space.
* Assumes the face normals are correct.
*
* \return angle in radians
*/
float BM_edge_calc_face_angle_with_imat3_ex(const BMEdge *e,
const float imat3[3][3],
float fallback) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
float BM_edge_calc_face_angle_with_imat3(const BMEdge *e,
const float imat3[3][3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
float BM_edge_calc_face_angle_signed(const BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BMESH EDGE/FACE TANGENT
*
* Calculate the tangent at this loop corner or fallback to the face normal on straight lines.
* This vector always points inward into the face.
*
* \brief BM_edge_calc_face_tangent
* \param e:
* \param e_loop: The loop to calculate the tangent at,
* used to get the face and winding direction.
* \param r_tangent: The loop corner tangent to set
*/
void BM_edge_calc_face_tangent(const BMEdge *e, const BMLoop *e_loop, float r_tangent[3])
ATTR_NONNULL();
float BM_vert_calc_edge_angle(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief BMESH VERT/EDGE ANGLE
*
* Calculates the angle a verts 2 edges.
*
* \returns the angle in radians
*/
float BM_vert_calc_edge_angle_ex(const BMVert *v, float fallback) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \note this isn't optimal to run on an array of verts,
* see 'solidify_add_thickness' for a function which runs on an array.
*/
float BM_vert_calc_shell_factor(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/* alternate version of #BM_vert_calc_shell_factor which only
* uses 'hflag' faces, but falls back to all if none found. */
float BM_vert_calc_shell_factor_ex(const BMVert *v,
const float no[3],
char hflag) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \note quite an obscure function.
* used in bmesh operators that have a relative scale options,
*/
float BM_vert_calc_median_tagged_edge_length(const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Returns the loop of the shortest edge in f.
*/
BMLoop *BM_face_find_shortest_loop(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns the loop of the longest edge in f.
*/
BMLoop *BM_face_find_longest_loop(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
BMEdge *BM_edge_exists(BMVert *v_a, BMVert *v_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns an edge sharing the same vertices as this one.
* This isn't an invalid state but tools should clean up these cases before
* returning the mesh to the user.
*/
BMEdge *BM_edge_find_double(BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Given a set of vertices (varr), find out if
* there is a face with exactly those vertices
* (and only those vertices).
*
* \note there used to be a BM_face_exists_overlap function that checks for partial overlap.
*/
BMFace *BM_face_exists(BMVert *const *varr, int len) ATTR_NONNULL(1);
/**
* Check if a face exists, using a subset of an existing face's loops.
* Edges defined by loops from `l_a` to `l_b` (inclusive) are used to check if they make a face,
* with an implied edge between `l_a->v` & `l_b->v` which must exist for there to be a face.
*
* \param l_a, l_b: First and last loop of the subset.
* \param f_len: Number of loops (vertices) between `l_a` & `l_b` (inclusive).
* \return The matching face if it exists, otherwise null.
*/
BMFace *BM_face_exists_subset_from_face(BMLoop *l_a,
BMLoop *l_b,
int f_len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if the face has an exact duplicate (both winding directions).
*/
BMFace *BM_face_find_double(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Given a set of vertices and edges (\a varr, \a earr), find out if
* all those vertices are filled in by existing faces that _only_ use those vertices.
*
* This is for use in cases where creating a face is possible but would result in
* many overlapping faces.
*
* An example of how this is used: when 2 triangles are selected that share an edge,
* pressing F-key would make a new overlapping quad (without a check like this)
*
* \a earr and \a varr can be in any order, however they _must_ form a closed loop.
*/
bool BM_face_exists_multi(BMVert **varr, BMEdge **earr, int len) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Same as #BM_face_exists_multi but builds the vert array from edges.
*/
bool BM_face_exists_multi_edge(BMEdge **earr, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Given a set of vertices (varr), find out if
* all those vertices overlap an existing face.
*
* \note The face may contain other verts \b not in \a varr.
*
* \note Its possible there are more than one overlapping faces,
* in this case the first one found will be returned.
*
* \param varr: Array of unordered verts.
* \param len: \a varr array length.
* \return The face or NULL.
*/
BMFace *BM_face_exists_overlap(BMVert **varr, int len) ATTR_WARN_UNUSED_RESULT;
/**
* Given a set of vertices (varr), find out if
* there is a face that uses vertices only from this list
* (that the face is a subset or made from the vertices given).
*
* \param varr: Array of unordered verts.
* \param len: varr array length.
*/
bool BM_face_exists_overlap_subset(BMVert **varr, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns the number of faces that are adjacent to both f1 and f2,
* \note Could be sped up a bit by not using iterators and by tagging
* faces on either side, then count the tags rather then searching.
*/
int BM_face_share_face_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Counts the number of edges two faces share (if any)
*/
int BM_face_share_edge_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Counts the number of verts two faces share (if any).
*/
int BM_face_share_vert_count(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* same as #BM_face_share_face_count but returns a bool
*/
bool BM_face_share_face_check(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns true if the faces share an edge
*/
bool BM_face_share_edge_check(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns true if the faces share a vert.
*/
bool BM_face_share_vert_check(BMFace *f_a, BMFace *f_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Returns true when 2 loops share an edge (are adjacent in the face-fan)
*/
bool BM_loop_share_edge_check(BMLoop *l_a, BMLoop *l_b) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Test if e1 shares any faces with e2
*/
bool BM_edge_share_face_check(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Test if e1 shares any quad faces with e2
*/
bool BM_edge_share_quad_check(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Tests to see if e1 shares a vertex with e2
*/
bool BM_edge_share_vert_check(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Return the shared vertex between the two edges or NULL
*/
BMVert *BM_edge_share_vert(BMEdge *e1, BMEdge *e2) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Return the Loop Shared by Edge and Vert
*
* Finds the loop used which uses \a v in face loop \a l
*
* \note this function takes a loop rather than an edge
* so we can select the face that the loop should be from.
*/
BMLoop *BM_edge_vert_share_loop(BMLoop *l, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Return the Loop Shared by Face and Vertex
*
* Finds the loop used which uses \a v in face loop \a l
*
* \note currently this just uses simple loop in future may be sped up
* using radial vars
*/
BMLoop *BM_face_vert_share_loop(BMFace *f, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief Return the Loop Shared by Face and Edge
*
* Finds the loop used which uses \a e in face loop \a l
*
* \note currently this just uses simple loop in future may be sped up
* using radial vars
*/
BMLoop *BM_face_edge_share_loop(BMFace *f, BMEdge *e) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
void BM_edge_ordered_verts(const BMEdge *edge, BMVert **r_v1, BMVert **r_v2) ATTR_NONNULL();
/**
* Returns the verts of an edge as used in a face
* if used in a face at all, otherwise just assign as used in the edge.
*
* Useful to get a deterministic winding order when calling
* BM_face_create_ngon() on an arbitrary array of verts,
* though be sure to pick an edge which has a face.
*
* \note This is in fact quite a simple check,
* mainly include this function so the intent is more obvious.
* We know these 2 verts will _always_ make up the loops edge
*/
void BM_edge_ordered_verts_ex(const BMEdge *edge,
BMVert **r_v1,
BMVert **r_v2,
const BMLoop *edge_loop) ATTR_NONNULL();
bool BM_vert_is_all_edge_flag_test(const BMVert *v,
char hflag,
bool respect_hide) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BM_vert_is_all_face_flag_test(const BMVert *v,
char hflag,
bool respect_hide) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
bool BM_edge_is_all_face_flag_test(const BMEdge *e,
char hflag,
bool respect_hide) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/* convenience functions for checking flags */
bool BM_edge_is_any_vert_flag_test(const BMEdge *e, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_edge_is_any_face_flag_test(const BMEdge *e, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_face_is_any_vert_flag_test(const BMFace *f, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_face_is_any_edge_flag_test(const BMFace *f, char hflag) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool BM_edge_is_any_face_len_test(const BMEdge *e, int len) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Use within asserts to check normals are valid.
*/
bool BM_face_is_normal_valid(const BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
double BM_mesh_calc_volume(BMesh *bm, bool is_signed) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Calculate isolated groups of faces with optional filtering.
*
* \param bm: the BMesh.
* \param r_groups_array: Array of integers to fill in, length of `bm->totface`
* (or when hflag_test is set, the number of flagged faces).
* \param r_group_index: index, length pairs into \a r_groups_array, size of return value
* int pairs: (array_start, array_length).
* \param filter_fn: Filter the edge-loops or vert-loops we step over (depends on \a htype_step).
* \param user_data: Optional user data for \a filter_fn, can be NULL.
* \param hflag_test: Optional flag to test faces,
* use to exclude faces from the calculation, 0 for all faces.
* \param htype_step: BM_VERT to walk over face-verts, BM_EDGE to walk over faces edges
* (having both set is supported too).
* \return The number of groups found.
*/
int BM_mesh_calc_face_groups(BMesh *bm,
int *r_groups_array,
int (**r_group_index)[2],
BMLoopFilterFunc filter_fn,
BMLoopPairFilterFunc filter_pair_fn,
void *user_data,
char hflag_test,
char htype_step) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3);
/**
* Calculate isolated groups of edges with optional filtering.
*
* \param bm: the BMesh.
* \param r_groups_array: Array of ints to fill in, length of `bm->totedge`
* (or when hflag_test is set, the number of flagged edges).
* \param r_group_index: index, length pairs into \a r_groups_array, size of return value
* int pairs: (array_start, array_length).
* \param filter_fn: Filter the edges or verts we step over (depends on \a htype_step)
* as to which types we deal with.
* \param user_data: Optional user data for \a filter_fn, can be NULL.
* \param hflag_test: Optional flag to test edges,
* use to exclude edges from the calculation, 0 for all edges.
* \return The number of groups found.
*
* \note Unlike #BM_mesh_calc_face_groups there is no 'htype_step' argument,
* since we always walk over verts.
*/
int BM_mesh_calc_edge_groups(BMesh *bm,
int *r_groups_array,
int (**r_group_index)[2],
BMVertFilterFunc filter_fn,
void *user_data,
char hflag_test) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3);
/**
* This is an alternative to #BM_mesh_calc_edge_groups.
*
* While we could call this, then create vertex & face arrays,
* it requires looping over geometry connectivity twice,
* this slows down edit-mesh separate by loose parts, see: #70864.
*/
int BM_mesh_calc_edge_groups_as_arrays(BMesh *bm,
BMVert **verts,
BMEdge **edges,
BMFace **faces,
int (**r_groups)[3]) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1, 2, 3, 4, 5);
/* Not really any good place to put this. */
float bmesh_subd_falloff_calc(int falloff, float val) ATTR_WARN_UNUSED_RESULT;
} // namespace blender
#include "bmesh_query_inline.hh" /* IWYU pragma: export */

View File

@@ -0,0 +1,151 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "BLI_utildefines.h"
#include "bmesh_class.hh"
namespace blender {
/**
* Returns whether or not a given vertex is
* is part of a given edge.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE
bool BM_vert_in_edge(const BMEdge *e, const BMVert *v)
{
return (ELEM(v, e->v1, e->v2));
}
/**
* Returns whether or not a given edge is part of a given loop.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
bool BM_edge_in_loop(const BMEdge *e, const BMLoop *l)
{
return (l->e == e || l->prev->e == e);
}
/**
* Returns whether or not two vertices are in
* a given edge
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2, 3) BLI_INLINE
bool BM_verts_in_edge(const BMVert *v1, const BMVert *v2, const BMEdge *e)
{
return ((e->v1 == v1 && e->v2 == v2) || (e->v1 == v2 && e->v2 == v1));
}
/**
* Given a edge and one of its vertices, returns
* the other vertex.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE BMVert *BM_edge_other_vert(BMEdge *e,
const BMVert *v)
{
if (e->v1 == v) {
return e->v2;
}
if (e->v2 == v) {
return e->v1;
}
return nullptr;
}
/**
* Tests whether or not the edge is part of a wire.
* (ie: has no faces attached to it)
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_wire(const BMEdge *e)
{
return (e->l == nullptr);
}
/**
* Tests whether or not this edge is manifold.
* A manifold edge has exactly 2 faces attached to it.
*/
#if 1 /* fast path for checking manifold */
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_manifold(const BMEdge *e)
{
const BMLoop *l = e->l;
return (l && (l->radial_next != l) && /* not 0 or 1 face users */
(l->radial_next->radial_next == l)); /* 2 face users */
}
#else
BLI_INLINE int BM_edge_is_manifold(BMEdge *e)
{
return (BM_edge_face_count(e) == 2);
}
#endif
/**
* Tests that the edge is manifold and
* that both its faces point the same way.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_contiguous(const BMEdge *e)
{
const BMLoop *l = e->l;
const BMLoop *l_other;
return (l && ((l_other = l->radial_next) != l) && /* not 0 or 1 face users */
(l_other->radial_next == l) && /* 2 face users */
(l_other->v != l->v));
}
/**
* Tests whether or not an edge is on the boundary
* of a shell (has one face associated with it)
*/
#if 1 /* fast path for checking boundary */
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_edge_is_boundary(const BMEdge *e)
{
const BMLoop *l = e->l;
return (l && (l->radial_next == l));
}
#else
BLI_INLINE int BM_edge_is_boundary(BMEdge *e)
{
return (BM_edge_face_count(e) == 1);
}
#endif
/**
* Tests whether one loop is next to another within the same face.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE
bool BM_loop_is_adjacent(const BMLoop *l_a, const BMLoop *l_b)
{
BLI_assert(l_a->f == l_b->f);
BLI_assert(l_a != l_b);
return (ELEM(l_b, l_a->next, l_a->prev));
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_loop_is_manifold(const BMLoop *l)
{
return ((l != l->radial_next) && (l == l->radial_next->radial_next));
}
/**
* Check if we have a single wire edge user.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) BLI_INLINE bool BM_vert_is_wire_endpoint(const BMVert *v)
{
const BMEdge *e = v->e;
if (e && e->l == nullptr) {
return (BM_DISK_EDGE_NEXT(e, v) == e);
}
return false;
}
} // namespace blender

View File

@@ -0,0 +1,210 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*/
#include "BLI_array.hh"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_math_vector_types.hh"
#include "BLI_string_ref.hh"
#include "BKE_attribute.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
namespace blender {
BMUVOffsets BM_uv_map_offsets_from_layer(const BMesh *bm, const int layer)
{
using namespace bke;
const int layer_index = CustomData_get_layer_index_n(&bm->ldata, CD_PROP_FLOAT2, layer);
if (layer_index == -1) {
return BMUVOFFSETS_NONE;
}
const StringRef name = bm->ldata.layers[layer_index].name;
char buffer[MAX_CUSTOMDATA_LAYER_NAME];
BMUVOffsets offsets;
offsets.uv = bm->ldata.layers[layer_index].offset;
offsets.pin = CustomData_get_offset_named(
&bm->ldata, CD_PROP_BOOL, BKE_uv_map_pin_name_get(name, buffer));
return offsets;
}
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm)
{
const int layer = CustomData_get_active_layer(&bm->ldata, CD_PROP_FLOAT2);
if (layer == -1) {
return BMUVOFFSETS_NONE;
}
return BM_uv_map_offsets_from_layer(bm, layer);
}
static void uv_aspect(const BMLoop *l,
const float aspect[2],
const int cd_loop_uv_offset,
float r_uv[2])
{
const float *uv = BM_ELEM_CD_GET_FLOAT_P(l, cd_loop_uv_offset);
r_uv[0] = uv[0] * aspect[0];
r_uv[1] = uv[1] * aspect[1];
}
/**
* Typically we avoid hiding arguments,
* make this an exception since it reads poorly with so many repeated arguments.
*/
#define UV_ASPECT(l, r_uv) uv_aspect(l, aspect, cd_loop_uv_offset, r_uv)
void BM_face_uv_calc_center_median_weighted(const BMFace *f,
const float aspect[2],
const int cd_loop_uv_offset,
float r_cent[2])
{
const BMLoop *l_iter;
const BMLoop *l_first;
float totw = 0.0f;
float w_prev;
zero_v2(r_cent);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
float uv_prev[2], uv_curr[2];
UV_ASPECT(l_iter->prev, uv_prev);
UV_ASPECT(l_iter, uv_curr);
w_prev = len_v2v2(uv_prev, uv_curr);
do {
float uv_next[2];
UV_ASPECT(l_iter->next, uv_next);
const float w_curr = len_v2v2(uv_curr, uv_next);
const float w = (w_curr + w_prev);
madd_v2_v2fl(r_cent, uv_curr, w);
totw += w;
w_prev = w_curr;
copy_v2_v2(uv_curr, uv_next);
} while ((l_iter = l_iter->next) != l_first);
if (totw != 0.0f) {
mul_v2_fl(r_cent, 1.0f / totw);
}
/* Reverse aspect. */
r_cent[0] /= aspect[0];
r_cent[1] /= aspect[1];
}
#undef UV_ASPECT
void BM_face_uv_calc_center_median(const BMFace *f, const int cd_loop_uv_offset, float r_cent[2])
{
const BMLoop *l_iter;
const BMLoop *l_first;
zero_v2(r_cent);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
add_v2_v2(r_cent, luv);
} while ((l_iter = l_iter->next) != l_first);
mul_v2_fl(r_cent, 1.0f / float(f->len));
}
float BM_face_uv_calc_cross(const BMFace *f, const int cd_loop_uv_offset)
{
Array<float2, BM_DEFAULT_NGON_STACK_SIZE> uvs(f->len);
const BMLoop *l_iter;
const BMLoop *l_first;
int i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
uvs[i++] = BM_ELEM_CD_GET_FLOAT2_P(l_iter, cd_loop_uv_offset);
} while ((l_iter = l_iter->next) != l_first);
return cross_poly_v2(reinterpret_cast<const float (*)[2]>(uvs.data()), f->len);
}
void BM_face_uv_minmax(const BMFace *f, float min[2], float max[2], const int cd_loop_uv_offset)
{
const BMLoop *l_iter;
const BMLoop *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
const float *luv = BM_ELEM_CD_GET_FLOAT_P(l_iter, cd_loop_uv_offset);
minmax_v2v2_v2(min, max, luv);
} while ((l_iter = l_iter->next) != l_first);
}
bool BM_loop_uv_share_edge_check(const BMLoop *l_a, const BMLoop *l_b, const int cd_loop_uv_offset)
{
BLI_assert(l_a->e == l_b->e);
const float *luv_a_curr = BM_ELEM_CD_GET_FLOAT_P(l_a, cd_loop_uv_offset);
const float *luv_a_next = BM_ELEM_CD_GET_FLOAT_P(l_a->next, cd_loop_uv_offset);
const float *luv_b_curr = BM_ELEM_CD_GET_FLOAT_P(l_b, cd_loop_uv_offset);
const float *luv_b_next = BM_ELEM_CD_GET_FLOAT_P(l_b->next, cd_loop_uv_offset);
if (l_a->v != l_b->v) {
std::swap(luv_b_curr, luv_b_next);
}
return (equals_v2v2(luv_a_curr, luv_b_curr) && equals_v2v2(luv_a_next, luv_b_next));
}
bool BM_loop_uv_share_vert_check(const BMLoop *l_a, const BMLoop *l_b, const int cd_loop_uv_offset)
{
BLI_assert(l_a->v == l_b->v);
const float *luv_a = BM_ELEM_CD_GET_FLOAT_P(l_a, cd_loop_uv_offset);
const float *luv_b = BM_ELEM_CD_GET_FLOAT_P(l_b, cd_loop_uv_offset);
if (!equals_v2v2(luv_a, luv_b)) {
return false;
}
return true;
}
bool BM_edge_uv_share_vert_check(const BMEdge *e,
const BMLoop *l_a,
const BMLoop *l_b,
const int cd_loop_uv_offset)
{
BLI_assert(l_a->v == l_b->v);
if (!BM_loop_uv_share_vert_check(l_a, l_b, cd_loop_uv_offset)) {
return false;
}
/* No need for null checks, these will always succeed. */
const BMLoop *l_other_a = BM_loop_other_vert_loop_by_edge(const_cast<BMLoop *>(l_a),
const_cast<BMEdge *>(e));
const BMLoop *l_other_b = BM_loop_other_vert_loop_by_edge(const_cast<BMLoop *>(l_b),
const_cast<BMEdge *>(e));
{
const float *luv_other_a = BM_ELEM_CD_GET_FLOAT_P(l_other_a, cd_loop_uv_offset);
const float *luv_other_b = BM_ELEM_CD_GET_FLOAT_P(l_other_b, cd_loop_uv_offset);
if (!equals_v2v2(luv_other_a, luv_other_b)) {
return false;
}
}
return true;
}
bool BM_face_uv_point_inside_test(const BMFace *f, const float co[2], const int cd_loop_uv_offset)
{
Array<float2, BM_DEFAULT_NGON_STACK_SIZE> projverts(f->len);
BMLoop *l_iter;
int i;
BLI_assert(BM_face_is_normal_valid(f));
for (i = 0, l_iter = BM_FACE_FIRST_LOOP(f); i < f->len; i++, l_iter = l_iter->next) {
projverts[i] = BM_ELEM_CD_GET_FLOAT2_P(l_iter, cd_loop_uv_offset);
}
return isect_point_poly_v2(co, reinterpret_cast<const float (*)[2]>(projverts.data()), f->len);
}
} // namespace blender

View File

@@ -0,0 +1,94 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "BKE_customdata.hh"
#include "bmesh_class.hh"
namespace blender {
/**
* Retrieve the custom data offsets for the UV map.
* \param layer: The layer index (where 0 is the first UV map).
* \return The layer offsets or -1 when not found.
*/
BMUVOffsets BM_uv_map_offsets_from_layer(const BMesh *bm, int layer);
/**
* Retrieve the custom data offsets for layers used for user interaction with the active UV map.
* \return The layer offsets or -1 when not found.
*/
BMUVOffsets BM_uv_map_offsets_get(const BMesh *bm);
float BM_loop_uv_calc_edge_length_squared(const BMLoop *l,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
float BM_loop_uv_calc_edge_length(const BMLoop *l, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Computes the UV center of a face, using the mean average weighted by edge length.
*
* See #BM_face_calc_center_median_weighted for matching spatial functionality.
*
* \param aspect: Calculate the center scaling by these values, and finally dividing.
* Since correct weighting depends on having the correct aspect.
*/
void BM_face_uv_calc_center_median_weighted(const BMFace *f,
const float aspect[2],
int cd_loop_uv_offset,
float r_cent[2]) ATTR_NONNULL();
void BM_face_uv_calc_center_median(const BMFace *f, int cd_loop_uv_offset, float r_cent[2])
ATTR_NONNULL();
/**
* Calculate the UV cross product (use the sign to check the winding).
*/
float BM_face_uv_calc_cross(const BMFace *f, int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
void BM_face_uv_minmax(const BMFace *f, float min[2], float max[2], int cd_loop_uv_offset);
bool BM_loop_uv_share_edge_check_with_limit(const BMLoop *l_a,
const BMLoop *l_b,
const float limit[2],
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Check if two loops that share an edge also have the same UV coordinates.
*/
bool BM_loop_uv_share_edge_check(const BMLoop *l_a,
const BMLoop *l_b,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if two loops that share a vertex also have the same UV coordinates.
*/
bool BM_edge_uv_share_vert_check(const BMEdge *e,
const BMLoop *l_a,
const BMLoop *l_b,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if two loops that share a vertex also have the same UV coordinates.
*/
bool BM_loop_uv_share_vert_check(const BMLoop *l_a,
const BMLoop *l_b,
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Check if the point is inside the UV face.
*/
bool BM_face_uv_point_inside_test(const BMFace *f,
const float co[2],
int cd_loop_uv_offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
} // namespace blender

View File

@@ -0,0 +1,573 @@
/* SPDX-FileCopyrightText: 2007 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Low level routines for manipulating the BM structure.
*/
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_private.hh"
namespace blender {
/**
* MISC utility functions.
*/
void bmesh_disk_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src)
{
if (e->v1 == v_src) {
e->v1 = v_dst;
e->v1_disk_link.next = e->v1_disk_link.prev = nullptr;
}
else if (e->v2 == v_src) {
e->v2 = v_dst;
e->v2_disk_link.next = e->v2_disk_link.prev = nullptr;
}
else {
BLI_assert(0);
}
}
void bmesh_edge_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src)
{
/* swap out loops */
if (e->l) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
if (l_iter->v == v_src) {
l_iter->v = v_dst;
}
else if (l_iter->next->v == v_src) {
l_iter->next->v = v_dst;
}
else {
BLI_assert(l_iter->prev->v != v_src);
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
/* swap out edges */
bmesh_disk_vert_replace(e, v_dst, v_src);
}
void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src)
{
BLI_assert(e->v1 == v_src || e->v2 == v_src);
bmesh_disk_edge_remove(e, v_src); /* Remove `e` from `v_src` disk cycle. */
bmesh_disk_vert_swap(e, v_dst, v_src); /* Swap out `v_src` for `v_dst` in `e`. */
bmesh_disk_edge_append(e, v_dst); /* Add `e` to `v_dst` disk cycle. */
BLI_assert(e->v1 != e->v2);
}
/**
* \section bm_cycles BMesh Cycles
*
* NOTE(@joeedh): this is somewhat outdated, though bits of its API are still used.
*
* Cycles are circular doubly linked lists that form the basis of adjacency
* information in the BME modeler. Full adjacency relations can be derived
* from examining these cycles very quickly. Although each cycle is a double
* circular linked list, each one is considered to have a 'base' or 'head',
* and care must be taken by Euler code when modifying the contents of a cycle.
*
* The contents of this file are split into two parts. First there are the
* bmesh_cycle family of functions which are generic circular double linked list
* procedures. The second part contains higher level procedures for supporting
* modification of specific cycle types.
*
* The three cycles explicitly stored in the BM data structure are as follows:
* 1: The Disk Cycle - A circle of edges around a vertex
* Base: vertex->edge pointer.
*
* This cycle is the most complicated in terms of its structure. Each bmesh_Edge contains
* two bmesh_CycleNode structures to keep track of that edges membership in the disk cycle
* of each of its vertices. However for any given vertex it may be the first in some edges
* in its disk cycle and the second for others. The bmesh_disk_XXX family of functions contain
* some nice utilities for navigating disk cycles in a way that hides this detail from the
* tool writer.
*
* Note that the disk cycle is completely independent from face data. One advantage of this
* is that wire edges are fully integrated into the topology database. Another is that the
* the disk cycle has no problems dealing with non-manifold conditions involving faces.
*
* Functions relating to this cycle:
* - #bmesh_disk_vert_replace
* - #bmesh_disk_edge_append
* - #bmesh_disk_edge_remove
* - #bmesh_disk_edge_next
* - #bmesh_disk_edge_prev
* - #bmesh_disk_facevert_count
* - #bmesh_disk_faceedge_find_first
* - #bmesh_disk_faceedge_find_next
* 2: The Radial Cycle - A circle of face edges (bmesh_Loop) around an edge
* Base: edge->l->radial structure.
*
* The radial cycle is similar to the radial cycle in the radial edge data structure.*
* Unlike the radial edge however, the radial cycle does not require a large amount of memory
* to store non-manifold conditions since BM does not keep track of region/shell information.
*
* Functions relating to this cycle:
* - #bmesh_radial_loop_append
* - #bmesh_radial_loop_remove
* - #bmesh_radial_facevert_count
* - #bmesh_radial_facevert_check
* - #bmesh_radial_faceloop_find_first
* - #bmesh_radial_faceloop_find_next
* - #bmesh_radial_validate
* 3: The Loop Cycle - A circle of face edges around a polygon.
* Base: polygon->lbase.
*
* The loop cycle keeps track of a faces vertices and edges. It should be noted that the
* direction of a loop cycle is either CW or CCW depending on the face normal, and is
* not oriented to the faces edit-edges.
*
* Functions relating to this cycle:
* - bmesh_cycle_XXX family of functions.
* \note the order of elements in all cycles except the loop cycle is undefined. This
* leads to slightly increased seek time for deriving some adjacency relations, however the
* advantage is that no intrinsic properties of the data structures are dependent upon the
* cycle order and all non-manifold conditions are represented trivially.
*/
void bmesh_disk_edge_append(BMEdge *e, BMVert *v)
{
if (!v->e) {
BMDiskLink *dl1 = bmesh_disk_edge_link_from_vert(e, v);
v->e = e;
dl1->next = dl1->prev = e;
}
else {
BMDiskLink *dl1, *dl2, *dl3;
dl1 = bmesh_disk_edge_link_from_vert(e, v);
dl2 = bmesh_disk_edge_link_from_vert(v->e, v);
dl3 = dl2->prev ? bmesh_disk_edge_link_from_vert(dl2->prev, v) : nullptr;
dl1->next = v->e;
dl1->prev = dl2->prev;
dl2->prev = e;
if (dl3) {
dl3->next = e;
}
}
}
void bmesh_disk_edge_remove(BMEdge *e, BMVert *v)
{
BMDiskLink *dl1, *dl2;
dl1 = bmesh_disk_edge_link_from_vert(e, v);
if (dl1->prev) {
dl2 = bmesh_disk_edge_link_from_vert(dl1->prev, v);
dl2->next = dl1->next;
}
if (dl1->next) {
dl2 = bmesh_disk_edge_link_from_vert(dl1->next, v);
dl2->prev = dl1->prev;
}
if (v->e == e) {
v->e = (e != dl1->next) ? dl1->next : nullptr;
}
dl1->next = dl1->prev = nullptr;
}
BMEdge *bmesh_disk_edge_exists(const BMVert *v1, const BMVert *v2)
{
if (v1->e) {
BMEdge *e_iter, *e_first;
e_first = e_iter = v1->e;
do {
if (BM_verts_in_edge(v1, v2, e_iter)) {
return e_iter;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v1)) != e_first);
}
return nullptr;
}
int bmesh_disk_count(const BMVert *v)
{
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
e_iter = e_first = v->e;
do {
count++;
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
int bmesh_disk_count_at_most(const BMVert *v, const int count_max)
{
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
e_iter = e_first = v->e;
do {
count++;
if (count == count_max) {
break;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
bool bmesh_disk_validate(int len, BMEdge *e, BMVert *v)
{
BMEdge *e_iter;
if (!BM_vert_in_edge(e, v)) {
return false;
}
if (len == 0 || bmesh_disk_count_at_most(v, len + 1) != len) {
return false;
}
e_iter = e;
do {
if (len != 1 && bmesh_disk_edge_prev(e_iter, v) == e_iter) {
return false;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return true;
}
int bmesh_disk_facevert_count(const BMVert *v)
{
/* is there an edge on this vert at all */
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
/* first, loop around edge */
e_first = e_iter = v->e;
do {
if (e_iter->l) {
count += bmesh_radial_facevert_count(e_iter->l, v);
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
int bmesh_disk_facevert_count_at_most(const BMVert *v, const int count_max)
{
/* is there an edge on this vert at all */
int count = 0;
if (v->e) {
BMEdge *e_first, *e_iter;
/* first, loop around edge */
e_first = e_iter = v->e;
do {
if (e_iter->l) {
count += bmesh_radial_facevert_count_at_most(e_iter->l, v, count_max - count);
if (count == count_max) {
break;
}
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e_first);
}
return count;
}
BMEdge *bmesh_disk_faceedge_find_first(const BMEdge *e, const BMVert *v)
{
const BMEdge *e_iter = e;
do {
if (e_iter->l != nullptr) {
return const_cast<BMEdge *>((e_iter->l->v == v) ? e_iter : e_iter->l->next->e);
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return nullptr;
}
BMLoop *bmesh_disk_faceloop_find_first(const BMEdge *e, const BMVert *v)
{
const BMEdge *e_iter = e;
do {
if (e_iter->l != nullptr) {
return (e_iter->l->v == v) ? e_iter->l : e_iter->l->next;
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return nullptr;
}
BMLoop *bmesh_disk_faceloop_find_first_visible(const BMEdge *e, const BMVert *v)
{
const BMEdge *e_iter = e;
do {
if (!BM_elem_flag_test(e_iter, BM_ELEM_HIDDEN)) {
if (e_iter->l != nullptr) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e_iter->l;
do {
if (!BM_elem_flag_test(l_iter->f, BM_ELEM_HIDDEN)) {
return (l_iter->v == v) ? l_iter : l_iter->next;
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
} while ((e_iter = bmesh_disk_edge_next(e_iter, v)) != e);
return nullptr;
}
BMEdge *bmesh_disk_faceedge_find_next(const BMEdge *e, const BMVert *v)
{
BMEdge *e_find;
e_find = bmesh_disk_edge_next(e, v);
do {
if (e_find->l && bmesh_radial_facevert_check(e_find->l, v)) {
return e_find;
}
} while ((e_find = bmesh_disk_edge_next(e_find, v)) != e);
return const_cast<BMEdge *>(e);
}
bool bmesh_radial_validate(int radlen, BMLoop *l)
{
BMLoop *l_iter = l;
int i = 0;
if (bmesh_radial_length(l) != radlen) {
return false;
}
do {
if (UNLIKELY(!l_iter)) {
BMESH_ASSERT(0);
return false;
}
if (l_iter->e != l->e) {
return false;
}
if (!ELEM(l_iter->v, l->e->v1, l->e->v2)) {
return false;
}
if (UNLIKELY(i > BM_LOOP_RADIAL_MAX)) {
BMESH_ASSERT(0);
return false;
}
i++;
} while ((l_iter = l_iter->radial_next) != l);
return true;
}
void bmesh_radial_loop_append(BMEdge *e, BMLoop *l)
{
if (e->l == nullptr) {
e->l = l;
l->radial_next = l->radial_prev = l;
}
else {
l->radial_prev = e->l;
l->radial_next = e->l->radial_next;
e->l->radial_next->radial_prev = l;
e->l->radial_next = l;
e->l = l;
}
if (UNLIKELY(l->e && l->e != e)) {
/* l is already in a radial cycle for a different edge */
BMESH_ASSERT(0);
}
l->e = e;
}
void bmesh_radial_loop_remove(BMEdge *e, BMLoop *l)
{
/* if e is non-nullptr, l must be in the radial cycle of e */
if (UNLIKELY(e != l->e)) {
BMESH_ASSERT(0);
}
if (l->radial_next != l) {
if (l == e->l) {
e->l = l->radial_next;
}
l->radial_next->radial_prev = l->radial_prev;
l->radial_prev->radial_next = l->radial_next;
}
else {
if (l == e->l) {
e->l = nullptr;
}
else {
BMESH_ASSERT(0);
}
}
/* l is no longer in a radial cycle; empty the links
* to the cycle and the link back to an edge */
l->radial_next = l->radial_prev = nullptr;
l->e = nullptr;
}
void bmesh_radial_loop_unlink(BMLoop *l)
{
if (l->radial_next != l) {
l->radial_next->radial_prev = l->radial_prev;
l->radial_prev->radial_next = l->radial_next;
}
/* l is no longer in a radial cycle; empty the links
* to the cycle and the link back to an edge */
l->radial_next = l->radial_prev = nullptr;
l->e = nullptr;
}
BMLoop *bmesh_radial_faceloop_find_first(const BMLoop *l, const BMVert *v)
{
const BMLoop *l_iter;
l_iter = l;
do {
if (l_iter->v == v) {
return const_cast<BMLoop *>(l_iter);
}
} while ((l_iter = l_iter->radial_next) != l);
return nullptr;
}
BMLoop *bmesh_radial_faceloop_find_next(const BMLoop *l, const BMVert *v)
{
BMLoop *l_iter;
l_iter = l->radial_next;
do {
if (l_iter->v == v) {
return l_iter;
}
} while ((l_iter = l_iter->radial_next) != l);
return const_cast<BMLoop *>(l);
}
int bmesh_radial_length(const BMLoop *l)
{
const BMLoop *l_iter = l;
int i = 0;
if (!l) {
return 0;
}
do {
if (UNLIKELY(!l_iter)) {
/* Radial cycle is broken (not a circular loop). */
BMESH_ASSERT(0);
return 0;
}
i++;
if (UNLIKELY(i >= BM_LOOP_RADIAL_MAX)) {
BMESH_ASSERT(0);
return -1;
}
} while ((l_iter = l_iter->radial_next) != l);
return i;
}
int bmesh_radial_facevert_count(const BMLoop *l, const BMVert *v)
{
const BMLoop *l_iter;
int count = 0;
l_iter = l;
do {
if (l_iter->v == v) {
count++;
}
} while ((l_iter = l_iter->radial_next) != l);
return count;
}
int bmesh_radial_facevert_count_at_most(const BMLoop *l, const BMVert *v, const int count_max)
{
const BMLoop *l_iter;
int count = 0;
l_iter = l;
do {
if (l_iter->v == v) {
count++;
if (count == count_max) {
break;
}
}
} while ((l_iter = l_iter->radial_next) != l);
return count;
}
bool bmesh_radial_facevert_check(const BMLoop *l, const BMVert *v)
{
const BMLoop *l_iter;
l_iter = l;
do {
if (l_iter->v == v) {
return true;
}
} while ((l_iter = l_iter->radial_next) != l);
return false;
}
bool bmesh_loop_validate(BMFace *f)
{
int i;
int len = f->len;
BMLoop *l_iter, *l_first;
l_first = BM_FACE_FIRST_LOOP(f);
if (l_first == nullptr) {
return false;
}
/* Validate that the face loop cycle is the length specified by f->len */
for (i = 1, l_iter = l_first->next; i < len; i++, l_iter = l_iter->next) {
if ((l_iter->f != f) || (l_iter == l_first)) {
return false;
}
}
if (l_iter != l_first) {
return false;
}
/* Validate the loop->prev links also form a cycle of length f->len */
for (i = 1, l_iter = l_first->prev; i < len; i++, l_iter = l_iter->prev) {
if (l_iter == l_first) {
return false;
}
}
if (l_iter != l_first) {
return false;
}
return true;
}
} // namespace blender

View File

@@ -0,0 +1,169 @@
/* SPDX-FileCopyrightText: 2004 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* The lowest level of functionality for manipulating bmesh structures.
* None of these functions should ever be exported to the rest of Blender.
*
* in the vast majority of cases there shouldn't be used directly.
* if absolutely necessary, see function definitions in code for
* descriptive comments. but seriously, don't use this stuff.
*/
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
namespace blender {
/* -------------------------------------------------------------------- */
/** \name Loop Cycle Management
* Loop cycle functions, e.g. loops surrounding a face.
* \{ */
bool bmesh_loop_validate(BMFace *f) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Disk Cycle Management
* \{ */
void bmesh_disk_edge_append(BMEdge *e, BMVert *v) ATTR_NONNULL();
void bmesh_disk_edge_remove(BMEdge *e, BMVert *v) ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_next_safe(const BMEdge *e,
const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_prev_safe(const BMEdge *e,
const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_next(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BLI_INLINE BMEdge *bmesh_disk_edge_prev(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
int bmesh_disk_facevert_count_at_most(const BMVert *v, int count_max) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief DISK COUNT FACE VERT
*
* Counts the number of loop users
* for this vertex. Note that this is
* equivalent to counting the number of
* faces incident upon this vertex
*/
int bmesh_disk_facevert_count(const BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief FIND FIRST FACE EDGE
*
* Finds the first edge in a vertices
* Disk cycle that has one of this
* vert's loops attached
* to it.
*/
BMEdge *bmesh_disk_faceedge_find_first(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* Special case for BM_LOOPS_OF_VERT & BM_FACES_OF_VERT, avoids 2x calls.
*
* The returned BMLoop.e matches the result of #bmesh_disk_faceedge_find_first
*/
BMLoop *bmesh_disk_faceloop_find_first(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* A version of #bmesh_disk_faceloop_find_first that ignores hidden faces.
*/
BMLoop *bmesh_disk_faceloop_find_first_visible(const BMEdge *e,
const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BMEdge *bmesh_disk_faceedge_find_next(const BMEdge *e, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Radial Cycle Management
*
* Radial cycle functions, e.g. loops surrounding edges.
* \{ */
void bmesh_radial_loop_append(BMEdge *e, BMLoop *l) ATTR_NONNULL();
/**
* \brief BMESH RADIAL REMOVE LOOP
*
* Removes a loop from an radial cycle. If edge e is non-NULL
* it should contain the radial cycle, and it will also get
* updated (in the case that the edge's link into the radial
* cycle was the loop which is being removed from the cycle).
*/
void bmesh_radial_loop_remove(BMEdge *e, BMLoop *l) ATTR_NONNULL();
/**
* A version of #bmesh_radial_loop_remove which only performs the radial unlink,
* leaving the edge untouched.
*/
void bmesh_radial_loop_unlink(BMLoop *l) ATTR_NONNULL();
/* NOTE:
* bmesh_radial_loop_next(BMLoop *l) / prev.
* just use member access l->radial_next, l->radial_prev now */
int bmesh_radial_facevert_count_at_most(const BMLoop *l,
const BMVert *v,
int count_max) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* \brief RADIAL COUNT FACE VERT
*
* Returns the number of times a vertex appears
* in a radial cycle
*/
int bmesh_radial_facevert_count(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief RADIAL CHECK FACE VERT
*
* Quicker check for `bmesh_radial_facevert_count(...) != 0`.
*/
bool bmesh_radial_facevert_check(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \brief BME RADIAL FIND FIRST FACE VERT
*
* Finds the first loop of v around radial
* cycle
*/
BMLoop *bmesh_radial_faceloop_find_first(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BMLoop *bmesh_radial_faceloop_find_next(const BMLoop *l, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
BMLoop *bmesh_radial_faceloop_find_vert(const BMFace *f, const BMVert *v) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool bmesh_radial_validate(int radlen, BMLoop *l) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** \} */
/* -------------------------------------------------------------------- */
/** \name Edge Utilities
* \{ */
void bmesh_disk_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src) ATTR_NONNULL();
/**
* Handles all connected data, use with care.
*
* Assumes caller has setup correct state before the swap is done.
*/
void bmesh_edge_vert_swap(BMEdge *e, BMVert *v_dst, BMVert *v_src) ATTR_NONNULL();
void bmesh_disk_vert_replace(BMEdge *e, BMVert *v_dst, BMVert *v_src) ATTR_NONNULL();
BMEdge *bmesh_disk_edge_exists(const BMVert *v1, const BMVert *v2) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
bool bmesh_disk_validate(int len, BMEdge *e, BMVert *v) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/** \} */
} // namespace blender
#include "intern/bmesh_structure_inline.hh" /* IWYU pragma: export */

View File

@@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh inline operator functions.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_compiler_compat.h"
#include "bmesh_class.hh"
#include "intern/bmesh_query.hh"
namespace blender {
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2)
BLI_INLINE BMDiskLink *bmesh_disk_edge_link_from_vert(const BMEdge *e, const BMVert *v)
{
BLI_assert(BM_vert_in_edge(e, v));
return const_cast<BMDiskLink *>(&(&e->v1_disk_link)[v == e->v2]);
}
/**
* \brief Next Disk Edge
*
* Find the next edge in a disk cycle
*
* \return Pointer to the next edge in the disk cycle for the vertex v.
*/
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
BLI_INLINE BMEdge *bmesh_disk_edge_next_safe(const BMEdge *e, const BMVert *v)
{
if (v == e->v1) {
return e->v1_disk_link.next;
}
if (v == e->v2) {
return e->v2_disk_link.next;
}
return nullptr;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
BLI_INLINE BMEdge *bmesh_disk_edge_prev_safe(const BMEdge *e, const BMVert *v)
{
if (v == e->v1) {
return e->v1_disk_link.prev;
}
if (v == e->v2) {
return e->v2_disk_link.prev;
}
return nullptr;
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE BMEdge *bmesh_disk_edge_next(const BMEdge *e,
const BMVert *v)
{
return BM_DISK_EDGE_NEXT(e, v);
}
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1, 2) BLI_INLINE BMEdge *bmesh_disk_edge_prev(const BMEdge *e,
const BMVert *v)
{
return BM_DISK_EDGE_PREV(e, v);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,581 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "bmesh_class.hh"
#include "BLI_vector_list.hh"
namespace blender {
/** \file
* \ingroup bmesh
*
* Overview
* ========
*
* The `BM_uvselect_*` API deals with synchronizing selection
* between UV's and selected vertices edges & faces,
* where a selected vertex in the 3D viewport may only have some of its
* UV vertices selected in the UV editor.
*
* Supporting this involves flushing in both directions depending on the selection being edited.
*
* \note See #78393 for a user-level overview of this functionality.
* This describes the motivation to synchronize selection between UV's and the mesh.
*
* \note A short-hand term for vertex/edge/face selection used
* in this file is View3D abbreviated to `v3d`, since this is the section
* manipulated in the viewport, e.g. #BM_mesh_uvselect_sync_to_mesh.
*
* \note This is quite involved. As a last resort the UV selection can always be cleared
* and re-set from the mesh (v3d) selection, however it's good to keep UV selection
* if possible because resetting may extend vertex selection to other UV islands.
*
* Terms
* =====
*
* - Synchronized Selection (abbreviated to "sync"). See #BMesh::uv_select_sync_valid.
* When the UV synchronized data is valid, it means there is a valid relationship
* between the UV selection flags (#BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE)
* and the meshes selection (#BM_ELEM_SELECT_UV).
*
* - When the UV selection changes (from the UV editor)
* this needs to be synchronized to the mesh.
* - When the base-selection flags change (from the 3D viewport)
* this needs to be synchronized to the UV's.
* Synchronizing in this direction may be lossy, although (depending on the operation),
* support for maintaining a synchronized selection may be possible.
*
* - Flushing Selection ("flush")
* When an element is selected or de-selected, the selection state
* of connected geometry may change too.
* So, de-selecting a vertex must de-select all faces that use that vertex.
*
* The rules for flushing may depend on the selection mode.
* When de-selecting a face in vertex-select-mode, all its vertices & edges
* must also be de-selected. When de-selecting a face in face-select-mode,
* only vertices and edges no longer connected to any selected faces will be de-selected.
*
* Since applying these rules while selecting individual elements is often impractical,
* it's common to adjust the selection, then flush based on the selection mode afterwards.
*
* - Flushing up:
* Flushing the selection from [verts -> edges/faces], [edges -> faces].
* - Flushing down:
* Flushing the selection from [faces -> verts/edges], [edges -> verts].
*
* - Isolated vertex or edge selection:
* When a vertex or edge is selected without being connected to a selected face.
*
* UV Selection Flags
* ==================
*
* - UV selection uses:
* - #BM_ELEM_SELECT_UV & #BM_ELEM_SELECT_UV_EDGE for #BMLoop
* to define selected vertices & edges.
* - #BM_ELEM_SELECT_UV for #BMFace.
*
* Hidden Flags
* ============
*
* Unlike viewport selection there is no requirement for hidden elements not to be selected.
* Therefor, UV selection checks must check the underlying geometry is not hidden.
* In practice this means hidden faces must be assumed unselected,
* since UV's are part of the faces (there is no such thing as a hidden face-corner)
* and any hidden edge or vertex causes connected faces to be hidden.
*
* UV Selection Flushing
* =====================
*
* Selection setting functions flush down (unless the `_noflush(...)` version is used),
* this means selecting a face also selects all verts & edges,
* selecting an edge selects its vertices.
*
* However it's expected the selection is flushed,
* de-selecting a vertex or edge must de-select it's faces (flushing up).
* For this, there are various flushing functions,
* exactly what is needed depends on the selection operation performed and the selection mode.
*
* There are also situations that shouldn't be allowed such as a single selected vertex in face
* select mode.
*
* Flushing & Synchronizing
* ========================
*
* Properly handling the selection state is important for operators that adjust the UV selection.
* This typically involves the following steps:
*
* - The UV selection changes.
* - The UV selection must be flushed between elements to ensure the selection is valid,
* (see: `BM_mesh_uvselect_flush_*` & `BM_mesh_uvselect_mode_flush_*` functions).
* - The UV selection must be synchronized to the mesh selection
* (see #BM_mesh_uvselect_sync_to_mesh).
* - The mesh must then flush selection to its elements
* (see: `BM_mesh_select_flush_*` & `BM_mesh_select_mode_flush_*` functions).
*
* Valid State
* ===========
*
* For a valid state:
* - A selected UV-vertex must have its underlying mesh vertex selected.
* - A selected mesh-vertex must have at least one UV-vertex selected.
*
* This is *mostly* true for edges/faces too, however there cases where
* the UV selection causes an edge/face to be selected in mesh space but not UV space.
*
* See #BM_mesh_uvselect_is_valid for details.
*
* Clearing the Valid State
* ========================
*
* As already noted, tools should maintain the synchronized UV selection where possible.
* However when this information *isn't* needed it should be cleared aggressively
* (see #BM_mesh_uvselect_clear), since it adds both computation & memory overhead.
*
* For actions that overwrite the selection such as selecting or de-selecting all,
* it's safe to "clear" the data, other actions such as adding new geometry that replaces
* the selection can also safely "clear" the UV selection.
*
* In practice users modeling in the 3D viewport are likely to clear the UV selection data
* since selecting the mesh without extending the selection is effectively a "De-select All".
* So the chances this data persists when it's not needed over many editing operations are low.
*/
/* -------------------------------------------------------------------- */
/** \name UV Selection Functions (low level)
*
* Selection checking functions.
* These should be used instead of checking #BM_ELEM_SELECT_UV,
* so hidden geometry is never considered selected.
* \{ */
bool BM_face_uvselect_test(const BMFace *f);
bool BM_loop_vert_uvselect_test(const BMLoop *l);
bool BM_loop_edge_uvselect_test(const BMLoop *l);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Connectivity Checks
*
* Regarding the `hflag` parameter: this is typically set to:
* - #BM_ELEM_SELECT for mesh selection.
* - #BM_ELEM_SELECT_UV for selected UV vertices.
* - #BM_ELEM_SELECT_UV_EDGE for selected UV edges.
* - #BM_ELEM_SELECT_TAG to allow the caller to use a separate non-selection flag.
*
* Each function asserts that a supported `hflag` is passed in.
* \{ */
bool BM_loop_vert_uvselect_check_other_loop_vert(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_vert_uvselect_check_other_loop_edge(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_vert_uvselect_check_other_edge(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_vert_uvselect_check_other_face(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_edge_uvselect_check_other_loop_edge(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_loop_edge_uvselect_check_other_face(BMLoop *l, char hflag, int cd_loop_uv_offset);
bool BM_face_uvselect_check_edges_all(BMFace *f);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Functions
* \{ */
/** Set the UV selection flag for `f` without flushing down to edges & vertices. */
void BM_face_uvselect_set_noflush(BMesh *bm, BMFace *f, bool select);
/** Set the UV selection flag for `f` & flush down to edges & vertices. */
void BM_face_uvselect_set(BMesh *bm, BMFace *f, bool select);
/** Set the UV selection flag for `e` without flushing down to vertices. */
void BM_loop_edge_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select);
/** Set the UV selection flag for `e` & flush down to vertices. */
void BM_loop_edge_uvselect_set(BMesh *bm, BMLoop *l, bool select);
/**
* Set the UV selection flag for `v` without flushing down.
* since there is nothing to flush down to.
*/
void BM_loop_vert_uvselect_set_noflush(BMesh *bm, BMLoop *l, bool select);
/**
* Call this function when selecting mesh elements in the viewport and
* the relationship with UV's is lost.
*
* \return True if UV select is cleared (a change was made).
*
* This has two purposes:
*
* - Maintaining the UV selection isn't needed:
* Some operations such as adding a new mesh primitive clear the selection,
* selecting all geometry from the new primitive.
* In this case a UV selection is redundant & should be cleared.
*
* - Maintaining the UV selection isn't supported:
* Some selection operations don't support maintaining a valid UV selection,
* in that case it's necessary to clear the UV selection otherwise tools may
* seem to be broken if they aren't operating on the selection properly.
*
* NOTE(@ideasman42): It's worth noting that in this case clearing the selection is "lossy",
* users may wish that all selection operations would handle UV selection data too.
* Supporting additional operations is always possible, at the time of writing it's
* impractical to do so, see: #131642 design task for details.
*
* Internally this marks the UV selection data as invalid,
* using the mesh selection as the "source-of-truth".
*
* \note By convention call this immediately after flushing.
*
* \note In many cases the UV selection can be maintained and this function removed,
* although it adds some complexity & overhead.
* See #UVSyncSelectFromMesh.
*
* \note Calls to this function that should *not* be removed in favor of supporting UV selection,
* this should be mentioned in a code-comment, making it clear this is not a limitation to *fix*.
*/
bool BM_mesh_uvselect_clear(BMesh *bm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Functions (Shared)
* \{ */
void BM_loop_vert_uvselect_set_shared(BMesh *bm, BMLoop *l, bool select, int cd_loop_uv_offset);
void BM_loop_edge_uvselect_set_shared(BMesh *bm, BMLoop *l, bool select, int cd_loop_uv_offset);
void BM_face_uvselect_set_shared(BMesh *bm, BMFace *f, bool select, int cd_loop_uv_offset);
void BM_mesh_uvselect_set_elem_shared(BMesh *bm,
bool select,
int cd_loop_uv_offset,
Span<BMLoop *> loop_verts,
Span<BMLoop *> loop_edges,
Span<BMFace *> faces);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Picking
* \{ */
struct BMUVSelectPickParams {
/**
* The custom data offset for the active UV layer.
* May be -1, in this case UV connectivity checks are skipped.
*/
int cd_loop_uv_offset = -1;
/**
* If true, selection changes propagate to all other UV elements
* that share the same UV coordinates (contiguous selection).
*
* Typically derived from #ToolSettings::uv_sticky, although in some cases
* it's assumed to be true (when switching selection modes for example)
* because the tool settings aren't available in that context.
*
* A boolean can be used since "Shared Vertex" (uv_sticky mode)
* can check the meshes vertex selection directly.
*/
bool shared = true;
};
void BM_vert_uvselect_set_pick(BMesh *bm,
BMVert *v,
bool select,
const BMUVSelectPickParams &params);
void BM_edge_uvselect_set_pick(BMesh *bm,
BMEdge *e,
bool select,
const BMUVSelectPickParams &params);
void BM_face_uvselect_set_pick(BMesh *bm,
BMFace *f,
bool select,
const BMUVSelectPickParams &params);
/**
* Select/deselect elements in the viewport,
* then integrate the selection with the UV selection,
* without clearing an re-initializing the synchronized state.
* (likely to re-select islands bounds from a user-perspective).
*/
void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm,
bool select,
const BMUVSelectPickParams &params,
Span<BMVert *> verts,
Span<BMEdge *> edges,
Span<BMFace *> faces);
/** \copydoc #BM_mesh_uvselect_set_elem_from_mesh. */
void BM_mesh_uvselect_set_elem_from_mesh(BMesh *bm,
bool select,
const BMUVSelectPickParams &params,
const VectorList<BMVert *> &verts,
const VectorList<BMEdge *> &edges,
const VectorList<BMFace *> &faces);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (Only Select/De-Select)
*
* \note In most cases flushing assumes selection has already been flushed down.
*
* This means:
* - A selected edge must have both UV vertices selected.
* - A selected faces has all its edges & vertices selected.
*
* It's often useful to call #BM_mesh_uvselect_flush_shared_only_select
* after using these non-UV-coordinate aware flushing functions.
* \{ */
void BM_mesh_uvselect_flush_from_loop_verts_only_select(BMesh *bm);
void BM_mesh_uvselect_flush_from_loop_verts_only_deselect(BMesh *bm);
void BM_mesh_uvselect_flush_from_loop_edges_only_select(BMesh *bm);
void BM_mesh_uvselect_flush_from_loop_edges_only_deselect(BMesh *bm);
void BM_mesh_uvselect_flush_from_faces_only_select(BMesh *bm);
void BM_mesh_uvselect_flush_from_faces_only_deselect(BMesh *bm);
/**
* A useful utility so simple selection operations can be performed on edges/faces,
* afterwards this can be used to select UV's that are connected.
* This avoids having to use more involved UV connectivity aware logic inline.
*/
void BM_mesh_uvselect_flush_shared_only_select(BMesh *bm, int cd_loop_uv_offset);
void BM_mesh_uvselect_flush_shared_only_deselect(BMesh *bm, int cd_loop_uv_offset);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (Between Elements)
*
* Regarding the `flush_down` argument.
*
* Primitive UV selection functions always flush down:
* - #BM_face_uvselect_set
* - #BM_loop_edge_uvselect_set
* - #BM_loop_vert_uvselect_set
*
* This means it's often only necessary to flush up after the selection has been changed.
* \{ */
/**
* Mode independent UV selection/de-selection flush from UV vertices.
*
* \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards.
*/
void BM_mesh_uvselect_flush_from_loop_verts(BMesh *bm);
/**
* Mode independent UV selection/de-selection flush from UV edges.
*
* Flush from loop edges up to faces and optionally down to vertices (when `flush_down` is true).
*
* \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards.
*/
void BM_mesh_uvselect_flush_from_loop_edges(BMesh *bm, bool flush_down);
/**
* Mode independent UV selection/de-selection flush from UV faces.
*
* Flush from faces down to edges & vertices (when `flush_down` is true).
*
* \note The caller may need to run #BM_mesh_uvselect_flush_shared_only_select afterwards.
*/
void BM_mesh_uvselect_flush_from_faces(BMesh *bm, bool flush_down);
/**
* Mode independent UV selection/de-selection flush from UV vertices.
*
* Use this when it's know geometry was only selected/de-selected.
*
* \note An equivalent to #BM_mesh_select_flush_from_verts for the UV selection.
*/
void BM_mesh_uvselect_flush_from_verts(BMesh *bm, bool select);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (Selection Mode Aware)
* \{ */
/**
* \param flush_down: See #BMSelectFlushFlag::Down for notes on flushing down.
*/
void BM_mesh_uvselect_mode_flush_ex(BMesh *bm, const short selectmode, bool flush_down);
void BM_mesh_uvselect_mode_flush(BMesh *bm);
/**
* Select elements based on the selection mode.
* (flushes the selection *up* based on the mode).
*
* - With vertex selection mode enabled: flush up to edges and faces.
* - With edge selection mode enabled: flush to faces.
* - With *only* face selection mode enabled: do nothing.
*
* \note An "only deselect" version function could be added, it's not needed at the moment.a
*/
void BM_mesh_uvselect_mode_flush_only_select(BMesh *bm);
/**
* When the select mode changes, update to ensure the selection is valid.
* So single vertices aren't selected in edge-select mode for example.
*
* The mesh selection flushing must have already run.
*/
void BM_mesh_uvselect_mode_flush_update(BMesh *bm,
short selectmode_old,
short selectmode_new,
int cd_loop_uv_offset);
/**
* A specialized flushing that fills in selection information after subdividing.
*
* It's important this runs:
* - After subdivision.
* - After the mesh selection has already been flushed.
*
* \note Intended to be a generic utility to be used in any situation
* new geometry is created by splitting existing geometry.
*/
void BM_mesh_uvselect_flush_post_subdivide(BMesh *bm, int cd_loop_uv_offset);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Flushing (From/To Mesh)
* \{ */
/* From 3D viewport to UV selection.
*
* These functions correspond to #ToolSettings::uv_sticky options. */
void BM_mesh_uvselect_sync_from_mesh_sticky_location(BMesh *bm, int cd_loop_uv_offset);
void BM_mesh_uvselect_sync_from_mesh_sticky_disabled(BMesh *bm);
void BM_mesh_uvselect_sync_from_mesh_sticky_vert(BMesh *bm);
/**
* Synchronize selection: from the UV selection to the 3D viewport.
*
* \note #BMesh::uv_select_sync_valid must be true.
*/
void BM_mesh_uvselect_sync_to_mesh(BMesh *bm);
/** \} */
/* -------------------------------------------------------------------- */
/** \name UV Selection Validation
* \{ */
/**
* UV/Mesh Synchronization
*
* Check the selection has been properly synchronized between the mesh and the UV's.
*
* \note It is essential for this to be correct and return no errors.
* Other checks are useful to ensure the selection state meets the expectations of the caller
* but the state is not invalid - as it is when the selection is out-of-sync.
*/
struct UVSelectValidateInfo_Sync {
/** When a vertex is unselected none of it's UV's may be selected. */
int count_uv_vert_any_selected_with_vert_unselected = 0;
/** When a vertex is selected at least one UV must be selected. */
int count_uv_vert_none_selected_with_vert_selected = 0;
/** When a edge is unselected none of it's UV's may be selected. */
int count_uv_edge_any_selected_with_edge_unselected = 0;
/** When a edge is selected at least one UV must be selected. */
int count_uv_edge_none_selected_with_edge_selected = 0;
};
/**
* Flushing between elements.
*
* Check the selection has been properly flushing between elements.
*/
struct UVSelectValidateInfo_Flush {
/** Edges are selected without selected vertices. */
int count_uv_edge_selected_with_any_verts_unselected = 0;
/** Edges are unselected with all selected vertices. */
int count_uv_edge_unselected_with_all_verts_selected = 0;
/** Faces are selected without selected vertices. */
int count_uv_face_selected_with_any_verts_unselected = 0;
/** Faces are unselected with all selected vertices. */
int count_uv_face_unselected_with_all_verts_selected = 0;
/** Faces are selected without selected edges. */
int count_uv_face_selected_with_any_edges_unselected = 0;
/** Faces are unselected with all selected edges. */
int count_uv_face_unselected_with_all_edges_selected = 0;
};
/**
* Contiguous.
*
* Check the selected UV's are contiguous,
* in situations where it's expected selecting a UV will select all "connected" UV's
* (UV's sharing the same vertex with the same UV coordinate).
*/
struct UVSelectValidateInfo_Contiguous {
/** When a vertices connected UV's are co-located without matching selection. */
int count_uv_vert_non_contiguous_selected = 0;
/** When a edges connected UV's are co-located without matching selection. */
int count_uv_edge_non_contiguous_selected = 0;
};
/**
* Flush & contiguous.
*
* In some cases it's necessary to check flushing and contiguous UV's are correct.
*/
struct UVSelectValidateInfo_FlushAndContiguous {
/** A vertex is selected in edge/face modes without being part of a selected edge/face. */
int count_uv_vert_isolated_in_edge_or_face_mode = 0;
/** A vertex is selected in face modes without being part of a selected face. */
int count_uv_vert_isolated_in_face_mode = 0;
/** An edge is selected in face modes without being part of a selected face. */
int count_uv_edge_isolated_in_face_mode = 0;
};
struct UVSelectValidateInfo {
UVSelectValidateInfo_Sync sync;
UVSelectValidateInfo_Flush flush;
UVSelectValidateInfo_Contiguous contiguous;
UVSelectValidateInfo_FlushAndContiguous flush_contiguous;
};
/**
* Check the UV selection is valid, mainly for debugging & testing purposes.
*
* The primary check which should remain valid is: `check_sync`,
* if there is ever a selected vertex without any selected UV's or a selected
* UV without it's vertex being selected (and similar kinds of issues),
* then the selection is out-of-sync, which Blender should *never* allow.
*
* While an invalid selection should not crash, tools that operate on selection
* may behave unpredictably.
*
* The other checks may be desired or not although this depends more on the situation.
*
* \param cd_loop_uv_offset: The UV custom-data layer to check.
* Ignored when -1 (UV checks wont be used).
*
* \param check_sync: When true, check the selection is synchronized
* between the UV and mesh selection. This should practically always be true,
* as it doesn't make sense to check the UV selection if valid otherwise,
* unless the UV selection is being set and has not yet been synchronized.
* \param check_flush: When true, check the selection is flushed based on #BMesh::selectmode.
* \param check_contiguous: When true, check that UV selection is contiguous.
* Note that this is not considered an *error* since users may cause this to happen and
* tools are expected to work properly, however some operations are expected to maintain
* a contiguous selection. This check is included to ensure those operations are working.
*/
bool BM_mesh_uvselect_is_valid(BMesh *bm,
int cd_loop_uv_offset,
bool check_sync,
bool check_flush,
bool check_contiguous,
UVSelectValidateInfo *info);
/** \} */
} // namespace blender

View File

@@ -0,0 +1,203 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* BMesh Walker API.
*
* NOTE(@joeedh): Details on design.
*
* Original design: walkers directly emulation recursive functions.
* functions save their state onto a #BMWalker.worklist, and also add new states
* to implement recursive or looping behavior.
* Generally only one state push per call with a specific state is desired.
*
* basic design pattern: the walker step function goes through its
* list of possible choices for recursion, and recurses (by pushing a new state)
* using the first non-visited one. This choice is the flagged as visited using the #GHash.
* Each step may push multiple new states onto the #BMWalker.worklist at once.
*
* - Walkers use tool flags, not header flags.
* - Walkers now use #GHash for storing visited elements,
* rather than stealing flags. #GHash can be rewritten
* to be faster if necessary, in the far future :) .
* - tools should ALWAYS have necessary error handling
* for if walkers fail.
*/
#include <cstdlib>
#include <cstring> /* For `memcpy`. */
#include "BLI_listbase.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "bmesh_walkers_private.hh"
namespace blender {
void *BMW_begin(BMWalker *walker, void *start)
{
BLI_assert(((BMHeader *)start)->htype & walker->begin_htype);
walker->begin(walker, start);
return BMW_current_state(walker) ? walker->step(walker) : nullptr;
}
void BMW_init(BMWalker *walker,
BMesh *bm,
int type,
short mask_vert,
short mask_edge,
short mask_face,
BMWFlag flag,
int layer,
BMWDelimitFlag delimit)
{
memset(walker, 0, sizeof(BMWalker));
walker->layer = layer;
walker->flag = flag;
walker->delimit = delimit;
walker->bm = bm;
walker->mask_vert = mask_vert;
walker->mask_edge = mask_edge;
walker->mask_face = mask_face;
walker->visit_set = MEM_new<Set<const void *>>("bmesh walkers");
walker->visit_set_alt = MEM_new<Set<const void *>>("bmesh walkers sec");
if (UNLIKELY(type >= BMW_MAXWALKERS || type < 0)) {
fprintf(stderr,
"%s: Invalid walker type in BMW_init; type: %d, "
"searchmask: (v:%d, e:%d, f:%d), flag: %d, layer: %d\n",
__func__,
type,
mask_vert,
mask_edge,
mask_face,
flag,
layer);
BLI_assert(0);
return;
}
if (type != BMW_CUSTOM) {
walker->begin_htype = bm_walker_types[type]->begin_htype;
walker->begin = bm_walker_types[type]->begin;
walker->yield = bm_walker_types[type]->yield;
walker->step = bm_walker_types[type]->step;
walker->structsize = bm_walker_types[type]->structsize;
walker->order = bm_walker_types[type]->order;
walker->valid_mask = bm_walker_types[type]->valid_mask;
walker->delimit_supported = bm_walker_types[type]->delimit_supported;
/* safety checks */
/* if this raises an error either the caller is wrong or
* 'bm_walker_types' needs updating */
BLI_assert(mask_vert == 0 || (walker->valid_mask & BM_VERT));
BLI_assert(mask_edge == 0 || (walker->valid_mask & BM_EDGE));
BLI_assert(mask_face == 0 || (walker->valid_mask & BM_FACE));
BLI_assert((delimit & ~walker->delimit_supported) == 0);
}
walker->worklist = BLI_mempool_create(walker->structsize, 0, 128, BLI_MEMPOOL_NOP);
walker->states.clear_no_delete();
}
void BMW_end(BMWalker *walker)
{
BLI_mempool_destroy(walker->worklist);
MEM_delete(walker->visit_set);
MEM_delete(walker->visit_set_alt);
}
void *BMW_step(BMWalker *walker)
{
BMHeader *head;
head = static_cast<BMHeader *>(BMW_walk(walker));
return head;
}
int BMW_current_depth(BMWalker *walker)
{
return walker->depth;
}
void *BMW_walk(BMWalker *walker)
{
void *current = nullptr;
while (BMW_current_state(walker)) {
current = walker->step(walker);
if (current) {
return current;
}
}
return nullptr;
}
void *BMW_current_state(BMWalker *walker)
{
BMwGenericWalker *currentstate = static_cast<BMwGenericWalker *>(walker->states.first);
if (currentstate) {
/* Automatic update of depth. For most walkers that
* follow the standard "Step" pattern of:
* - read current state
* - remove current state
* - push new states
* - return walk result from just-removed current state
* this simple automatic update should keep track of depth
* just fine. Walkers that deviate from that pattern may
* need to manually update the depth if they care about
* keeping it correct. */
walker->depth = currentstate->depth + 1;
}
return currentstate;
}
void BMW_state_remove(BMWalker *walker)
{
void *oldstate;
oldstate = BMW_current_state(walker);
BLI_remlink(&walker->states, oldstate);
BLI_mempool_free(walker->worklist, oldstate);
}
void *BMW_state_add(BMWalker *walker)
{
BMwGenericWalker *newstate;
newstate = static_cast<BMwGenericWalker *>(BLI_mempool_alloc(walker->worklist));
newstate->depth = walker->depth;
switch (walker->order) {
case BMW_DEPTH_FIRST:
BLI_addhead(&walker->states, newstate);
break;
case BMW_BREADTH_FIRST:
BLI_addtail(&walker->states, newstate);
break;
default:
BLI_assert(0);
break;
}
return newstate;
}
void BMW_reset(BMWalker *walker)
{
while (BMW_current_state(walker)) {
BMW_state_remove(walker);
}
walker->depth = 0;
walker->visit_set->clear();
walker->visit_set_alt->clear();
}
} // namespace blender

View File

@@ -0,0 +1,197 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*/
#include "bmesh_class.hh"
#include "BLI_set.hh"
namespace blender {
/*
* NOTE: do NOT modify topology while walking a mesh!
*/
struct BMwGenericWalker;
enum BMWOrder {
BMW_DEPTH_FIRST,
BMW_BREADTH_FIRST,
};
enum BMWFlag {
BMW_FLAG_NOP = 0,
BMW_FLAG_TEST_HIDDEN = (1 << 0),
};
enum BMWDelimitFlag {
BMW_DELIMIT_NONE = 0,
BMW_DELIMIT_EDGE_LOOP_INNER_CORNERS = 1 << 0,
BMW_DELIMIT_EDGE_LOOP_OUTER_CORNERS = 1 << 1,
BMW_DELIMIT_EDGE_LOOP_NGONS = 1 << 2,
BMW_DELIMIT_EDGE_RING_NGONS = 1 << 3,
BMW_DELIMIT_EDGE_MARK_SEAM = 1 << 4,
BMW_DELIMIT_EDGE_MARK_SHARP = 1 << 5,
BMW_DELIMIT_FACE_MARK_MATERIAL = 1 << 6,
};
ENUM_OPERATORS(BMWDelimitFlag)
/*Walkers*/
struct BMWalker {
char begin_htype; /* only for validating input */
void (*begin)(struct BMWalker *walker, void *start);
void *(*step)(struct BMWalker *walker);
void *(*yield)(struct BMWalker *walker);
int structsize;
BMWOrder order;
int valid_mask;
BMWDelimitFlag delimit_supported;
/* runtime */
int layer;
BMesh *bm;
BLI_mempool *worklist;
ListBaseT<BMwGenericWalker> states;
/* these masks are to be tested against elements BMO_elem_flag_test(),
* should never be accessed directly only through BMW_init() and bmw_mask_check_*() functions */
short mask_vert;
short mask_edge;
short mask_face;
BMWFlag flag;
BMWDelimitFlag delimit;
Set<const void *> *visit_set;
Set<const void *> *visit_set_alt;
int depth;
};
/* define to make BMW_init more clear */
#define BMW_MASK_NOP 0
/**
* \brief Initialize Walker
*
* Allocates and returns a new mesh walker of a given type.
* The elements visited are filtered by the bit-mask `searchmask`.
*/
void BMW_init(struct BMWalker *walker,
BMesh *bm,
int type,
short mask_vert,
short mask_edge,
short mask_face,
BMWFlag flag,
int layer,
BMWDelimitFlag delimit);
void *BMW_begin(BMWalker *walker, void *start);
/**
* \brief Step Walker
*/
void *BMW_step(struct BMWalker *walker);
/**
* \brief End Walker
*
* Frees a walker's worklist.
*/
void BMW_end(struct BMWalker *walker);
/**
* \brief Walker Current Depth
*
* Returns the current depth of the walker.
*/
int BMW_current_depth(BMWalker *walker);
/* These are used by custom walkers. */
/**
* \brief Current Walker State
*
* Returns the first state from the walker state
* worklist. This state is the next in the
* worklist for processing.
*/
void *BMW_current_state(BMWalker *walker);
/**
* \brief Add a new Walker State
*
* Allocate a new empty state and put it on the worklist.
* A pointer to the new state is returned so that the caller
* can fill in the state data. The new state will be inserted
* at the front for depth-first walks, and at the end for
* breadth-first walks.
*/
void *BMW_state_add(BMWalker *walker);
/**
* \brief Remove Current Walker State
*
* Remove and free an item from the end of the walker state
* worklist.
*/
void BMW_state_remove(BMWalker *walker);
/**
* \brief Main Walking Function
*
* Steps a mesh walker forward by one element
*/
void *BMW_walk(BMWalker *walker);
/**
* \brief Reset Walker
*
* Frees all states from the worklist, resetting the walker
* for reuse in a new walk.
*/
void BMW_reset(BMWalker *walker);
#define BMW_ITER(ele, walker, data) \
for (BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMW_begin(walker, (BM_CHECK_TYPE_ELEM(data), data)); ele; \
BM_CHECK_TYPE_ELEM_ASSIGN(ele) = BMW_step(walker))
/*
* example of usage, walking over an island of tool flagged faces:
*
* BMWalker walker;
* BMFace *f;
*
* BMW_init(&walker, bm, BMW_ISLAND, SOME_OP_FLAG);
*
* for (f = BMW_begin(&walker, some_start_face); f; f = BMW_step(&walker)) {
* // do something with f
* }
* BMW_end(&walker);
*/
enum {
BMW_VERT_SHELL,
BMW_LOOP_SHELL,
BMW_LOOP_SHELL_WIRE,
BMW_FACE_SHELL,
BMW_EDGELOOP,
BMW_FACELOOP,
BMW_EDGERING,
BMW_EDGEBOUNDARY,
BMW_EDGELOOP_NONMANIFOLD,
/* BMW_RING, */
BMW_LOOPDATA_ISLAND,
BMW_ISLANDBOUND,
BMW_ISLAND,
BMW_ISLAND_MANIFOLD,
BMW_CONNECTED_VERTEX,
/* End of array index enum values. */
/* Do not initialize function pointers and struct size in #BMW_init. */
BMW_CUSTOM,
BMW_MAXWALKERS,
};
/* use with BMW_init, so as not to confuse with restrict flags */
#define BMW_NIL_LAY 0
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,99 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/** \file
* \ingroup bmesh
*
* BMesh walker API.
*/
#include "bmesh_class.hh"
namespace blender {
struct BMWalker;
extern const BMWalker *bm_walker_types[];
extern const int bm_totwalkers;
/* Pointer hiding */
struct BMwGenericWalker {
Link link;
int depth;
};
struct BMwShellWalker {
BMwGenericWalker header;
BMEdge *curedge;
};
struct BMwLoopShellWalker {
BMwGenericWalker header;
BMLoop *curloop;
};
struct BMwLoopShellWireWalker {
BMwGenericWalker header;
BMElem *curelem;
};
struct BMwIslandboundWalker {
BMwGenericWalker header;
BMLoop *base;
BMVert *lastv;
BMLoop *curloop;
};
struct BMwIslandWalker {
BMwGenericWalker header;
BMFace *cur;
};
struct BMwEdgeLoopWalker {
BMwGenericWalker header;
BMEdge *cur, *start;
BMVert *lastv, *startv;
BMFace *f_hub;
bool is_boundary; /* boundary looping changes behavior */
bool is_single; /* single means the edge verts are only connected to 1 face */
};
struct BMwFaceLoopWalker {
BMwGenericWalker header;
BMLoop *l;
bool no_calc;
};
struct BMwEdgeringWalker {
BMwGenericWalker header;
BMLoop *l;
BMEdge *wireedge;
bool no_calc;
};
struct BMwEdgeboundaryWalker {
BMwGenericWalker header;
BMEdge *e;
};
struct BMwNonManifoldEdgeLoopWalker {
BMwGenericWalker header;
BMEdge *start, *cur;
BMVert *startv, *lastv;
int face_count; /* face count around the edge. */
};
struct BMwUVEdgeWalker {
BMwGenericWalker header;
BMLoop *l;
};
struct BMwConnectedVertexWalker {
BMwGenericWalker header;
BMVert *curvert;
};
} // namespace blender

View File

@@ -0,0 +1,73 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Beautify the mesh by rotating edges between triangles
* to more attractive positions until no more rotations can be made.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
#define ELE_NEW 1
#define FACE_MARK 2
void bmo_beautify_fill_exec(BMesh *bm, BMOperator *op)
{
BMIter iter;
BMOIter siter;
BMFace *f;
BMEdge *e;
const bool use_restrict_tag = BMO_slot_bool_get(op->slots_in, "use_restrict_tag");
const short flag =
((use_restrict_tag ? VERT_RESTRICT_TAG : 0) |
/* Enable to avoid iterative edge rotation to cause the direction of faces to flip. */
EDGE_RESTRICT_DEGENERATE);
const short method = short(BMO_slot_int_get(op->slots_in, "method"));
BMEdge **edge_array;
int edge_array_len = 0;
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len == 3) {
BMO_face_flag_enable(bm, f, FACE_MARK);
}
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_flag_disable(e, BM_ELEM_TAG);
}
/* will over alloc if some edges can't be rotated */
edge_array = MEM_new_array_uninitialized<BMEdge *>(
size_t(BMO_slot_buffer_len(op->slots_in, "edges")), __func__);
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
/* edge is manifold and can be rotated */
if (BM_edge_rotate_check(e) &&
/* faces are tagged */
BMO_face_flag_test(bm, e->l->f, FACE_MARK) &&
BMO_face_flag_test(bm, e->l->radial_next->f, FACE_MARK))
{
edge_array[edge_array_len] = e;
edge_array_len++;
}
}
BM_mesh_beautify_fill(
bm, edge_array, edge_array_len, flag, method, ELE_NEW, FACE_MARK | ELE_NEW);
MEM_delete(edge_array);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_EDGE | BM_FACE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,96 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Bevel wrapper around #BM_mesh_bevel
*/
#include "DNA_curveprofile_types.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "BKE_customdata.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_bevel_exec(BMesh *bm, BMOperator *op)
{
const float offset = BMO_slot_float_get(op->slots_in, "offset");
const int offset_type = BMO_slot_int_get(op->slots_in, "offset_type");
const int profile_type = BMO_slot_int_get(op->slots_in, "profile_type");
const int seg = BMO_slot_int_get(op->slots_in, "segments");
const int affect_type = BMO_slot_int_get(op->slots_in, "affect");
const float profile = BMO_slot_float_get(op->slots_in, "profile");
const bool clamp_overlap = BMO_slot_bool_get(op->slots_in, "clamp_overlap");
const int material = BMO_slot_int_get(op->slots_in, "material");
const bool loop_slide = BMO_slot_bool_get(op->slots_in, "loop_slide");
const bool mark_seam = BMO_slot_bool_get(op->slots_in, "mark_seam");
const bool mark_sharp = BMO_slot_bool_get(op->slots_in, "mark_sharp");
const bool harden_normals = BMO_slot_bool_get(op->slots_in, "harden_normals");
const int face_strength_mode = BMO_slot_int_get(op->slots_in, "face_strength_mode");
const int miter_outer = BMO_slot_int_get(op->slots_in, "miter_outer");
const int miter_inner = BMO_slot_int_get(op->slots_in, "miter_inner");
const float spread = BMO_slot_float_get(op->slots_in, "spread");
const CurveProfile *custom_profile = static_cast<const CurveProfile *>(
BMO_slot_ptr_get(op->slots_in, "custom_profile"));
const int vmesh_method = BMO_slot_int_get(op->slots_in, "vmesh_method");
if (offset > 0) {
BMOIter siter;
BMEdge *e;
BMVert *v;
/* first flush 'geom' into flags, this makes it possible to check connected data,
* BM_FACE is cleared so we can put newly created faces into a bmesh slot. */
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
BMO_ITER (e, &siter, op->slots_in, "geom", BM_EDGE) {
if (BM_edge_is_manifold(e)) {
BM_elem_flag_enable(e, BM_ELEM_TAG);
/* in case verts were not also included in the geom */
BM_elem_flag_enable(e->v1, BM_ELEM_TAG);
BM_elem_flag_enable(e->v2, BM_ELEM_TAG);
}
}
BM_mesh_bevel(bm,
offset,
offset_type,
profile_type,
seg,
profile,
affect_type,
false,
clamp_overlap,
nullptr,
-1,
material,
loop_slide,
mark_seam,
mark_sharp,
harden_normals,
face_strength_mode,
miter_outer,
miter_inner,
spread,
custom_profile,
vmesh_method,
CustomData_get_offset_named(&bm->vdata, CD_PROP_FLOAT, "bevel_weight_vert"),
CustomData_get_offset_named(&bm->edata, CD_PROP_FLOAT, "bevel_weight_edge"));
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "edges.out", BM_EDGE, BM_ELEM_TAG);
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "verts.out", BM_VERT, BM_ELEM_TAG);
}
}
} // namespace blender

View File

@@ -0,0 +1,98 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Wrapper around #BM_mesh_bisect_plane
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_NEW 1
#define ELE_CUT 2
#define ELE_INPUT 4
void bmo_bisect_plane_exec(BMesh *bm, BMOperator *op)
{
const float dist = BMO_slot_float_get(op->slots_in, "dist");
const bool use_snap_center = BMO_slot_bool_get(op->slots_in, "use_snap_center");
const bool clear_outer = BMO_slot_bool_get(op->slots_in, "clear_outer");
const bool clear_inner = BMO_slot_bool_get(op->slots_in, "clear_inner");
float plane_co[3];
float plane_no[3];
float plane[4];
BMO_slot_vec_get(op->slots_in, "plane_co", plane_co);
BMO_slot_vec_get(op->slots_in, "plane_no", plane_no);
if (is_zero_v3(plane_no)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Zero normal given");
return;
}
plane_from_point_normal_v3(plane, plane_co, plane_no);
/* tag geometry to bisect */
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "geom", BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_flag_enable(bm, op->slots_in, "geom", BM_ALL_NOLOOP, ELE_INPUT);
BM_mesh_bisect_plane(bm, plane, use_snap_center, true, ELE_CUT, ELE_NEW, dist);
if (clear_outer || clear_inner) {
/* Use an array of vertices because 'geom' contains both verts and edges that may use them.
* Removing a vert may remove and edge which is later checked by #BMO_ITER.
* over-allocate the total possible vert count. */
const int vert_arr_max = min_ii(bm->totvert, BMO_slot_buffer_len(op->slots_in, "geom"));
BMVert **vert_arr = MEM_new_array_uninitialized<BMVert *>(vert_arr_max, __func__);
BMOIter siter;
BMVert *v;
float plane_inner[4];
float plane_outer[4];
STACK_DECLARE(vert_arr);
copy_v3_v3(plane_outer, plane);
copy_v3_v3(plane_inner, plane);
plane_outer[3] = plane[3] - dist;
plane_inner[3] = plane[3] + dist;
STACK_INIT(vert_arr, vert_arr_max);
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
if ((clear_outer && plane_point_side_v3(plane_outer, v->co) > 0.0f) ||
(clear_inner && plane_point_side_v3(plane_inner, v->co) < 0.0f))
{
STACK_PUSH(vert_arr, v);
}
}
while ((v = STACK_POP(vert_arr))) {
BM_vert_kill(bm, v);
}
MEM_delete(vert_arr);
}
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, ELE_NEW | ELE_INPUT);
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_cut.out", BM_VERT | BM_EDGE, ELE_CUT);
}
} // namespace blender

View File

@@ -0,0 +1,666 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect verts across faces (splits faces) and bridge tool.
*/
#include "BLI_listbase.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/**
* TODO(@ideasman42): Many connected edge loops can cause an error attempting
* to create faces with duplicate vertices. While this needs to be investigated,
* it's simple enough to check for this case, see: #102232.
*/
#define USE_DUPLICATE_FACE_VERT_CHECK
#define EDGE_MARK 4
#define EDGE_OUT 8
#define FACE_OUT 16
/* el_a and el_b _must_ be same size */
static void bm_bridge_splice_loops(BMesh *bm,
LinkData *el_a,
LinkData *el_b,
const float merge_factor)
{
BMOperator op_weld;
BMOpSlot *slot_targetmap;
BMO_op_init(bm, &op_weld, 0, "weld_verts");
slot_targetmap = BMO_slot_get(op_weld.slots_in, "targetmap");
do {
BMVert *v_a = static_cast<BMVert *>(el_a->data), *v_b = static_cast<BMVert *>(el_b->data);
BM_data_interp_from_verts(bm, v_a, v_b, v_b, merge_factor);
interp_v3_v3v3(v_b->co, v_a->co, v_b->co, merge_factor);
BLI_assert(v_a != v_b);
BMO_slot_map_elem_insert(&op_weld, slot_targetmap, v_a, v_b);
} while ((void)(el_b = el_b->next), (el_a = el_a->next));
BMO_op_exec(bm, &op_weld);
BMO_op_finish(bm, &op_weld);
}
/* get the 2 loops matching 2 verts.
* first attempt to get the face corners that use the edge defined by v1 & v2,
* if that fails just get any loop that's on the vert (the first one) */
static void bm_vert_loop_pair(BMesh *bm, BMVert *v1, BMVert *v2, BMLoop **l1, BMLoop **l2)
{
BMEdge *e = BM_edge_exists(v1, v2);
BMLoop *l = e->l;
if (l) {
if (l->v == v1) {
*l1 = l;
*l2 = l->next;
}
else {
*l2 = l;
*l1 = l->next;
}
}
else {
/* fallback to _any_ loop */
*l1 = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v1, 0));
*l2 = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v2, 0));
}
}
/* el_b can have any offset */
static float bm_edgeloop_offset_length(LinkData *el_a,
LinkData *el_b,
LinkData *el_b_first,
const float len_max)
{
float len = 0.0f;
BLI_assert(el_a->prev == nullptr); /* must be first */
do {
len += len_v3v3((static_cast<BMVert *>(el_a->data))->co,
(static_cast<BMVert *>(el_b->data))->co);
} while ((void)(el_b = el_b->next ? el_b->next : el_b_first),
(el_a = el_a->next) && (len < len_max));
return len;
}
static void bm_bridge_best_rotation(BMEdgeLoopStore *el_store_a, BMEdgeLoopStore *el_store_b)
{
ListBaseT<LinkData> *lb_a = BM_edgeloop_verts_get(el_store_a);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(el_store_b);
LinkData *el_a = static_cast<LinkData *>(lb_a->first);
LinkData *el_b = static_cast<LinkData *>(lb_b->first);
LinkData *el_b_first = el_b;
LinkData *el_b_best = nullptr;
float len_best = FLT_MAX;
for (; el_b; el_b = el_b->next) {
const float len = bm_edgeloop_offset_length(el_a, el_b, el_b_first, len_best);
if (len < len_best) {
el_b_best = el_b;
len_best = len;
}
}
if (el_b_best) {
BLI_listbase_rotate_first(lb_b, el_b_best);
}
}
static void bm_face_edges_tag_out(BMesh *bm, BMFace *f)
{
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMO_edge_flag_enable(bm, l_iter->e, EDGE_OUT);
} while ((l_iter = l_iter->next) != l_first);
}
static bool bm_edge_test_cb(BMEdge *e, void *bm_v)
{
return BMO_edge_flag_test((BMesh *)bm_v, e, EDGE_MARK);
}
static void bridge_loop_pair(BMesh *bm,
BMEdgeLoopStore *el_store_a,
BMEdgeLoopStore *el_store_b,
const bool use_merge,
const float merge_factor,
const int twist_offset)
{
const float eps = 0.00001f;
LinkData *el_a_first, *el_b_first;
const bool is_closed = BM_edgeloop_is_closed(el_store_a) && BM_edgeloop_is_closed(el_store_b);
int el_store_a_len, el_store_b_len;
bool el_store_b_free = false;
float el_dir[3];
float dot_a, dot_b;
const bool use_edgeout = true;
el_store_a_len = BM_edgeloop_length_get(el_store_a);
el_store_b_len = BM_edgeloop_length_get(el_store_b);
if (el_store_a_len < el_store_b_len) {
std::swap(el_store_a_len, el_store_b_len);
std::swap(el_store_a, el_store_b);
}
if (use_merge) {
BLI_assert(el_store_a_len == el_store_b_len);
}
if (el_store_a_len != el_store_b_len) {
BM_mesh_elem_hflag_disable_all(bm, BM_FACE | BM_EDGE, BM_ELEM_TAG, false);
}
sub_v3_v3v3(el_dir, BM_edgeloop_center_get(el_store_a), BM_edgeloop_center_get(el_store_b));
if (is_closed) {
/* if all loops are closed this will calculate twice for all loops */
BM_edgeloop_calc_normal(bm, el_store_a);
BM_edgeloop_calc_normal(bm, el_store_b);
}
else {
ListBaseT<LinkData> *lb_a = BM_edgeloop_verts_get(el_store_a);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(el_store_b);
/* normalizing isn't strictly needed but without we may get very large values */
float no[3];
float dir_a_orig[3], dir_b_orig[3];
float dir_a[3], dir_b[3];
const float *test_a, *test_b;
sub_v3_v3v3(dir_a_orig,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_a->first))->data))->co,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_a->last))->data))->co);
sub_v3_v3v3(dir_b_orig,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_b->first))->data))->co,
(static_cast<BMVert *>((static_cast<LinkData *>(lb_b->last))->data))->co);
/* make the directions point out from the normals, 'no' is used as a temp var */
cross_v3_v3v3(no, dir_a_orig, el_dir);
cross_v3_v3v3(dir_a, no, el_dir);
cross_v3_v3v3(no, dir_b_orig, el_dir);
cross_v3_v3v3(dir_b, no, el_dir);
if (LIKELY(!is_zero_v3(dir_a) && !is_zero_v3(dir_b))) {
test_a = dir_a;
test_b = dir_b;
}
else {
/**
* This is a corner case:
*
* <pre>
* (loop a) (loop b)
* +--------+ +--------+
* </pre>
*
* When loops are aligned to the direction between
* the loops values of 'dir_a/b' is degenerate,
* in this case compare the original directions
* (before they were corrected by 'el_dir'),
* see: #43013
*/
test_a = dir_a_orig;
test_b = dir_b_orig;
}
if (dot_v3v3(test_a, test_b) < 0.0f) {
BM_edgeloop_flip(bm, el_store_b);
}
normalize_v3_v3(no, el_dir);
BM_edgeloop_calc_normal_aligned(bm, el_store_a, no);
BM_edgeloop_calc_normal_aligned(bm, el_store_b, no);
}
dot_a = dot_v3v3(BM_edgeloop_normal_get(el_store_a), el_dir);
dot_b = dot_v3v3(BM_edgeloop_normal_get(el_store_b), el_dir);
if (UNLIKELY((len_squared_v3(el_dir) < eps) || ((fabsf(dot_a) < eps) && (fabsf(dot_b) < eps)))) {
/* in this case there is no depth between the two loops,
* eg: 2x 2d circles, one scaled smaller,
* in this case 'el_dir' can't be used, just ensure we have matching flipping. */
if (dot_v3v3(BM_edgeloop_normal_get(el_store_a), BM_edgeloop_normal_get(el_store_b)) < 0.0f) {
BM_edgeloop_flip(bm, el_store_b);
}
}
else if ((dot_a < 0.0f) != (dot_b < 0.0f)) {
BM_edgeloop_flip(bm, el_store_b);
}
/* we only care about flipping if we make faces */
if (use_merge == false) {
float no[3];
add_v3_v3v3(no, BM_edgeloop_normal_get(el_store_a), BM_edgeloop_normal_get(el_store_b));
if (dot_v3v3(no, el_dir) < 0.0f) {
BM_edgeloop_flip(bm, el_store_a);
BM_edgeloop_flip(bm, el_store_b);
}
/* vote on winding (so new face winding is based on existing connected faces) */
if (bm->totface) {
BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
int i;
int winding_votes[2] = {0, 0};
int winding_dir = 1;
for (i = 0; i < 2; i++, winding_dir = -winding_dir) {
for (LinkData &el : *BM_edgeloop_verts_get(estore_pair[i])) {
LinkData *el_next = BM_EDGELINK_NEXT(estore_pair[i], &el);
if (el_next) {
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el.data),
static_cast<BMVert *>(el_next->data));
if (e && BM_edge_is_boundary(e)) {
winding_votes[i] += ((e->l->v == el.data) ? winding_dir : -winding_dir);
}
}
}
}
if (winding_votes[0] || winding_votes[1]) {
bool flip[2] = {false, false};
/* for direction aligned loops we can't rely on the directly we have,
* use the winding defined by the connected faces (see #48356). */
if (fabsf(dot_a) < eps) {
if (winding_votes[0] < 0) {
flip[0] = !flip[0];
winding_votes[0] *= -1;
}
}
if (fabsf(dot_b) < eps) {
if (winding_votes[1] < 0) {
flip[1] = !flip[1];
winding_votes[1] *= -1;
}
}
/* when both loops contradict the winding, flip them so surrounding geometry matches */
if ((winding_votes[0] + winding_votes[1]) < 0) {
flip[0] = !flip[0];
flip[1] = !flip[1];
/* valid but unused */
#if 0
winding_votes[0] *= -1;
winding_votes[1] *= -1;
#endif
}
if (flip[0]) {
BM_edgeloop_flip(bm, el_store_a);
}
if (flip[1]) {
BM_edgeloop_flip(bm, el_store_b);
}
}
}
}
if (el_store_a_len > el_store_b_len) {
el_store_b = BM_edgeloop_copy(el_store_b);
BM_edgeloop_expand(bm, el_store_b, el_store_a_len, false, nullptr);
el_store_b_free = true;
}
if (is_closed) {
bm_bridge_best_rotation(el_store_a, el_store_b);
/* add twist */
if (twist_offset != 0) {
const int len_b = BM_edgeloop_length_get(el_store_b);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(el_store_b);
LinkData *el_b = static_cast<LinkData *>(BLI_rfindlink(lb_b, mod_i(twist_offset, len_b)));
BLI_listbase_rotate_first(lb_b, el_b);
}
}
/* Assign after flipping is finalized */
el_a_first = static_cast<LinkData *>(BM_edgeloop_verts_get(el_store_a)->first);
el_b_first = static_cast<LinkData *>(BM_edgeloop_verts_get(el_store_b)->first);
if (use_merge) {
bm_bridge_splice_loops(bm, el_a_first, el_b_first, merge_factor);
}
else {
LinkData *el_a = el_a_first;
LinkData *el_b = el_b_first;
LinkData *el_a_next;
LinkData *el_b_next;
while (true) {
BMFace *f, *f_example;
BMLoop *l_iter;
BMVert *v_a, *v_b, *v_a_next, *v_b_next;
BMLoop *l_a = nullptr;
BMLoop *l_b = nullptr;
BMLoop *l_a_next = nullptr;
BMLoop *l_b_next = nullptr;
if (is_closed) {
el_a_next = BM_EDGELINK_NEXT(el_store_a, el_a);
el_b_next = BM_EDGELINK_NEXT(el_store_b, el_b);
}
else {
el_a_next = el_a->next;
el_b_next = el_b->next;
if (ELEM(nullptr, el_a_next, el_b_next)) {
break;
}
}
v_a = static_cast<BMVert *>(el_a->data);
v_b = static_cast<BMVert *>(el_b->data);
v_a_next = static_cast<BMVert *>(el_a_next->data);
v_b_next = static_cast<BMVert *>(el_b_next->data);
/* get loop data - before making the face */
if (v_b != v_b_next) {
bm_vert_loop_pair(bm, v_a, v_a_next, &l_a, &l_a_next);
bm_vert_loop_pair(bm, v_b, v_b_next, &l_b, &l_b_next);
}
else {
/* lazy, could be more clever here */
bm_vert_loop_pair(bm, v_a, v_a_next, &l_a, &l_a_next);
l_b = l_b_next = static_cast<BMLoop *>(BM_iter_at_index(bm, BM_LOOPS_OF_VERT, v_b, 0));
}
if (l_a && l_a_next == nullptr) {
l_a_next = l_a;
}
if (l_a_next && l_a == nullptr) {
l_a = l_a_next;
}
if (l_b && l_b_next == nullptr) {
l_b_next = l_b;
}
if (l_b_next && l_b == nullptr) {
l_b = l_b_next;
}
f_example = l_a ? l_a->f : (l_b ? l_b->f : nullptr);
if (v_b != v_b_next) {
#ifdef USE_DUPLICATE_FACE_VERT_CHECK /* Only check for duplicates between loops. */
BLI_assert((v_b != v_b_next) && (v_a_next != v_a));
if (UNLIKELY(ELEM(v_b, v_a_next, v_a) || ELEM(v_b_next, v_a_next, v_a))) {
f = nullptr;
}
else
#endif
{
BMVert *v_arr[4] = {v_b, v_b_next, v_a_next, v_a};
f = BM_face_exists(v_arr, 4);
if (f == nullptr) {
/* copy if loop data if its is missing on one ring */
f = BM_face_create_verts(bm, v_arr, 4, nullptr, BM_CREATE_NOP, true);
l_iter = BM_FACE_FIRST_LOOP(f);
if (l_b) {
BM_elem_attrs_copy(bm, l_b, l_iter);
}
l_iter = l_iter->next;
if (l_b_next) {
BM_elem_attrs_copy(bm, l_b_next, l_iter);
}
l_iter = l_iter->next;
if (l_a_next) {
BM_elem_attrs_copy(bm, l_a_next, l_iter);
}
l_iter = l_iter->next;
if (l_a) {
BM_elem_attrs_copy(bm, l_a, l_iter);
}
}
}
}
else {
#ifdef USE_DUPLICATE_FACE_VERT_CHECK /* Only check for duplicates between loops. */
BLI_assert(v_a_next != v_a);
if (UNLIKELY(ELEM(v_b, v_a_next, v_a))) {
f = nullptr;
}
else
#endif
{
BMVert *v_arr[3] = {v_b, v_a_next, v_a};
f = BM_face_exists(v_arr, 3);
if (f == nullptr) {
/* fan-fill a triangle */
f = BM_face_create_verts(bm, v_arr, 3, nullptr, BM_CREATE_NOP, true);
l_iter = BM_FACE_FIRST_LOOP(f);
if (l_b) {
BM_elem_attrs_copy(bm, l_b, l_iter);
}
l_iter = l_iter->next;
if (l_a_next) {
BM_elem_attrs_copy(bm, l_a_next, l_iter);
}
l_iter = l_iter->next;
if (l_a) {
BM_elem_attrs_copy(bm, l_a, l_iter);
}
}
}
}
#ifdef USE_DUPLICATE_FACE_VERT_CHECK
if (f != nullptr)
#endif
{
if (f_example && (f_example != f)) {
BM_elem_attrs_copy(bm, f_example, f);
}
BMO_face_flag_enable(bm, f, FACE_OUT);
BM_elem_flag_enable(f, BM_ELEM_TAG);
/* tag all edges of the face, untag the loop edges after */
if (use_edgeout) {
bm_face_edges_tag_out(bm, f);
}
}
if (el_a_next == el_a_first) {
break;
}
el_a = el_a_next;
el_b = el_b_next;
}
}
if (el_store_a_len != el_store_b_len) {
BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
int i;
BMOperator op_sub;
/* when we have to bridge between different sized edge-loops,
* be clever and post-process for best results */
/* triangulate inline */
BMO_op_initf(bm, &op_sub, 0, "triangulate faces=%hf", BM_ELEM_TAG, true);
/* calc normals for input faces before executing */
{
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_sub.slots_in, "faces", BM_FACE) {
BM_face_normal_update(f);
}
}
BMO_op_exec(bm, &op_sub);
BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "faces.out", BM_FACE, FACE_OUT);
BMO_slot_buffer_hflag_enable(bm, op_sub.slots_out, "faces.out", BM_FACE, BM_ELEM_TAG, false);
BMO_op_finish(bm, &op_sub);
/* tag verts on each side so we can restrict rotation of edges to verts on the same side */
for (i = 0; i < 2; i++) {
for (LinkData &el : *BM_edgeloop_verts_get(estore_pair[i])) {
BM_elem_flag_set((BMVert *)el.data, BM_ELEM_TAG, i);
}
}
BMO_op_initf(bm,
&op_sub,
0,
"beautify_fill faces=%hf edges=ae use_restrict_tag=%b method=%i",
BM_ELEM_TAG,
true,
1);
if (use_edgeout) {
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_sub.slots_in, "faces", BM_FACE) {
BMO_face_flag_enable(bm, f, FACE_OUT);
bm_face_edges_tag_out(bm, f);
}
}
BMO_op_exec(bm, &op_sub);
/* there may also be tagged faces that didn't rotate, mark input */
if (use_edgeout) {
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_sub.slots_out, "geom.out", BM_FACE) {
BMO_face_flag_enable(bm, f, FACE_OUT);
bm_face_edges_tag_out(bm, f);
}
}
else {
BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "geom.out", BM_FACE, FACE_OUT);
}
BMO_op_finish(bm, &op_sub);
}
if (use_edgeout && use_merge == false) {
/* we've enabled all face edges above, now disable all loop edges */
BMEdgeLoopStore *estore_pair[2] = {el_store_a, el_store_b};
int i;
for (i = 0; i < 2; i++) {
for (LinkData &el : *BM_edgeloop_verts_get(estore_pair[i])) {
LinkData *el_next = BM_EDGELINK_NEXT(estore_pair[i], &el);
if (el_next) {
if (el.data != el_next->data) {
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el.data),
static_cast<BMVert *>(el_next->data));
BMO_edge_flag_disable(bm, e, EDGE_OUT);
}
}
}
}
}
if (el_store_b_free) {
BM_edgeloop_free(el_store_b);
}
}
void bmo_bridge_loops_exec(BMesh *bm, BMOperator *op)
{
ListBaseT<BMEdgeLoopStore> eloops = {nullptr};
/* merge-bridge support */
const bool use_pairs = BMO_slot_bool_get(op->slots_in, "use_pairs");
const bool use_merge = BMO_slot_bool_get(op->slots_in, "use_merge");
const float merge_factor = BMO_slot_float_get(op->slots_in, "merge_factor");
const bool use_cyclic = BMO_slot_bool_get(op->slots_in, "use_cyclic") && (use_merge == false);
const int twist_offset = BMO_slot_int_get(op->slots_in, "twist_offset");
int count;
bool changed = false;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
count = BM_mesh_edgeloops_find(bm, &eloops, bm_edge_test_cb, bm);
BM_mesh_edgeloops_calc_center(bm, &eloops);
if (count < 2) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Select at least two edge loops");
goto cleanup;
}
if (use_pairs && (count % 2)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Select an even number of loops to bridge pairs");
goto cleanup;
}
if (use_merge) {
bool match = true;
const int eloop_len = BM_edgeloop_length_get(static_cast<BMEdgeLoopStore *>(eloops.first));
for (BMEdgeLoopStore &el_store : eloops) {
if (eloop_len != BM_edgeloop_length_get(&el_store)) {
match = false;
break;
}
}
if (!match) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Selected loops must have equal edge counts");
goto cleanup;
}
}
if (count > 2) {
if (use_pairs) {
BM_mesh_edgeloops_calc_normal(bm, &eloops);
}
BM_mesh_edgeloops_calc_order(bm, &eloops, use_pairs);
}
/* No ListBaseT iterator because of incomplete type. */
for (const Link *el_store = static_cast<const Link *>(eloops.first); el_store;
el_store = el_store->next)
{
Link *el_store_next = el_store->next;
if (el_store_next == nullptr) {
if (use_cyclic && (count > 2)) {
el_store_next = static_cast<Link *>(eloops.first);
}
else {
break;
}
}
bridge_loop_pair(bm,
reinterpret_cast<BMEdgeLoopStore *>(const_cast<Link *>(el_store)),
reinterpret_cast<BMEdgeLoopStore *>(el_store_next),
use_merge,
merge_factor,
twist_offset);
if (use_pairs) {
el_store = el_store->next;
}
changed = true;
}
cleanup:
BM_mesh_edgeloops_free(&eloops);
if (changed) {
if (use_merge == false) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
}
}
}
} // namespace blender

View File

@@ -0,0 +1,747 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Circularize selected boundary chains.
*/
#include "BLI_kdopbvh.hh"
#include "BLI_map.hh"
#include "BLI_math_geom.h"
#include "BLI_math_matrix.h"
#include "BLI_math_matrix.hh"
#include "BLI_math_vector.hh"
#include "BLI_set.hh"
#include "BLI_span.hh"
#include "BLI_vector.hh"
#include <numbers>
#include <optional>
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/** Maximum iterations for the non linear least squares solver. */
constexpr int NON_LINEAR_LEAST_SQUARES_MAX_ITERATIONS = 500;
/** Threshold for considering a vertex to be on the mirror plane. */
constexpr float MIRROR_LIMIT = 0.001f;
/** Used for convergence checks and precision comparisons. */
constexpr float CIRCULARIZE_EPSILON = 1e-6f;
/** Method used for fitting the circle. */
enum FitMethod { FIT_METHOD_LEAST_SQUARE = 0, FIT_METHOD_CONTRACT = 1 };
/** Holds data for a vertex projected onto the local plane. */
struct CircleVert {
BMVert *v;
/** Current position on the plane. */
float2 co_2d;
/** Where it should move to on the circle. */
float2 target_2d;
};
/** Stores the boundary geometry that defines the circle. */
struct VertChain {
/** The ordered vertices that defines the circle's boundary. */
Vector<BMVert *> verts;
/** This is true if the path forms a closed chain, for open chains it's false. */
bool is_closed;
};
/**
* Detects whether an edge should be considered a valid boundary
* edge for circularization.
* Valid boundary edges are edges that are selected, not hidden
* and are not interior. They lie on the boundary between a selected
* face and an unselected face and do not lie on the mirror plane.
*/
static bool is_valid_boundary_edge(BMEdge *e, const char hflag, const bool check_axis[3])
{
if (!BM_elem_flag_test(e, hflag) || BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
return false;
}
/* Wire edges are not valid boundary edges. */
if (!e->l) {
return false;
}
int selected_face_count = 0;
BMIter fiter;
BMFace *f;
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (!BM_elem_flag_test(f, BM_ELEM_HIDDEN) && BM_elem_flag_test(f, hflag)) {
selected_face_count++;
if (selected_face_count > 1) {
break;
}
}
}
if (selected_face_count > 1) {
return false;
}
/* If both vertices of an edge lie close to the same coordinate plane
* (X = 0, Y = 0, or Z = 0), the edge lies on a mirror plane and is not
* considered a valid boundary edge. */
for (int i = 0; i < 3; i++) {
if (check_axis[i] && std::abs(e->v1->co[i]) < MIRROR_LIMIT &&
std::abs(e->v2->co[i]) < MIRROR_LIMIT)
{
return false;
}
}
return true;
}
/**
* Traverses a connected path of boundary edges to form a continuous sequence of vertices.
* This function handles two cases:
* Closed chains: walks until the traversal returns to the start vertex.
* Open chains: walks in one direction until a dead end, then walks in the
* opposite direction from the start edge and merges the results.
*/
static std::optional<VertChain> walk_boundary_chain(BMEdge *start_edge,
Set<BMEdge *> &visited,
const char hflag,
const bool check_axis[3])
{
VertChain chain_data;
/* Finds the next valid boundary edge that isn't visited. */
auto get_next_edge_fn = [&](BMVert *v, BMEdge *exclude_e) -> BMEdge * {
BMIter eiter;
BMEdge *e_next;
BM_ITER_ELEM (e_next, &eiter, v, BM_EDGES_OF_VERT) {
if (e_next != exclude_e && !visited.contains(e_next)) {
if (is_valid_boundary_edge(e_next, hflag, check_axis)) {
return e_next;
}
}
}
return nullptr;
};
/* Walks in one direction until a dead end. */
auto walk_fn = [&](BMVert *curr_v, BMEdge *curr_e, Vector<BMVert *> &list) {
while (true) {
BMEdge *next_e = get_next_edge_fn(curr_v, curr_e);
if (!next_e) {
break;
}
/* Move to next vertex. */
curr_v = BM_edge_other_vert(next_e, curr_v);
curr_e = next_e;
list.append(curr_v);
visited.add(curr_e);
}
};
chain_data.verts.append(start_edge->v1);
chain_data.verts.append(start_edge->v2);
visited.add(start_edge);
/* The initial edge direction (v1 -> v2) is arbitrary.
* We walk from v2 to extend this sequence. */
walk_fn(start_edge->v2, start_edge, chain_data.verts);
/* If the traversal forms a closed chain, the last vertex will match the first.
* Remove the duplicate end vertex. */
if (chain_data.verts.size() > 2 && chain_data.verts.first() == chain_data.verts.last()) {
if (chain_data.verts.size() < 4) {
return std::nullopt;
}
chain_data.verts.remove_last();
chain_data.is_closed = true;
return chain_data;
}
/* If we are here, the chain is open.
* We need to check the other direction from the start vertex. */
Vector<BMVert *> pre_chain;
walk_fn(start_edge->v1, start_edge, pre_chain);
if (!pre_chain.is_empty()) {
std::reverse(pre_chain.begin(), pre_chain.end());
pre_chain.extend(chain_data.verts);
chain_data.verts = std::move(pre_chain);
}
chain_data.is_closed = false;
if (chain_data.verts.size() < 3) {
return std::nullopt;
}
return chain_data;
}
/** Collects all valid boundary edge chains from the current selection. */
static void bm_vert_chain_extract_from_boundary_edges(BMesh *bm,
Vector<VertChain> &r_chains,
const char hflag,
const bool check_axis[3])
{
Set<BMEdge *> visited;
BMIter iter;
BMEdge *edge;
BM_ITER_MESH (edge, &iter, bm, BM_EDGES_OF_MESH) {
if (visited.contains(edge)) {
continue;
}
if (!is_valid_boundary_edge(edge, hflag, check_axis)) {
continue;
}
std::optional<VertChain> ld = walk_boundary_chain(edge, visited, hflag, check_axis);
if (ld.has_value()) {
r_chains.append(*ld);
}
}
}
/** Computes the local coordinate system defining the 2D plane of the vertex chain. */
static float3x3 bm_vert_chain_orientation_matrix_calc(Span<BMVert *> chain, float3 &r_center)
{
r_center = float3(0.0f);
for (BMVert *v : chain) {
r_center += float3(v->co);
}
r_center /= float(chain.size());
BMVert *prev = chain.last();
float3 normal = float3(0.0f);
/* Compute a best fit plane normal for the chain using Newell's method. */
for (BMVert *curr : chain) {
add_newell_cross_v3_v3v3(normal, prev->co, curr->co);
prev = curr;
}
normal = math::normalize(normal);
float3 guess = float3(1.0f, 0.0f, 0.0f);
/* If normal is parallel to (1,0,0),the cross product would be zero.
* In that case, we switch the guess to the y axis to allow a valid
* perpendicular vector to be found. */
if (std::abs(math::dot(normal, guess)) > 0.99f) {
guess = float3(0.0f, 1.0f, 0.0f);
}
float3 p = math::normalize(math::cross(normal, guess));
float3 q = math::cross(normal, p);
float3x3 mat;
mat.x_axis() = p;
mat.y_axis() = q;
mat.z_axis() = normal;
return mat;
}
/** Projects 3D vertex coordinates onto a local 2D plane defined by the P and Q basis vectors. */
static void project_chain_to_2d(Span<BMVert *> chain,
const float3 &center,
const float3x3 &mat,
Vector<CircleVert> &r_2d_verts)
{
r_2d_verts.reserve(chain.size());
for (BMVert *v : chain) {
float3 vec = float3(v->co) - center;
CircleVert cv{.v = v, .co_2d = {math::dot(vec, mat.x_axis()), math::dot(vec, mat.y_axis())}};
r_2d_verts.append(cv);
}
}
static void calculate_circle_best_fit(Span<CircleVert> verts,
const std::optional<float2> &fixed_center,
float2 &r_center,
float *r_radius)
{
/* If the center is locked, we skip the solver. The best fit for the fixed center
* is simply the average radius. */
if (fixed_center.has_value()) {
r_center = *fixed_center;
float radius = 0.0f;
for (const CircleVert &cv : verts) {
radius += math::length(cv.co_2d);
}
radius /= verts.size();
*r_radius = radius;
return;
}
/* Initial guesses. */
float2 initial_center = float2(0.0f);
float initial_radius = 1.0f;
for (int iter = 0; iter < NON_LINEAR_LEAST_SQUARES_MAX_ITERATIONS; iter++) {
float3x3 normal_matrix = float3x3::zero();
float3 jacobian_transpose_residual = float3(0.0f);
for (const CircleVert &cv : verts) {
const float2 d_vec = initial_center - cv.co_2d;
const float distance = math::length(d_vec);
if (distance < CIRCULARIZE_EPSILON) {
continue;
}
const float3 j_row = {d_vec / distance, -1.0f};
const float residual = initial_radius - distance;
for (int row = 0; row < 3; row++) {
for (int col = 0; col < 3; col++) {
normal_matrix[col][row] += j_row[row] * j_row[col];
}
jacobian_transpose_residual[row] += j_row[row] * residual;
}
}
bool success;
float3x3 inverse_normal_matrix = math::invert(normal_matrix, success);
if (!success) {
break;
}
float3 delta = inverse_normal_matrix * jacobian_transpose_residual;
initial_center.x += delta.x;
initial_center.y += delta.y;
initial_radius += delta.z;
/* Check for convergence to stop iterating if we're close enough to the optimal
* solution. */
if (std::abs(delta.x) < CIRCULARIZE_EPSILON && std::abs(delta.y) < CIRCULARIZE_EPSILON &&
std::abs(delta.z) < CIRCULARIZE_EPSILON)
{
break;
}
}
r_center = initial_center;
*r_radius = initial_radius;
}
static void calculate_circle_inside_fit(Span<CircleVert> verts,
const std::optional<float2> &fixed_center,
float2 &r_center,
float *r_radius)
{
float2 center;
if (fixed_center.has_value()) {
center = *fixed_center;
}
else {
float total_edge_length = 0.0f;
center = float2(0.0f);
float2 prev_co = verts.last().co_2d;
for (const CircleVert &cv : verts) {
const float2 &curr_co = cv.co_2d;
const float edge_length = math::distance(prev_co, curr_co);
center += (prev_co + curr_co) * edge_length;
total_edge_length += edge_length;
prev_co = curr_co;
}
if (total_edge_length != 0.0f) {
center *= (0.5f / total_edge_length);
}
}
float radius_sq = FLT_MAX;
for (const CircleVert &cv : verts) {
const float dist_sq = math::distance_squared(center, cv.co_2d);
radius_sq = std::min(radius_sq, dist_sq);
}
r_center = center;
*r_radius = math::sqrt(radius_sq);
}
static void calculate_target_locations(MutableSpan<CircleVert> verts,
const float2 &center,
const float radius,
const bool is_regular,
const bool is_closed,
const float rotation_angle)
{
float step = 0.0f;
float start_angle = 0.0f;
if (is_regular) {
float total_angle = 2.0f * std::numbers::pi_v<float>;
int divisions = verts.size();
/* For open chains, we calculate the total angle obtained by traversing
* the vertices. Unlike closed chains whose total angle is 2*Pi,
* we cannot assume Pi for an open chain because it might span any amount
* of the circle. */
if (!is_closed && divisions > 1) {
total_angle = 0.0f;
divisions = verts.size() - 1;
float2 vec_prev = verts[0].co_2d - center;
vec_prev = math::normalize(vec_prev);
/* Skip the first vertex because it was used to initialize vec_prev otherwise
* we'll end up with a self comparison in the first iteration. */
for (const int i : verts.index_range().drop_front(1)) {
float2 vec_curr = verts[i].co_2d - center;
vec_curr = math::normalize(vec_curr);
total_angle -= angle_signed_v2v2(vec_prev, vec_curr);
vec_prev = vec_curr;
}
/* In case the angle exceeds a full revolution, clamp it to the max angle. */
const float max_angle = 2.0f * std::numbers::pi_v<float>;
total_angle = std::clamp(total_angle, -max_angle, max_angle);
}
step = total_angle / divisions;
float sum_sin = 0.0f;
float sum_cos = 0.0f;
/* Using only one vertex as the basis for the start angle can skew
* the resulting rotation of the circle in an undesirable way.
* So instead, we calculate the circular mean of the rotation by measuring
* the angular deviation for every vertex and averaging them to find the best
* fit alignment.
* Note: We accumulate the sine and cosine of the angular deviations to calculate
* the circular mean because angles wrap around 360 degrees, and averaging them directly
* would give incorrect results. */
for (const int i : verts.index_range()) {
float2 vec = verts[i].co_2d - center;
const float angle_diff = atan2f(vec.y, vec.x) - (step * i);
sum_sin += sinf(angle_diff);
sum_cos += cosf(angle_diff);
}
start_angle = atan2f(sum_sin, sum_cos);
}
for (const int i : verts.index_range()) {
float angle;
if (is_regular) {
angle = start_angle + step * i - rotation_angle;
}
else {
float2 vec = verts[i].co_2d - center;
angle = atan2f(vec.y, vec.x) - rotation_angle;
}
verts[i].target_2d.x = center.x + cosf(angle) * radius;
verts[i].target_2d.y = center.y + sinf(angle) * radius;
}
}
struct NearestTriUserData {
Span<std::array<BMLoop *, 3>> looptris;
};
/** Callback for BLI_bvhtree_find_nearest. Finds the closest point on the given triangle. */
static void nearest_tri_cb(void *userdata, int index, const float co[3], BVHTreeNearest *nearest)
{
const NearestTriUserData *data = static_cast<const NearestTriUserData *>(userdata);
const std::array<BMLoop *, 3> &ltri = data->looptris[index];
float3 closest;
closest_on_tri_to_point_v3(closest, co, ltri[0]->v->co, ltri[1]->v->co, ltri[2]->v->co);
const float dist_sq = math::distance_squared(float3(co), closest);
if (dist_sq < nearest->dist_sq) {
nearest->dist_sq = dist_sq;
nearest->index = index;
copy_v3_v3(nearest->co, closest);
}
}
using FaceTessellationCache = Map<BMFace *, Array<std::array<BMVert *, 3>>>;
static void project_on_mesh(BVHTree *bvh_tree,
NearestTriUserData *bvh_data,
BMVert *v,
const float3 &center_pos,
const float3 &normal,
float3 &r_pos,
FaceTessellationCache &tess_cache)
{
float3 vec = center_pos - float3(v->co);
float length;
vec = math::normalize_and_get_length(vec, length);
/* If vertices are too close, normalization can fail. */
if (length == 0.0f) {
r_pos = center_pos;
return;
}
const float angle = angle_normalized_v3v3(vec, normal);
if (std::abs(angle) < CIRCULARIZE_EPSILON ||
std::abs(std::numbers::pi_v<float> - angle) < CIRCULARIZE_EPSILON)
{
r_pos = float3(v->co);
return;
}
float3 p2 = center_pos + normal;
float best_dist_sq = FLT_MAX;
bool found = false;
auto test_tri_fn = [&](BMVert *v1, BMVert *v2, BMVert *v3) {
float lambda;
float2 uv;
if (isect_line_tri_v3(center_pos, p2, v1->co, v2->co, v3->co, &lambda, uv)) {
float3 hit_pos = center_pos + normal * lambda;
const float dist_sq = math::distance_squared(center_pos, hit_pos);
if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
r_pos = hit_pos;
found = true;
}
}
};
BMIter fiter;
BMFace *f;
BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
if (f->len < 3 || BM_elem_flag_test(f, BM_ELEM_HIDDEN)) {
continue;
}
if (ELEM(f->len, 3, 4)) {
BMLoop *l_start = f->l_first;
BMVert *v1 = l_start->prev->v;
BMVert *v2 = l_start->v;
BMVert *v3 = l_start->next->v;
test_tri_fn(v1, v2, v3);
if (f->len == 4) {
BMVert *v4 = l_start->next->next->v;
test_tri_fn(v1, v3, v4);
}
}
else {
const Array<std::array<BMVert *, 3>> &cached = tess_cache.lookup_or_add_cb(f, [&]() {
const int tottri = f->len - 2;
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> loops(f->len);
Array<std::array<uint, 3>, BM_DEFAULT_NGON_STACK_SIZE> index(tottri);
BM_face_calc_tessellation(
f, false, loops.data(), reinterpret_cast<uint(*)[3]>(index.data()));
Array<std::array<BMVert *, 3>> tris(tottri);
for (int i = 0; i < tottri; i++) {
tris[i] = {loops[index[i][0]]->v, loops[index[i][1]]->v, loops[index[i][2]]->v};
}
return tris;
});
for (const std::array<BMVert *, 3> &tri : cached) {
test_tri_fn(tri[0], tri[1], tri[2]);
}
}
}
if (found) {
return;
}
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
float3 closest;
closest_to_line_v3(closest, center_pos, e->v1->co, e->v2->co);
const float fac = line_point_factor_v3(closest, e->v1->co, e->v2->co);
if (fac > CIRCULARIZE_EPSILON && fac < 1.0f - CIRCULARIZE_EPSILON) {
const float dist_sq = math::distance_squared(center_pos, closest);
if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
r_pos = closest;
found = true;
}
}
}
if (found) {
return;
}
if (bvh_tree) {
BVHTreeNearest nearest;
nearest.dist_sq = FLT_MAX;
nearest.index = -1;
BLI_bvhtree_find_nearest(bvh_tree, center_pos, &nearest, nearest_tri_cb, bvh_data);
if (nearest.index != -1) {
r_pos = float3(nearest.co);
return;
}
}
r_pos = center_pos;
}
void bmo_circularize_exec(BMesh *bm, BMOperator *op)
{
const float factor = BMO_slot_float_get(op->slots_in, "factor");
const float custom_radius = BMO_slot_float_get(op->slots_in, "custom_radius");
const float angle = BMO_slot_float_get(op->slots_in, "angle");
const int fit_method = BMO_slot_int_get(op->slots_in, "fit_method");
const float flatten = BMO_slot_float_get(op->slots_in, "flatten");
const bool regular = BMO_slot_bool_get(op->slots_in, "regular");
const bool check_axis[3] = {
BMO_slot_bool_get(op->slots_in, "mirror_x"),
BMO_slot_bool_get(op->slots_in, "mirror_y"),
BMO_slot_bool_get(op->slots_in, "mirror_z"),
};
const bool lock_x = BMO_slot_bool_get(op->slots_in, "lock_x");
const bool lock_y = BMO_slot_bool_get(op->slots_in, "lock_y");
const bool lock_z = BMO_slot_bool_get(op->slots_in, "lock_z");
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(
bm, op->slots_in, "geom", BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
Vector<VertChain> chains;
bm_vert_chain_extract_from_boundary_edges(bm, chains, BM_ELEM_TAG, check_axis);
/* Builds a BVH tree when flatten is disabled. Without this we would have to iterate
* over every face in the mesh for every vertex which is too slow.
*
* Note: There is the possibility of a feedback loop here, with the geometry
* being manipulated which is used in the BVH tree. However, in practice this
* is an acceptable limitation that is unlikely to cause problems. */
Vector<std::array<BMLoop *, 3>> looptris;
BVHTree *bvh_tree = nullptr;
NearestTriUserData bvh_data = {};
FaceTessellationCache tess_cache;
if (flatten < 1.0f) {
const int tot_tri = poly_to_tri_count(bm->totface, bm->totloop);
looptris.reinitialize(tot_tri);
BM_mesh_calc_tessellation(bm, looptris);
bvh_tree = BLI_bvhtree_new(tot_tri, 0.0f, 8, 8);
for (const int i : looptris.index_range()) {
const std::array<BMLoop *, 3> &ltri = looptris[i];
float3 tri_coords[3] = {
float3(ltri[0]->v->co), float3(ltri[1]->v->co), float3(ltri[2]->v->co)};
BLI_bvhtree_insert(bvh_tree, i, reinterpret_cast<float *>(tri_coords), 3);
}
BLI_bvhtree_balance(bvh_tree);
bvh_data.looptris = looptris;
}
for (VertChain &chain_data : chains) {
Vector<BMVert *> &chain = chain_data.verts;
float3 normal_accum = float3(0.0f);
for (BMVert *v : chain) {
normal_accum += float3(v->no);
}
float3 center_3d;
float3x3 mat = bm_vert_chain_orientation_matrix_calc(chain, center_3d);
/* Reverse the chain winding if the Newell normal opposes the cumulative vertex normal. */
if (math::dot(mat.z_axis(), normal_accum) < 0.0f) {
std::reverse(chain.begin(), chain.end());
mat = bm_vert_chain_orientation_matrix_calc(chain, center_3d);
}
bool is_mirrored = false;
int mirror_axis = -1;
if (!chain_data.is_closed) {
BMVert *v_start = chain.first();
BMVert *v_end = chain.last();
for (int i = 0; i < 3; i++) {
if (check_axis[i] && std::abs(v_start->co[i]) < MIRROR_LIMIT &&
std::abs(v_end->co[i]) < MIRROR_LIMIT)
{
is_mirrored = true;
mirror_axis = i;
}
}
}
/* For open chains on a symmetry plane, force the center to the midpoint of the endpoints
* to keep the circle aligned with the mirror plane. */
if (is_mirrored) {
BMVert *v_start = chain.first();
BMVert *v_end = chain.last();
center_3d = math::midpoint(float3(v_start->co), float3(v_end->co));
float3 p = math::normalize(float3(v_start->co) - center_3d);
float3 q = math::normalize(math::cross(mat.z_axis(), p));
mat.x_axis() = p;
mat.y_axis() = q;
}
Vector<CircleVert> circle_verts;
project_chain_to_2d(chain, center_3d, mat, circle_verts);
float2 circle_center_2d;
float radius;
std::optional<float2> fixed_center = std::nullopt;
if (is_mirrored) {
fixed_center = float2(0.0f);
}
if (fit_method == FIT_METHOD_CONTRACT) {
calculate_circle_inside_fit(circle_verts, fixed_center, circle_center_2d, &radius);
}
else {
calculate_circle_best_fit(circle_verts, fixed_center, circle_center_2d, &radius);
}
if (custom_radius > 0.0f) {
radius = custom_radius;
}
calculate_target_locations(
circle_verts, circle_center_2d, radius, regular, chain_data.is_closed, angle);
for (const CircleVert &cv : circle_verts) {
const float3 target_local(cv.target_2d.x, cv.target_2d.y, 0.0f);
float3 final_pos = center_3d + mat * target_local;
if (flatten < 1.0f) {
float3 projected_pos;
project_on_mesh(
bvh_tree, &bvh_data, cv.v, final_pos, mat.z_axis(), projected_pos, tess_cache);
interp_v3_v3v3(final_pos, projected_pos, final_pos, flatten);
}
/* If this vertex is an endpoint of a mirrored chain, force it
* exactly to 0.0 on the mirror axis.
* There are some cases where a slight floating point drift ends up being
* produced which prevents the mirror modifier from merging vertices. */
if (is_mirrored) {
BLI_assert(mirror_axis != -1);
if (cv.v == chain.first() || cv.v == chain.last()) {
final_pos[mirror_axis] = 0.0f;
}
}
/* If an axis is locked, restore the original coordinate. */
if (lock_x || lock_y || lock_z) {
const float *orig = cv.v->co;
if (lock_x) {
final_pos.x = orig[0];
}
if (lock_y) {
final_pos.y = orig[1];
}
if (lock_z) {
final_pos.z = orig[2];
}
}
interp_v3_v3v3(cv.v->co, cv.v->co, final_pos, factor);
}
}
/* There would be a memory leak if this isn't freed. */
if (bvh_tree) {
BLI_bvhtree_free(bvh_tree);
}
}
} // namespace blender

View File

@@ -0,0 +1,220 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect verts across faces (splits faces).
*/
#include <array>
#include "BLI_array.hh"
#include "BLI_linklist_stack.h"
#include "BLI_utildefines.h"
#include "BLI_utildefines_stack.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define VERT_INPUT 1
#define EDGE_OUT 1
/* Edge spans 2 VERT_INPUT's, its a NOP,
* but include in "edges.out" */
#define EDGE_OUT_ADJ 2
#define FACE_TAG 2
#define FACE_EXCLUDE 4
static int bm_face_connect_verts(BMesh *bm, BMFace *f, const bool check_degenerate)
{
const uint pair_split_max = f->len / 2;
Array<std::array<BMLoop *, 2>, BM_DEFAULT_NGON_STACK_SIZE> loops_split_buf(pair_split_max);
BMLoop *(*loops_split)[2] = reinterpret_cast<BMLoop *(*)[2]>(loops_split_buf.data());
STACK_DECLARE(loops_split);
Array<std::array<BMVert *, 2>, BM_DEFAULT_NGON_STACK_SIZE> verts_pair_buf(pair_split_max);
std::array<BMVert *, 2> *verts_pair = verts_pair_buf.data();
STACK_DECLARE(verts_pair);
BMLoop *l_tag_prev = nullptr, *l_tag_first = nullptr;
BMLoop *l_iter, *l_first;
uint i;
int result = 1;
STACK_INIT(loops_split, pair_split_max);
STACK_INIT(verts_pair, pair_split_max);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (BMO_vert_flag_test(bm, l_iter->v, VERT_INPUT) &&
/* Ensure this vertex isn't part of a contiguous group. */
((BMO_vert_flag_test(bm, l_iter->prev->v, VERT_INPUT) == 0) ||
(BMO_vert_flag_test(bm, l_iter->next->v, VERT_INPUT) == 0)))
{
if (!l_tag_prev) {
l_tag_prev = l_tag_first = l_iter;
continue;
}
if (!BM_loop_is_adjacent(l_tag_prev, l_iter)) {
BMEdge *e;
e = BM_edge_exists(l_tag_prev->v, l_iter->v);
if (e == nullptr || !BMO_edge_flag_test(bm, e, EDGE_OUT)) {
BMLoop **l_pair = STACK_PUSH_RET(loops_split);
l_pair[0] = l_tag_prev;
l_pair[1] = l_iter;
}
}
l_tag_prev = l_iter;
}
} while ((l_iter = l_iter->next) != l_first);
if (STACK_SIZE(loops_split) == 0) {
return 0;
}
if (!BM_loop_is_adjacent(l_tag_first, l_tag_prev) &&
/* ensure we don't add the same pair twice */
(((loops_split[0][0] == l_tag_first) && (loops_split[0][1] == l_tag_prev)) == 0))
{
BMLoop **l_pair = STACK_PUSH_RET(loops_split);
l_pair[0] = l_tag_first;
l_pair[1] = l_tag_prev;
}
if (check_degenerate) {
BM_face_splits_check_legal(bm, f, loops_split, STACK_SIZE(loops_split));
}
else {
BM_face_splits_check_optimal(f, loops_split, STACK_SIZE(loops_split));
}
for (i = 0; i < STACK_SIZE(loops_split); i++) {
if (loops_split[i][0] == nullptr) {
continue;
}
std::array<BMVert *, 2> &v_pair = STACK_PUSH_RET(verts_pair);
v_pair[0] = loops_split[i][0]->v;
v_pair[1] = loops_split[i][1]->v;
}
/* Clear and re-use to store duplicate faces, to remove after splitting is finished. */
STACK_CLEAR(loops_split);
for (i = 0; i < STACK_SIZE(verts_pair); i++) {
BMFace *f_new;
BMLoop *l_new;
BMLoop *l_pair[2];
/* Note that duplicate edges in this case is very unlikely but it can happen, see #70287. */
bool edge_exists = (BM_edge_exists(verts_pair[i][0], verts_pair[i][1]) != nullptr);
if ((l_pair[0] = BM_face_vert_share_loop(f, verts_pair[i][0])) &&
(l_pair[1] = BM_face_vert_share_loop(f, verts_pair[i][1])))
{
f_new = BM_face_split(bm, f, l_pair[0], l_pair[1], &l_new, nullptr, edge_exists);
/* Check if duplicate faces have been created, store the loops for removal in this case.
* Note that this matches how triangulate works (newly created duplicates get removed). */
if (UNLIKELY(edge_exists)) {
BMLoop **l_pair_deferred_remove = nullptr;
for (int j = 0; j < 2; j++) {
if (BM_face_find_double(l_pair[j]->f)) {
if (l_pair_deferred_remove == nullptr) {
l_pair_deferred_remove = STACK_PUSH_RET(loops_split);
l_pair_deferred_remove[0] = nullptr;
l_pair_deferred_remove[1] = nullptr;
}
l_pair_deferred_remove[j] = l_pair[j];
}
}
}
}
else {
f_new = nullptr;
l_new = nullptr;
}
if (!l_new || !f_new) {
result = -1;
break;
}
f = f_new;
// BMO_face_flag_enable(bm, f_new, FACE_NEW);
BMO_edge_flag_enable(bm, l_new->e, EDGE_OUT);
}
for (i = 0; i < STACK_SIZE(loops_split); i++) {
for (int j = 0; j < 2; j++) {
if (loops_split[i][j] != nullptr) {
BM_face_kill(bm, loops_split[i][j]->f);
}
}
}
return result;
}
void bmo_connect_verts_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMVert *v;
BMFace *f;
const bool check_degenerate = BMO_slot_bool_get(op->slots_in, "check_degenerate");
BLI_LINKSTACK_DECLARE(faces, BMFace *);
BLI_LINKSTACK_INIT(faces);
/* tag so we won't touch ever (typically hidden faces) */
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces_exclude", BM_FACE, FACE_EXCLUDE);
/* add all faces connected to verts */
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BMIter iter;
BMLoop *l_iter;
BMO_vert_flag_enable(bm, v, VERT_INPUT);
BM_ITER_ELEM (l_iter, &iter, v, BM_LOOPS_OF_VERT) {
f = l_iter->f;
if (!BMO_face_flag_test(bm, f, FACE_EXCLUDE)) {
if (!BMO_face_flag_test(bm, f, FACE_TAG)) {
BMO_face_flag_enable(bm, f, FACE_TAG);
if (f->len > 3) {
BLI_LINKSTACK_PUSH(faces, f);
}
}
}
/* flag edges even if these are not newly created
* this way cut-pairs that include co-linear edges will get
* predictable output. */
if (BMO_vert_flag_test(bm, l_iter->prev->v, VERT_INPUT)) {
BMO_edge_flag_enable(bm, l_iter->prev->e, EDGE_OUT_ADJ);
}
if (BMO_vert_flag_test(bm, l_iter->next->v, VERT_INPUT)) {
BMO_edge_flag_enable(bm, l_iter->e, EDGE_OUT_ADJ);
}
}
}
/* connect faces */
while ((f = BLI_LINKSTACK_POP(faces))) {
if (bm_face_connect_verts(bm, f, check_degenerate) == -1) {
BMO_error_raise(bm, op, BMO_ERROR_FATAL, "Could not connect vertices");
}
}
BLI_LINKSTACK_FREE(faces);
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT | EDGE_OUT_ADJ);
}
} // namespace blender

View File

@@ -0,0 +1,214 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect vertices so all resulting faces are convex.
*
* Implementation:
*
* - triangulate all concave face (tagging convex verts),
* - rotate edges (beautify) so edges will connect nearby verts.
* - sort long edges (longest first),
* put any edges between 2 convex verts last since they often split convex regions.
* - merge the sorted edges as long as they don't create convex ngons.
*/
#include <algorithm>
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_heap.h"
#include "BLI_linklist.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_memarena.h"
#include "BLI_polyfill_2d.h"
#include "BLI_polyfill_2d_beautify.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_OUT (1 << 0)
#define FACE_OUT (1 << 1)
static bool bm_face_split_by_concave(BMesh *bm,
BMFace *f_base,
const float eps,
MemArena *pf_arena,
Heap *pf_heap)
{
const int f_base_len = f_base->len;
int faces_array_tot = f_base_len - 3;
int edges_array_tot = f_base_len - 3;
Array<BMFace *, BM_DEFAULT_NGON_STACK_SIZE> faces_array(faces_array_tot);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges_array(edges_array_tot);
const int quad_method = 0, ngon_method = 0; /* beauty */
LinkNode *faces_double = nullptr;
float normal[3];
BLI_assert(f_base->len > 3);
copy_v3_v3(normal, f_base->no);
BM_face_triangulate(bm,
f_base,
faces_array.data(),
&faces_array_tot,
edges_array.data(),
&edges_array_tot,
&faces_double,
quad_method,
ngon_method,
false,
pf_arena,
pf_heap);
BLI_assert(edges_array_tot <= f_base_len - 3);
if (faces_array_tot) {
int i;
for (i = 0; i < faces_array_tot; i++) {
BMFace *f = faces_array[i];
BMO_face_flag_enable(bm, f, FACE_OUT);
}
}
BMO_face_flag_enable(bm, f_base, FACE_OUT);
if (edges_array_tot) {
int i;
std::sort(
edges_array.begin(), edges_array.begin() + edges_array_tot, [](BMEdge *e_a, BMEdge *e_b) {
int e_a_concave = (BM_elem_flag_test(e_a->v1, BM_ELEM_TAG) &&
BM_elem_flag_test(e_a->v2, BM_ELEM_TAG));
int e_b_concave = (BM_elem_flag_test(e_b->v1, BM_ELEM_TAG) &&
BM_elem_flag_test(e_b->v2, BM_ELEM_TAG));
/* merge edges between concave edges last since these
* are most likely to remain and be the main dividers */
if (e_a_concave != e_b_concave) {
return e_a_concave < e_b_concave;
}
/* otherwise shortest edges last */
const float e_a_len = BM_edge_calc_length_squared(e_a);
const float e_b_len = BM_edge_calc_length_squared(e_b);
return e_a_len > e_b_len;
});
for (i = 0; i < edges_array_tot; i++) {
BMLoop *l_pair[2];
BMEdge *e = edges_array[i];
BMO_edge_flag_enable(bm, e, EDGE_OUT);
if (BM_edge_is_contiguous(e) && BM_edge_loop_pair(e, &l_pair[0], &l_pair[1])) {
bool ok = true;
int j;
for (j = 0; j < 2; j++) {
BMLoop *l = l_pair[j];
/* check that merging the edge (on this side)
* wouldn't result in a convex face-loop.
*
* This is the (l->next, l->prev) we would have once joined.
*/
float cross[3];
cross_tri_v3(cross, l->v->co, l->radial_next->next->next->v->co, l->prev->v->co);
if (dot_v3v3(cross, normal) <= eps) {
ok = false;
break;
}
}
if (ok) {
BMFace *f_double;
BMFace *f_new, *f_pair[2] = {l_pair[0]->f, l_pair[1]->f};
f_new = BM_faces_join(bm, f_pair, 2, true, &f_double);
/* If a double is found - queue the new face to be removed
* since it's not expected that this would remove other existing geometry. */
if (f_double) [[unlikely]] {
BLI_linklist_prepend(&faces_double, f_new);
f_new = nullptr;
}
if (f_new) {
BMO_face_flag_enable(bm, f_new, FACE_OUT);
}
}
}
}
}
BLI_heap_clear(pf_heap, nullptr);
while (faces_double) {
LinkNode *next = faces_double->next;
BM_face_kill(bm, static_cast<BMFace *>(faces_double->link));
MEM_delete(faces_double);
faces_double = next;
}
return true;
}
static bool bm_face_convex_tag_verts(BMFace *f)
{
bool is_concave = false;
if (f->len > 3) {
const BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (BM_loop_is_convex(l_iter) == false) {
is_concave = true;
BM_elem_flag_enable(l_iter->v, BM_ELEM_TAG);
}
else {
BM_elem_flag_disable(l_iter->v, BM_ELEM_TAG);
}
} while ((l_iter = l_iter->next) != l_first);
}
return is_concave;
}
void bmo_connect_verts_concave_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMFace *f;
bool changed = false;
MemArena *pf_arena;
Heap *pf_heap;
pf_arena = BLI_memarena_new(BLI_POLYFILL_ARENA_SIZE, __func__);
pf_heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len > 3 && bm_face_convex_tag_verts(f)) {
if (bm_face_split_by_concave(bm, f, FLT_EPSILON, pf_arena, pf_heap)) {
changed = true;
}
}
}
if (changed) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
}
BLI_memarena_free(pf_arena);
BLI_heap_free(pf_heap, nullptr);
}
} // namespace blender

View File

@@ -0,0 +1,174 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect verts non-planer faces iteratively (splits faces).
*/
#include "BLI_array.hh"
#include "BLI_linklist_stack.h"
#include "BLI_math_geom.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_OUT (1 << 0)
#define FACE_OUT (1 << 1)
/**
* Calculates how non-planar the face subset is.
*/
static float bm_face_subset_calc_planar(BMLoop *l_first, BMLoop *l_last, const float no[3])
{
float axis_mat[3][3];
float z_prev;
float delta_z = 0.0f;
/* Newell's Method */
BMLoop *l_iter = l_first;
BMLoop *l_term = l_last->next;
axis_dominant_v3_to_m3(axis_mat, no);
z_prev = dot_m3_v3_row_z(axis_mat, l_last->v->co);
do {
const float z_curr = dot_m3_v3_row_z(axis_mat, l_iter->v->co);
delta_z += fabsf(z_curr - z_prev);
z_prev = z_curr;
} while ((l_iter = l_iter->next) != l_term);
return delta_z;
}
static bool bm_face_split_find(BMesh *bm, BMFace *f, BMLoop *l_pair[2], float *r_angle_cos)
{
BMLoop *l_iter, *l_first;
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> l_arr(f->len);
const uint f_len = f->len;
uint i_a, i_b;
bool found = false;
/* angle finding */
float err_best = FLT_MAX;
float angle_best_cos = -FLT_MAX;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
i_a = 0;
do {
l_arr[i_a++] = l_iter;
} while ((l_iter = l_iter->next) != l_first);
/* now for the big search, O(N^2), however faces normally aren't so large */
for (i_a = 0; i_a < f_len; i_a++) {
BMLoop *l_a = l_arr[i_a];
for (i_b = i_a + 2; i_b < f_len; i_b++) {
BMLoop *l_b = l_arr[i_b];
/* check these are not touching
* (we could be smarter here) */
if (!BM_loop_is_adjacent(l_a, l_b)) {
/* first calculate normals */
float no_a[3], no_b[3];
if (BM_face_calc_normal_subset(l_a, l_b, no_a) != 0.0f &&
BM_face_calc_normal_subset(l_b, l_a, no_b) != 0.0f)
{
const float err_a = bm_face_subset_calc_planar(l_a, l_b, no_a);
const float err_b = bm_face_subset_calc_planar(l_b, l_a, no_b);
const float err_test = err_a + err_b;
if (err_test < err_best) {
/* check we're legal (we could batch this) */
BMLoop *l_split[2] = {l_a, l_b};
BM_face_splits_check_legal(bm, f, &l_split, 1);
if (l_split[0]) {
err_best = err_test;
l_pair[0] = l_a;
l_pair[1] = l_b;
angle_best_cos = dot_v3v3(no_a, no_b);
found = true;
}
}
}
}
}
}
*r_angle_cos = angle_best_cos;
return found;
}
static bool bm_face_split_by_angle(BMesh *bm,
BMFace *f,
BMFace *r_f_pair[2],
const float angle_limit_cos)
{
BMLoop *l_pair[2];
float angle_cos;
if (bm_face_split_find(bm, f, l_pair, &angle_cos) && (angle_cos < angle_limit_cos)) {
BMFace *f_new;
BMLoop *l_new;
f_new = BM_face_split(bm, f, l_pair[0], l_pair[1], &l_new, nullptr, false);
if (f_new) {
r_f_pair[0] = f;
r_f_pair[1] = f_new;
BMO_face_flag_enable(bm, f, FACE_OUT);
BMO_face_flag_enable(bm, f_new, FACE_OUT);
BMO_edge_flag_enable(bm, l_new->e, EDGE_OUT);
return true;
}
}
return false;
}
void bmo_connect_verts_nonplanar_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMFace *f;
bool changed = false;
BLI_LINKSTACK_DECLARE(fstack, BMFace *);
const float angle_limit_cos = cosf(BMO_slot_float_get(op->slots_in, "angle_limit"));
BLI_LINKSTACK_INIT(fstack);
BMO_ITER (f, &siter, op->slots_in, "faces", BM_FACE) {
if (f->len > 3) {
BLI_LINKSTACK_PUSH(fstack, f);
}
}
while ((f = BLI_LINKSTACK_POP(fstack))) {
BMFace *f_pair[2];
if (bm_face_split_by_angle(bm, f, f_pair, angle_limit_cos)) {
int j;
for (j = 0; j < 2; j++) {
BM_face_normal_update(f_pair[j]);
if (f_pair[j]->len > 3) {
BLI_LINKSTACK_PUSH(fstack, f_pair[j]);
}
}
changed = true;
}
}
BLI_LINKSTACK_FREE(fstack);
if (changed) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
}
}
} // namespace blender

View File

@@ -0,0 +1,745 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Connect vertex pair across multiple faces (splits faces).
*/
#include "MEM_guardedalloc.h"
#include "BLI_heap_simple.h"
#include "BLI_math_matrix.h"
#include "BLI_math_vector.h"
#include "BLI_utildefines.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#include "BLI_mempool.h"
namespace blender {
/**
* Method for connecting across many faces.
*
* - use the line between both verts and their normal average to construct a matrix.
* - using the matrix, we can find all intersecting verts/edges.
* - walk the connected data and find the shortest path.
* - store a heap of paths which are being scanned (#PathContext.states).
* - continuously search the shortest path in the heap.
* - never step over the same element twice (tag elements as #ELE_TOUCHED).
* this avoids going into an eternal loop if there are many possible branches (see #45582).
* - when running into a branch, create a new #PathLinkState state and add to the heap.
* - when the target is reached,
* finish - since none of the other paths can be shorter than the one just found.
* - if the connection can't be found - fail.
* - with the connection found, split all edges tagging verts
* (or tag verts that sit on the intersection).
* - run the standard connect operator.
*/
#define CONNECT_EPS 0.0001f
#define VERT_OUT 1
#define VERT_EXCLUDE 2
/* typically hidden faces */
#define FACE_EXCLUDE 2
/* any element we've walked over (only do it once!) */
#define ELE_TOUCHED 4
#define FACE_WALK_TEST(f) \
(CHECK_TYPE_INLINE(f, BMFace *), BMO_face_flag_test(pc->bm_bmoflag, f, FACE_EXCLUDE) == 0)
#define VERT_WALK_TEST(v) \
(CHECK_TYPE_INLINE(v, BMVert *), BMO_vert_flag_test(pc->bm_bmoflag, v, VERT_EXCLUDE) == 0)
#if 0
# define ELE_TOUCH_TEST(e) \
(CHECK_TYPE_ANY(e, BMVert *, BMEdge *, BMElem *, BMElemF *), \
BMO_elem_flag_test(pc->bm_bmoflag, (BMElemF *)e, ELE_TOUCHED))
#endif
#define ELE_TOUCH_MARK(e) \
{ \
CHECK_TYPE_ANY(e, BMVert *, BMEdge *, BMElem *, BMElemF *); \
BMO_elem_flag_enable(pc->bm_bmoflag, (BMElemF *)e, ELE_TOUCHED); \
} \
((void)0)
#define ELE_TOUCH_TEST_VERT(v) BMO_vert_flag_test(pc->bm_bmoflag, v, ELE_TOUCHED)
// #define ELE_TOUCH_MARK_VERT(v) BMO_vert_flag_enable(pc->bm_bmoflag, (BMElemF *)v, ELE_TOUCHED)
#define ELE_TOUCH_TEST_EDGE(e) BMO_edge_flag_test(pc->bm_bmoflag, e, ELE_TOUCHED)
// #define ELE_TOUCH_MARK_EDGE(e) BMO_edge_flag_enable(pc->bm_bmoflag, (BMElemF *)e, ELE_TOUCHED)
// #define ELE_TOUCH_TEST_FACE(f) BMO_face_flag_test(pc->bm_bmoflag, f, ELE_TOUCHED)
// #define ELE_TOUCH_MARK_FACE(f) BMO_face_flag_enable(pc->bm_bmoflag, (BMElemF *)f, ELE_TOUCHED)
// #define DEBUG_PRINT
struct PathContext {
HeapSimple *states;
float matrix[3][3];
float axis_sep;
/* only to access BMO flags */
BMesh *bm_bmoflag;
BMVert *v_pair[2];
BLI_mempool *link_pool;
};
/**
* Single linked list where each item contains state and points to previous path item.
*/
struct PathLink {
PathLink *next;
BMElem *ele; /* edge or vert */
BMElem *ele_from; /* edge or face we came from (not 'next->ele') */
};
struct PathLinkState {
/* chain of links */
PathLink *link_last;
/* length along links */
float dist;
float co_prev[3];
};
/* -------------------------------------------------------------------- */
/** \name Min Dist Dir Utilities
*
* Simply getting the closest intersecting vert/edge is _not_ good enough. see #43792
* we need to get the closest in both directions since the absolute closest may be a dead-end.
*
* Logic is simple:
*
* - First intersection, store the direction.
* - Successive intersections will update the first distance if its aligned with the first hit.
* otherwise update the opposite distance.
* - Caller stores best outcome in both directions.
* \{ */
struct MinDistDir {
/* distance in both directions (FLT_MAX == uninitialized) */
float dist_min[2];
/* direction of the first intersection found */
float dir[3];
};
#define MIN_DIST_DIR_INIT \
{ \
{ \
FLT_MAX, FLT_MAX \
} \
}
static int min_dist_dir_test(MinDistDir *mddir, const float dist_dir[3], const float dist_sq)
{
if (mddir->dist_min[0] == FLT_MAX) {
return 0;
}
if (dot_v3v3(dist_dir, mddir->dir) > 0.0f) {
if (dist_sq < mddir->dist_min[0]) {
return 0;
}
}
else {
if (dist_sq < mddir->dist_min[1]) {
return 1;
}
}
return -1;
}
static void min_dist_dir_update(MinDistDir *dist, const float dist_dir[3])
{
if (dist->dist_min[0] == FLT_MAX) {
copy_v3_v3(dist->dir, dist_dir);
}
}
/** \} */
static int state_isect_co_pair(const PathContext *pc, const float co_a[3], const float co_b[3])
{
const float diff_a = dot_m3_v3_row_x(pc->matrix, co_a) - pc->axis_sep;
const float diff_b = dot_m3_v3_row_x(pc->matrix, co_b) - pc->axis_sep;
const int test_a = (fabsf(diff_a) < CONNECT_EPS) ? 0 : (diff_a < 0.0f) ? -1 : 1;
const int test_b = (fabsf(diff_b) < CONNECT_EPS) ? 0 : (diff_b < 0.0f) ? -1 : 1;
if ((test_a && test_b) && (test_a != test_b)) {
return 1; /* on either side */
}
return 0;
}
static int state_isect_co_exact(const PathContext *pc, const float co[3])
{
const float diff = dot_m3_v3_row_x(pc->matrix, co) - pc->axis_sep;
return (fabsf(diff) <= CONNECT_EPS);
}
static float state_calc_co_pair_fac(const PathContext *pc,
const float co_a[3],
const float co_b[3])
{
float diff_a, diff_b, diff_tot;
diff_a = fabsf(dot_m3_v3_row_x(pc->matrix, co_a) - pc->axis_sep);
diff_b = fabsf(dot_m3_v3_row_x(pc->matrix, co_b) - pc->axis_sep);
diff_tot = (diff_a + diff_b);
return (diff_tot > FLT_EPSILON) ? (diff_a / diff_tot) : 0.5f;
}
static void state_calc_co_pair(const PathContext *pc,
const float co_a[3],
const float co_b[3],
float r_co[3])
{
const float fac = state_calc_co_pair_fac(pc, co_a, co_b);
interp_v3_v3v3(r_co, co_a, co_b, fac);
}
#ifndef NDEBUG
/**
* Ideally we wouldn't need this and for most cases we don't.
* But when a face has vertices that are on the boundary more than once this becomes tricky.
*/
static bool state_link_find(const PathLinkState *state, BMElem *ele)
{
PathLink *link = state->link_last;
BLI_assert(ELEM(ele->head.htype, BM_VERT, BM_EDGE, BM_FACE));
if (link) {
do {
if (link->ele == ele) {
return true;
}
} while ((link = link->next));
}
return false;
}
#endif
static void state_link_add(PathContext *pc, PathLinkState *state, BMElem *ele, BMElem *ele_from)
{
PathLink *step_new = static_cast<PathLink *>(BLI_mempool_alloc(pc->link_pool));
BLI_assert(ele != ele_from);
BLI_assert(state_link_find(state, ele) == false);
/* never walk onto this again */
ELE_TOUCH_MARK(ele);
#ifdef DEBUG_PRINT
printf("%s: adding to state %p, %.4f - ", __func__, state, state->dist);
if (ele->head.htype == BM_VERT) {
printf("vert %d, ", BM_elem_index_get(ele));
}
else if (ele->head.htype == BM_EDGE) {
printf("edge %d, ", BM_elem_index_get(ele));
}
else {
BLI_assert(0);
}
if (ele_from == nullptr) {
printf("from nullptr\n");
}
else if (ele_from->head.htype == BM_EDGE) {
printf("from edge %d\n", BM_elem_index_get(ele_from));
}
else if (ele_from->head.htype == BM_FACE) {
printf("from face %d\n", BM_elem_index_get(ele_from));
}
else {
BLI_assert(0);
}
#endif
/* track distance */
{
float co[3];
if (ele->head.htype == BM_VERT) {
copy_v3_v3(co, (reinterpret_cast<BMVert *>(ele))->co);
}
else if (ele->head.htype == BM_EDGE) {
state_calc_co_pair(pc,
(reinterpret_cast<BMEdge *>(ele))->v1->co,
(reinterpret_cast<BMEdge *>(ele))->v2->co,
co);
}
else {
BLI_assert(0);
}
/* tally distance */
if (ele_from) {
state->dist += len_v3v3(state->co_prev, co);
}
copy_v3_v3(state->co_prev, co);
}
step_new->ele = ele;
step_new->ele_from = ele_from;
step_new->next = state->link_last;
state->link_last = step_new;
}
static PathLinkState *state_dupe_add(PathLinkState *state, const PathLinkState *state_orig)
{
state = MEM_new_uninitialized<PathLinkState>(__func__);
*state = *state_orig;
return state;
}
static PathLinkState *state_link_add_test(PathContext *pc,
PathLinkState *state,
const PathLinkState *state_orig,
BMElem *ele,
BMElem *ele_from)
{
const bool is_new = (state_orig->link_last != state->link_last);
if (is_new) {
state = state_dupe_add(state, state_orig);
}
state_link_add(pc, state, ele, ele_from);
/* after adding a link so we use the updated 'state->dist' */
if (is_new) {
BLI_heapsimple_insert(pc->states, state->dist, state);
}
return state;
}
/* walk around the face edges */
static PathLinkState *state_step__face_edges(PathContext *pc,
PathLinkState *state,
const PathLinkState *state_orig,
BMLoop *l_iter,
BMLoop *l_last,
MinDistDir *mddir)
{
BMLoop *l_iter_best[2] = {nullptr, nullptr};
int i;
do {
if (state_isect_co_pair(pc, l_iter->v->co, l_iter->next->v->co)) {
float dist_test;
float co_isect[3];
float dist_dir[3];
int index;
state_calc_co_pair(pc, l_iter->v->co, l_iter->next->v->co, co_isect);
sub_v3_v3v3(dist_dir, co_isect, state_orig->co_prev);
dist_test = len_squared_v3(dist_dir);
if ((index = min_dist_dir_test(mddir, dist_dir, dist_test)) != -1) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->e);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
if (FACE_WALK_TEST((BMFace *)ele_next_from) &&
(ELE_TOUCH_TEST_EDGE((BMEdge *)ele_next) == false))
{
min_dist_dir_update(mddir, dist_dir);
mddir->dist_min[index] = dist_test;
l_iter_best[index] = l_iter;
}
}
}
} while ((l_iter = l_iter->next) != l_last);
for (i = 0; i < 2; i++) {
if ((l_iter = l_iter_best[i])) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->e);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
state = state_link_add_test(pc, state, state_orig, ele_next, ele_next_from);
}
}
return state;
}
/* walk around the face verts */
static PathLinkState *state_step__face_verts(PathContext *pc,
PathLinkState *state,
const PathLinkState *state_orig,
BMLoop *l_iter,
BMLoop *l_last,
MinDistDir *mddir)
{
BMLoop *l_iter_best[2] = {nullptr, nullptr};
int i;
do {
if (state_isect_co_exact(pc, l_iter->v->co)) {
float dist_test;
const float *co_isect = l_iter->v->co;
float dist_dir[3];
int index;
sub_v3_v3v3(dist_dir, co_isect, state_orig->co_prev);
dist_test = len_squared_v3(dist_dir);
if ((index = min_dist_dir_test(mddir, dist_dir, dist_test)) != -1) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->v);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
if (FACE_WALK_TEST((BMFace *)ele_next_from) &&
(ELE_TOUCH_TEST_VERT((BMVert *)ele_next) == false))
{
min_dist_dir_update(mddir, dist_dir);
mddir->dist_min[index] = dist_test;
l_iter_best[index] = l_iter;
}
}
}
} while ((l_iter = l_iter->next) != l_last);
for (i = 0; i < 2; i++) {
if ((l_iter = l_iter_best[i])) {
BMElem *ele_next = reinterpret_cast<BMElem *>(l_iter->v);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(l_iter->f);
state = state_link_add_test(pc, state, state_orig, ele_next, ele_next_from);
}
}
return state;
}
static bool state_step(PathContext *pc, PathLinkState *state)
{
PathLinkState state_orig = *state;
BMElem *ele = state->link_last->ele;
const void *ele_from = state->link_last->ele_from;
if (ele->head.htype == BM_EDGE) {
BMEdge *e = reinterpret_cast<BMEdge *>(ele);
BMIter liter;
BMLoop *l_start;
BM_ITER_ELEM (l_start, &liter, e, BM_LOOPS_OF_EDGE) {
if ((l_start->f != ele_from) && FACE_WALK_TEST(l_start->f)) {
MinDistDir mddir = MIN_DIST_DIR_INIT;
/* Very similar to block below. */
state = state_step__face_edges(pc, state, &state_orig, l_start->next, l_start, &mddir);
state = state_step__face_verts(
pc, state, &state_orig, l_start->next->next, l_start, &mddir);
}
}
}
else if (ele->head.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(ele);
/* Vert loops. */
{
BMIter liter;
BMLoop *l_start;
BM_ITER_ELEM (l_start, &liter, v, BM_LOOPS_OF_VERT) {
if ((l_start->f != ele_from) && FACE_WALK_TEST(l_start->f)) {
MinDistDir mddir = MIN_DIST_DIR_INIT;
/* Very similar to block above. */
state = state_step__face_edges(
pc, state, &state_orig, l_start->next, l_start->prev, &mddir);
if (l_start->f->len > 3) {
/* Adjacent verts are handled in #state_step__vert_edges. */
state = state_step__face_verts(
pc, state, &state_orig, l_start->next->next, l_start->prev, &mddir);
}
}
}
}
/* Vert edges. */
{
BMIter eiter;
BMEdge *e;
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
BMVert *v_other = BM_edge_other_vert(e, v);
if ((reinterpret_cast<BMElem *>(e) != ele_from) && VERT_WALK_TEST(v_other)) {
if (state_isect_co_exact(pc, v_other->co)) {
BMElem *ele_next = reinterpret_cast<BMElem *>(v_other);
BMElem *ele_next_from = reinterpret_cast<BMElem *>(e);
if (ELE_TOUCH_TEST_VERT((BMVert *)ele_next) == false) {
state = state_link_add_test(pc, state, &state_orig, ele_next, ele_next_from);
}
}
}
}
}
}
else {
BLI_assert(0);
}
return (state_orig.link_last != state->link_last);
}
/**
* Get a orientation matrix from 2 vertices.
*/
static void bm_vert_pair_to_matrix(BMVert *v_pair[2], float r_unit_mat[3][3])
{
const float eps = 1e-8f;
float basis_dir[3];
float basis_tmp[3];
float basis_nor[3];
sub_v3_v3v3(basis_dir, v_pair[0]->co, v_pair[1]->co);
normalize_v3(basis_dir);
#if 0
add_v3_v3v3(basis_nor, v_pair[0]->no, v_pair[1]->no);
cross_v3_v3v3(basis_tmp, basis_nor, basis_dir);
cross_v3_v3v3(basis_nor, basis_tmp, basis_dir);
#else
/* align both normals to the directions before combining */
{
float basis_nor_a[3];
float basis_nor_b[3];
/* align normal to direction */
project_plane_normalized_v3_v3v3(basis_nor_a, v_pair[0]->no, basis_dir);
project_plane_normalized_v3_v3v3(basis_nor_b, v_pair[1]->no, basis_dir);
/* Don't normalize before combining so as normals approach the direction,
* they have less effect (#46784). */
/* combine the normals */
/* for flipped faces */
if (dot_v3v3(basis_nor_a, basis_nor_b) < 0.0f) {
negate_v3(basis_nor_b);
}
add_v3_v3v3(basis_nor, basis_nor_a, basis_nor_b);
}
#endif
/* get third axis */
normalize_v3(basis_nor);
cross_v3_v3v3(basis_tmp, basis_dir, basis_nor);
/* Try get the axis from surrounding faces, fallback to 'ortho_v3_v3' */
if (UNLIKELY(normalize_v3(basis_tmp) < eps)) {
/* vertex normals are directly opposite */
/* find the loop with the lowest angle */
struct {
float nor[3];
float angle_cos;
} axis_pair[2];
int i;
for (i = 0; i < 2; i++) {
BMIter liter;
BMLoop *l;
zero_v2(axis_pair[i].nor);
axis_pair[i].angle_cos = -FLT_MAX;
BM_ITER_ELEM (l, &liter, v_pair[i], BM_LOOPS_OF_VERT) {
float basis_dir_proj[3];
float angle_cos_test;
/* project basis dir onto the normal to find its closest angle */
project_plane_normalized_v3_v3v3(basis_dir_proj, basis_dir, l->f->no);
if (normalize_v3(basis_dir_proj) > eps) {
angle_cos_test = dot_v3v3(basis_dir_proj, basis_dir);
if (angle_cos_test > axis_pair[i].angle_cos) {
axis_pair[i].angle_cos = angle_cos_test;
copy_v3_v3(axis_pair[i].nor, basis_dir_proj);
}
}
}
}
/* create a new 'basis_nor' from the best direction.
* NOTE: we could add the directions,
* but this more often gives 45d rotated matrix, so just use the best one. */
copy_v3_v3(basis_nor, axis_pair[axis_pair[0].angle_cos < axis_pair[1].angle_cos].nor);
project_plane_normalized_v3_v3v3(basis_nor, basis_nor, basis_dir);
cross_v3_v3v3(basis_tmp, basis_dir, basis_nor);
/* last resort, pick _any_ ortho axis */
if (UNLIKELY(normalize_v3(basis_tmp) < eps)) {
ortho_v3_v3(basis_nor, basis_dir);
normalize_v3(basis_nor);
cross_v3_v3v3(basis_tmp, basis_dir, basis_nor);
normalize_v3(basis_tmp);
}
}
copy_v3_v3(r_unit_mat[0], basis_tmp);
copy_v3_v3(r_unit_mat[1], basis_dir);
copy_v3_v3(r_unit_mat[2], basis_nor);
if (invert_m3(r_unit_mat) == false) {
unit_m3(r_unit_mat);
}
}
void bmo_connect_vert_pair_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *op_verts_slot = BMO_slot_get(op->slots_in, "verts");
PathContext pc;
PathLinkState state_best = {nullptr};
if (op_verts_slot->len != 2) {
/* fail! */
return;
}
pc.bm_bmoflag = bm;
pc.v_pair[0] = (static_cast<BMVert **>(op_verts_slot->data.p))[0];
pc.v_pair[1] = (static_cast<BMVert **>(op_verts_slot->data.p))[1];
/* fail! */
if (!(pc.v_pair[0] && pc.v_pair[1])) {
return;
}
#ifdef DEBUG_PRINT
printf("%s: v_pair[0]: %d\n", __func__, BM_elem_index_get(pc.v_pair[0]));
printf("%s: v_pair[1]: %d\n", __func__, BM_elem_index_get(pc.v_pair[1]));
#endif
/* tag so we won't touch ever (typically hidden faces) */
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces_exclude", BM_FACE, FACE_EXCLUDE);
BMO_slot_buffer_flag_enable(bm, op->slots_in, "verts_exclude", BM_VERT, VERT_EXCLUDE);
/* setup context */
{
pc.states = BLI_heapsimple_new();
pc.link_pool = BLI_mempool_create(sizeof(PathLink), 0, 512, BLI_MEMPOOL_NOP);
}
/* calculate matrix */
{
bm_vert_pair_to_matrix(pc.v_pair, pc.matrix);
pc.axis_sep = dot_m3_v3_row_x(pc.matrix, pc.v_pair[0]->co);
}
/* add first vertex */
{
PathLinkState *state;
state = MEM_new_zeroed<PathLinkState>(__func__);
state_link_add(&pc, state, reinterpret_cast<BMElem *>(pc.v_pair[0]), nullptr);
BLI_heapsimple_insert(pc.states, state->dist, state);
}
while (!BLI_heapsimple_is_empty(pc.states)) {
#ifdef DEBUG_PRINT
printf("\n%s: stepping %u\n", __func__, BLI_heapsimple_len(pc.states));
#endif
while (!BLI_heapsimple_is_empty(pc.states)) {
PathLinkState *state = static_cast<PathLinkState *>(BLI_heapsimple_pop_min(pc.states));
/* either we insert this into 'pc.states' or its freed */
bool continue_search;
if (state->link_last->ele == reinterpret_cast<BMElem *>(pc.v_pair[1])) {
/* pass, wait until all are found */
#ifdef DEBUG_PRINT
printf("%s: state %p loop found %.4f\n", __func__, state, state->dist);
#endif
state_best = *state;
/* we're done, exit all loops */
BLI_heapsimple_clear(pc.states, MEM_delete_void);
continue_search = false;
}
else if (state_step(&pc, state)) {
continue_search = true;
}
else {
/* didn't reach the end, remove it,
* links are shared between states so just free the link_pool at the end */
#ifdef DEBUG_PRINT
printf("%s: state %p removed\n", __func__, state);
#endif
continue_search = false;
}
if (continue_search) {
BLI_heapsimple_insert(pc.states, state->dist, state);
}
else {
MEM_delete(state);
}
}
}
if (state_best.link_last) {
PathLink *link;
/* find the best state */
link = state_best.link_last;
do {
if (link->ele->head.htype == BM_EDGE) {
BMEdge *e = reinterpret_cast<BMEdge *>(link->ele);
BMVert *v_new;
float e_fac = state_calc_co_pair_fac(&pc, e->v1->co, e->v2->co);
v_new = BM_edge_split(bm, e, e->v1, nullptr, e_fac);
/* Adding vertices makes the face-normals stale.
* These are used for the `connect_verts` call next for projecting onto the face,
* so the normals must be recalculated here. */
if (e->l) {
BMLoop *l_iter = e->l;
do {
BM_face_normal_update(l_iter->f);
} while ((l_iter = l_iter->radial_next) != e->l);
}
BMO_vert_flag_enable(bm, v_new, VERT_OUT);
}
else if (link->ele->head.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(link->ele);
BMO_vert_flag_enable(bm, v, VERT_OUT);
}
else {
BLI_assert(0);
}
} while ((link = link->next));
}
BMO_vert_flag_enable(bm, pc.v_pair[0], VERT_OUT);
BMO_vert_flag_enable(bm, pc.v_pair[1], VERT_OUT);
BLI_mempool_destroy(pc.link_pool);
BLI_heapsimple_free(pc.states, MEM_delete_void);
#if 1
if (state_best.link_last) {
BMOperator op_sub;
BMO_op_initf(bm,
&op_sub,
0,
"connect_verts verts=%fv faces_exclude=%s check_degenerate=%b",
VERT_OUT,
op,
"faces_exclude",
true);
BMO_op_exec(bm, &op_sub);
BMO_slot_copy(&op_sub, slots_out, "edges.out", op, slots_out, "edges.out");
BMO_op_finish(bm, &op_sub);
}
#endif
}
} // namespace blender

View File

@@ -0,0 +1,299 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Create faces or edges (F-key by default).
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_NEW 1
#define ELE_OUT 2
void bmo_contextual_create_exec(BMesh *bm, BMOperator *op)
{
/* NOTE(@ideasman42): doing the best thing here isn't always easy create vs dissolve,
* its nice to support but it _really_ gives issues we might have to not call dissolve. */
BMOIter oiter;
BMHeader *h;
int totv = 0, tote = 0, totf = 0;
const short mat_nr = BMO_slot_int_get(op->slots_in, "mat_nr");
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
/* count number of each element type we were passe */
BMO_ITER (h, &oiter, op->slots_in, "geom", BM_VERT | BM_EDGE | BM_FACE) {
switch (h->htype) {
case BM_VERT:
BMO_vert_flag_enable(bm, (BMVert *)h, ELE_NEW);
totv++;
break;
case BM_EDGE:
BMO_edge_flag_enable(bm, (BMEdge *)h, ELE_NEW);
tote++;
break;
case BM_FACE:
BMO_face_flag_enable(bm, (BMFace *)h, ELE_NEW);
totf++;
break;
}
}
/* --- Support Edge Creation ---
* simple case when we only have 2 verts selected.
*/
if (totv == 2 && tote == 0 && totf == 0) {
BMVert *verts[2];
BMEdge *e;
if (BMO_iter_as_array(op->slots_in, "geom", BM_VERT, reinterpret_cast<void **>(verts), 2) == 2)
{
/* create edge */
e = BM_edge_create(bm, verts[0], verts[1], nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_OUT);
tote += 1;
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_OUT);
}
return;
}
/* --- Support for Special Case ---
* where there is a contiguous edge ring with one isolated vertex.
*
* This example shows 2 edges created from 3 verts
* with 1 free standing vertex. Dotted lines denote the 2 edges that are created.
*
* note that this works for any sided shape.
*
* +--------+
* | .
* | .
* | .
* | .
* +........+ <-- starts out free standing.
*/
/* Here we check for consistency and create 2 edges */
if (totf == 0 && totv >= 4 && totv == tote + 2) {
/* find a free standing vertex and 2 endpoint verts */
BMVert *v, *v_free = nullptr, *v_a = nullptr, *v_b = nullptr;
bool ok = true;
BMO_ITER (v, &oiter, op->slots_in, "geom", BM_VERT) {
/* count how many flagged edges this vertex uses */
const int tot_edges = BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, v, ELE_NEW, true);
if (tot_edges == 0) {
/* only accept 1 free vert */
if (v_free == nullptr) {
v_free = v;
}
else {
ok = false;
} /* only ever want one of these */
}
else if (tot_edges == 1) {
if (v_a == nullptr) {
v_a = v;
}
else if (v_b == nullptr) {
v_b = v;
}
else {
ok = false;
} /* only ever want 2 of these */
}
else if (tot_edges == 2) {
/* do nothing, regular case */
}
else {
ok = false; /* if a vertex has 3+ edge users then cancel - this is only simple cases */
}
if (ok == false) {
break;
}
}
if (ok == true && v_free && v_a && v_b) {
BMEdge *e;
e = BM_edge_create(bm, v_free, v_a, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
e = BM_edge_create(bm, v_free, v_b, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
tote += 2;
}
}
/* --- end special case support, continue as normal --- */
/* -------------------------------------------------------------------- */
/* EdgeNet Create */
if (tote != 0) {
/* call edgenet prepare op so additional face creation cases work */
BMOperator op_sub;
BMO_op_initf(bm, &op_sub, op->flag, "edgenet_prepare edges=%fe", ELE_NEW);
BMO_op_exec(bm, &op_sub);
BMO_slot_buffer_flag_enable(bm, op_sub.slots_out, "edges.out", BM_EDGE, ELE_NEW);
BMO_op_finish(bm, &op_sub);
BMO_op_initf(bm,
&op_sub,
op->flag,
"edgenet_fill edges=%fe mat_nr=%i use_smooth=%b sides=%i",
ELE_NEW,
mat_nr,
use_smooth,
10000);
BMO_op_exec(bm, &op_sub);
/* return if edge net create did something */
if (BMO_slot_buffer_len(op_sub.slots_out, "faces.out")) {
BMO_slot_copy(&op_sub, slots_out, "faces.out", op, slots_out, "faces.out");
BMO_op_finish(bm, &op_sub);
return;
}
BMO_op_finish(bm, &op_sub);
}
/* -------------------------------------------------------------------- */
/* Dissolve Face */
if (totf != 0) { /* should be (totf > 1)... see below */
/* NOTE: allow this to run on single faces so running on a single face
* won't go on to create a face, treating them as random */
BMOperator op_sub;
BMO_op_initf(bm, &op_sub, op->flag, "dissolve_faces faces=%ff", ELE_NEW);
BMO_op_exec(bm, &op_sub);
/* if we dissolved anything, then return */
if (BMO_slot_buffer_len(op_sub.slots_out, "region.out")) {
BMO_slot_copy(&op_sub, slots_out, "region.out", op, slots_out, "faces.out");
BMO_op_finish(bm, &op_sub);
return;
}
BMO_op_finish(bm, &op_sub);
}
/* -------------------------------------------------------------------- */
/* Fill EdgeLoop's - fills isolated loops, different from edgenet */
if (tote > 2) {
BMOperator op_sub;
/* NOTE: in most cases 'edgenet_fill' will handle this case since in common cases
* users fill in empty spaces, however its possible to have an edge selection around
* existing geometry that makes 'edgenet_fill' fail. */
BMO_op_initf(bm, &op_sub, op->flag, "edgeloop_fill edges=%fe", ELE_NEW);
BMO_op_exec(bm, &op_sub);
/* return if edge loop fill did something */
if (BMO_slot_buffer_len(op_sub.slots_out, "faces.out")) {
BMO_slot_copy(&op_sub, slots_out, "faces.out", op, slots_out, "faces.out");
BMO_op_finish(bm, &op_sub);
return;
}
BMO_op_finish(bm, &op_sub);
}
/* -------------------------------------------------------------------- */
/* Continue with ad-hoc fill methods since operators fail,
* edge, vcloud... may add more */
if (false) { /* nice feature but perhaps it should be a different tool? */
/* tricky feature for making a line/edge from selection history...
*
* Rather than do nothing, when 5+ verts are selected, check if they are in our history,
* when this is so, we can make edges from them, but _not_ a face,
* if it is the intention to make a face the user can just hit F again
* since there will be edges next time around.
*
* if all history verts have ELE_NEW flagged and the total number of history verts == totv,
* then we know the history contains all verts here and we can continue...
*/
int tot_ese_v = 0;
for (BMEditSelection &ese : bm->selected) {
if (ese.htype == BM_VERT) {
if (BMO_vert_flag_test(bm, (BMVert *)ese.ele, ELE_NEW)) {
tot_ese_v++;
}
else {
/* unflagged vert means we are not in sync */
tot_ese_v = -1;
break;
}
}
}
if (tot_ese_v == totv) {
BMVert *v_prev = nullptr;
/* yes, all select-history verts are accounted for, now make edges */
for (BMEditSelection &ese : bm->selected) {
if (ese.htype == BM_VERT) {
BMVert *v = reinterpret_cast<BMVert *>(ese.ele);
if (v_prev) {
BMEdge *e = BM_edge_create(bm, v, v_prev, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_OUT);
}
v_prev = v;
}
}
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_OUT);
/* done creating edges */
return;
}
/* -------------------------------------------------------------------- */
/* Fill Vertex Cloud
*
* last resort when all else fails.
*/
if (totv > 2) {
/* TODO: some of these vertices may be connected by edges,
* this connectivity could be used rather than treating
* them as a bunch of isolated verts. */
BMVert **vert_arr = MEM_new_array_uninitialized<BMVert *>(totv, __func__);
BMFace *f;
totv = BMO_iter_as_array(
op->slots_in, "geom", BM_VERT, reinterpret_cast<void **>(vert_arr), totv);
BM_verts_sort_radial_plane(vert_arr, totv);
/* create edges and find the winding (if faces are attached to any existing edges) */
f = BM_face_create_ngon_verts(bm, vert_arr, totv, nullptr, BM_CREATE_NO_DOUBLE, true, true);
if (f) {
BMO_face_flag_enable(bm, f, ELE_OUT);
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
BM_face_copy_shared(bm, f, nullptr, nullptr);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, ELE_OUT);
}
MEM_delete(vert_arr);
}
}
} // namespace blender

View File

@@ -0,0 +1,969 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Removes isolated geometry regions without creating holes in the mesh.
*/
#include <cmath>
#include "MEM_guardedalloc.h"
#include "BLI_math_vector.h"
#include "BLI_stack.h"
#include "BLI_vector.hh"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
/* ***_ISGC: mark for garbage-collection */
#define FACE_MARK 1
#define FACE_ORIG 2
#define FACE_NEW 4
#define FACE_TAG 8
#define EDGE_MARK 1
#define EDGE_TAG 2
#define EDGE_ISGC 8
/**
* Set when the edge is part of a chain,
* where at least of it's vertices has exactly one other connected edge.
*/
#define EDGE_CHAIN 16
#define VERT_MARK 1
#define VERT_MARK_PAIR 4
#define VERT_TAG 2
#define VERT_ISGC 8
#define VERT_MARK_TEAR 16
/* -------------------------------------------------------------------- */
/** \name Internal Utility API
* \{ */
static bool UNUSED_FUNCTION(check_hole_in_region)(BMesh *bm, BMFace *f)
{
BMWalker regwalker;
BMIter liter2;
BMLoop *l2, *l3;
BMFace *f2;
/* Checks if there are any unmarked boundary edges in the face region. */
BMW_init(&regwalker,
bm,
BMW_ISLAND,
BMW_MASK_NOP,
BMW_MASK_NOP,
FACE_MARK,
BMW_FLAG_NOP,
BMW_NIL_LAY,
BMW_DELIMIT_NONE);
for (f2 = static_cast<BMFace *>(BMW_begin(&regwalker, f)); f2;
f2 = static_cast<BMFace *>(BMW_step(&regwalker)))
{
BM_ITER_ELEM (l2, &liter2, f2, BM_LOOPS_OF_FACE) {
l3 = l2->radial_next;
if (BMO_face_flag_test(bm, l3->f, FACE_MARK) != BMO_face_flag_test(bm, l2->f, FACE_MARK)) {
if (!BMO_edge_flag_test(bm, l2->e, EDGE_MARK)) {
return false;
}
}
}
}
BMW_end(&regwalker);
return true;
}
/**
* Calculates the angle of an edge pair, from a combination of raw angle and normal angle.
*/
static float bmo_vert_calc_edge_angle_blended(const BMVert *v)
{
BMEdge *e_pair[2];
const bool is_edge_pair = BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]);
BLI_assert(is_edge_pair);
UNUSED_VARS_NDEBUG(is_edge_pair);
/* Compute the angle between the edges. Start with the raw angle. */
BMVert *v_a = BM_edge_other_vert(e_pair[0], v);
BMVert *v_b = BM_edge_other_vert(e_pair[1], v);
float angle = M_PI - angle_v3v3v3(v_a->co, v->co, v_b->co);
/* There are two ways to measure the angle around a vert with two edges. The first is to
* measure the raw angle between the two neighboring edges, the second is to measure the
* angle of the edges around the vertex normal vector. When the vert is an edge pair
* between two faces, The normal measurement is better in general. In the specific case of
* a vert between two faces, but the faces have a *very* sharp angle between them, then the
* raw angle is better, because the normal is perpendicular to average of the two faces,
* and if the faces are folded almost 180 degrees, the vertex normal becomes more an more
* edge-on to the faces, meaning the angle *around the normal* becomes more and more flat,
* even if it makes a sharp angle when viewed from the side.
*
* When the faces become very folded, the `raw_factor` adds some of the "as seen from the side"
* angle back into the computation, making the algorithm behave more intuitively.
*
* The `raw_factor` is computed as follows:
* - When not a face pair, part this is skipped, and the raw angle is used.
* - When a face pair is co-planar, or has an angle up to 90 degrees, `raw_factor` is 0.0.
* - As angle increases from 90 to 180 degrees, `raw_factor` increases from 0.0 to 1.0.
*/
BMFace *f_pair[2];
if (BM_edge_face_pair(v->e, &f_pair[0], &f_pair[1])) {
/* Due to merges, the normals are not currently trustworthy. Compute them. */
float no_a[3], no_b[3];
BM_face_calc_normal(f_pair[0], no_a);
BM_face_calc_normal(f_pair[1], no_b);
/* Now determine the raw factor based on how folded the faces are. */
const float raw_factor = std::clamp(-dot_v3v3(no_a, no_b), 0.0f, 1.0f);
/* Blend the two ways of computing the angle. */
float normal_angle = M_PI - angle_on_axis_v3v3v3_v3(v_a->co, v->co, v_b->co, v->no);
angle = interpf(angle, normal_angle, raw_factor);
}
return angle;
}
/**
* A wrapper for #BM_vert_collapse_edge which ensures correct hidden state & merges edge flags.
*/
static BMEdge *bm_vert_collapse_edge_and_merge(BMesh *bm, BMVert *v, const bool do_del)
{
/* Merge the header flags on the two edges that will be merged. */
BMEdge *e_pair[2];
const bool is_edge_pair = BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]);
BLI_assert(is_edge_pair);
UNUSED_VARS_NDEBUG(is_edge_pair);
BM_elem_flag_merge_ex(e_pair[0], e_pair[1], BM_ELEM_HIDDEN);
/* Dissolve the vertex. */
BMEdge *e_new = BM_vert_collapse_edge(bm, v->e, v, do_del, true, true);
if (e_new) {
/* Ensure the result of dissolving never leaves visible edges connected to hidden vertices.
* From a user perspective this is an invalid state which tools should not allow. */
if (!BM_elem_flag_test(e_new, BM_ELEM_HIDDEN)) {
if (BM_elem_flag_test(e_new->v1, BM_ELEM_HIDDEN) ||
BM_elem_flag_test(e_new->v2, BM_ELEM_HIDDEN))
{
if (BM_elem_flag_test(e_new, BM_ELEM_SELECT)) {
BM_edge_select_set_noflush(bm, e_new, false);
}
BM_elem_flag_enable(e_new, BM_ELEM_HIDDEN);
}
}
}
return e_new;
}
static void bm_face_split(BMesh *bm, const short oflag, bool use_edge_delete)
{
BLI_Stack *edge_delete_verts;
BMIter iter;
BMVert *v;
if (use_edge_delete) {
edge_delete_verts = BLI_stack_new(sizeof(BMVert *), __func__);
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm, v, oflag)) {
if (BM_vert_is_edge_pair(v) == false) {
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
if (l->f->len > 3) {
if (BMO_vert_flag_test(bm, l->next->v, oflag) == 0 &&
BMO_vert_flag_test(bm, l->prev->v, oflag) == 0)
{
BM_face_split(bm, l->f, l->next, l->prev, nullptr, nullptr, true);
}
}
}
if (use_edge_delete) {
BLI_stack_push(edge_delete_verts, &v);
}
}
}
}
if (use_edge_delete) {
while (!BLI_stack_is_empty(edge_delete_verts)) {
/* remove surrounding edges & faces */
BLI_stack_pop(edge_delete_verts, &v);
while (v->e) {
BM_edge_kill(bm, v->e);
}
}
BLI_stack_free(edge_delete_verts);
}
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Public Execute Functions
* \{ */
void bmo_dissolve_faces_exec(BMesh *bm, BMOperator *op)
{
BMOIter oiter;
BMFace *f;
BMWalker regwalker;
const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts");
if (use_verts) {
/* tag verts that start out with only 2 edges,
* don't remove these later */
BMIter viter;
BMVert *v;
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
BMO_vert_flag_set(bm, v, VERT_MARK, !BM_vert_is_edge_pair(v));
}
}
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_MARK | FACE_TAG);
/* List of regions which are themselves a list of faces. */
Vector<Vector<BMFace *>> regions;
/* collect region */
BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) {
if (!BMO_face_flag_test(bm, f, FACE_TAG)) {
continue;
}
BMW_init(&regwalker,
bm,
BMW_ISLAND_MANIFOLD,
BMW_MASK_NOP,
BMW_MASK_NOP,
FACE_MARK,
/* no need to check BMW_FLAG_TEST_HIDDEN, faces are already marked by the bmo. */
BMW_FLAG_NOP,
BMW_NIL_LAY,
BMW_DELIMIT_NONE);
/* Check there are at least two faces before creating the array. */
BMFace *faces_init[2];
if ((faces_init[0] = static_cast<BMFace *>(BMW_begin(&regwalker, f))) &&
(faces_init[1] = static_cast<BMFace *>(BMW_step(&regwalker))))
{
Vector<BMFace *> faces;
faces.append(faces_init[0]);
faces.append(faces_init[1]);
BMFace *f_iter;
while ((f_iter = static_cast<BMFace *>(BMW_step(&regwalker)))) {
faces.append(f_iter);
}
for (BMFace *face : faces) {
BMO_face_flag_disable(bm, face, FACE_TAG);
BMO_face_flag_enable(bm, face, FACE_ORIG);
}
regions.append_as(std::move(faces));
}
BMW_end(&regwalker);
}
/* track how many faces we should end up with */
int totface_target = bm->totface;
for (Vector<BMFace *> &faces : regions) {
const int64_t faces_len = faces.size();
BMFace *f_double;
BMFace *f_new = BM_faces_join(bm, faces.data(), faces_len, true, &f_double);
if (LIKELY(f_new)) {
/* All the joined faces are gone and the fresh f_new represents their union. */
totface_target -= faces_len - 1;
if (UNLIKELY(f_double)) {
/* `BM_faces_join()` succeeded, but there is a double. Keep the pre-existing face
* and retain its custom-data. Remove the newly made merge result. */
BM_face_kill(bm, f_new);
totface_target -= 1;
f_new = f_double;
}
/* Un-mark the joined face to ensure it is not garbage collected later. */
BMO_face_flag_disable(bm, f_new, FACE_ORIG);
/* Mark the joined face so it can be added to the selection later. */
BMO_face_flag_enable(bm, f_new, FACE_NEW);
}
else {
/* `BM_faces_join()` failed. */
/* NOTE: prior to 3.0 this raised an error: "Could not create merged face".
* Change behavior since it's not useful to fail entirely when a single face-group
* can't be merged into one face. Continue with other face groups instead.
*
* This could optionally do a partial merge, where some faces are joined. */
/* Prevent these faces from being removed. */
for (BMFace *face : faces) {
BMO_face_flag_disable(bm, face, FACE_ORIG);
}
}
}
/* Typically no faces need to be deleted */
if (totface_target != bm->totface) {
BMO_op_callf(bm, op->flag, "delete geom=%ff context=%i", FACE_ORIG, DEL_FACES);
}
if (use_verts) {
BMIter viter;
BMVert *v, *v_next;
BM_ITER_MESH_MUTABLE (v, v_next, &viter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
if (BM_vert_is_edge_pair(v)) {
bm_vert_collapse_edge_and_merge(bm, v, true);
}
}
}
BLI_assert(!BMO_error_occurred_at_level(bm, BMO_ERROR_FATAL));
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW);
}
/**
* Given an edge, and vert that are part of a chain, finds the vert at the far end of the chain.
*
* If `edge_oflag` is provided, each edge along the chain is tagged,
* and walking stops when an edge that is already tagged is found.
* This avoids repeatedly re-walking the chain.
*
* Returns `nullptr` if already tagged edges are found, or if the chain loops.
*/
static BMVert *bmo_find_end_of_chain(BMesh *bm, BMEdge *e, BMVert *v, const short edge_oflag = 0)
{
BMVert *v_init = v;
while (BM_vert_is_edge_pair(v)) {
/* Move one step down the chain. */
e = BM_DISK_EDGE_NEXT(e, v);
v = BM_edge_other_vert(e, v);
/* If we walk to an edge that has already been processed, there's no need to keep working.
* If `edge_oflag` is 0, this test never returns true,
* so iteration will truly go to the end. */
if (BMO_edge_flag_test(bm, e, edge_oflag)) {
return nullptr;
}
/* Optionally mark along the chain.
* If `edge_oflag` is 0, `hflag |= 0` is still faster than if + test + jump. */
BMO_edge_flag_enable(bm, e, edge_oflag);
/* While this should never happen in the context this function is called.
* Avoid an eternal loop even in the case of degenerate geometry. */
BLI_assert(v != v_init);
if (UNLIKELY(v == v_init)) {
return nullptr;
}
}
return v;
}
/**
* Determines if a vert touches an unselected face that would be altered if the vert was dissolved.
* This is sometimes desirable (T-junction) and sometimes not (other cases).
*/
static bool bmo_vert_touches_unselected_face(BMesh *bm, BMVert *v)
{
/* If the vert was already tested and marked, don't test again. */
if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
return false;
}
/* Check each face at this vert by checking each loop. */
BMIter iter;
BMLoop *l_a;
BM_ITER_ELEM (l_a, &iter, v, BM_LOOPS_OF_VERT) {
BMLoop *l_b = BM_loop_other_edge_loop(l_a, v);
/* `l_a` and `l_b` are now the two edges of the face that share this vert.
* if both are untagged, return true. */
if (!BMO_edge_flag_test(bm, l_a->e, EDGE_TAG) && !BMO_edge_flag_test(bm, l_b->e, EDGE_TAG)) {
return true;
}
}
return false;
}
/**
* Counts how many edges touching a vert are tagged with the specified `edge_oflag`.
*/
static int bmo_vert_tagged_edges_count_at_most(BMesh *bm,
BMVert *v,
const short edge_oflag,
const int max)
{
int retval = 0;
BMIter iter;
BMEdge *e;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e, edge_oflag)) {
retval++;
}
if (retval == max) {
return retval;
}
}
return retval;
}
void bmo_dissolve_edges_init(BMOperator *op)
{
/* Set the default not to limit dissolving at all. */
BMO_slot_float_set(op->slots_in, "angle_threshold", M_PI);
}
void bmo_dissolve_edges_exec(BMesh *bm, BMOperator *op)
{
// BMOperator fop;
BMOIter eiter;
BMIter iter;
BMEdge *e, *e_next;
BMVert *v, *v_next;
/* Even when geometry has exact angles like 0 or 90 or 180 deg, `angle_on_axis_v3v3v3_v3`
* can return slightly incorrect values due to cos/sin functions, floating point error, etc.
* This lets the test ignore that tiny bit of math error so users won't notice. */
const float angle_epsilon = RAD2DEGF(0.0001f);
const float angle_threshold = BMO_slot_float_get(op->slots_in, "angle_threshold");
/* Use verts when told to... except, do *not* use verts when angle_threshold is 0.0. */
const bool use_verts = BMO_slot_bool_get(op->slots_in, "use_verts") &&
(angle_threshold > angle_epsilon);
/* If angle threshold is 180, don't bother with angle math, just dissolve everything. */
const bool dissolve_all = (angle_threshold > M_PI - angle_epsilon);
const bool use_face_split = BMO_slot_bool_get(op->slots_in, "use_face_split");
const bool use_preserve_quads = BMO_slot_bool_get(op->slots_in, "use_preserve_quads");
if (use_face_split || use_verts) {
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_TAG);
}
/* Tag certain geometry around the selected edges, for later processing. */
BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
/* Connected edge chains have endpoints with edge pairs. The existing behavior was to dissolve
* the verts, both in the middle, and at the ends, of any selected edges in chains. Mark these
* kind of edges, so we know to skip the angle threshold test later. */
if (BM_vert_is_edge_pair(e->v1) || BM_vert_is_edge_pair(e->v2)) {
BMO_edge_flag_enable(bm, e, EDGE_CHAIN);
}
BMFace *f_pair[2];
if (BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
/* Tag all the edges and verts of the two faces on either side of this edge.
* This edge is going to be dissolved, and after that happens, some of those elements of the
* surrounding faces might end up as loose geometry, depending on how the dissolve affected
* geometry near them. Tag them `*_ISGC`, to be checked later, and cleaned up if loose. */
uint j;
for (j = 0; j < 2; j++) {
BMLoop *l_first, *l_iter;
l_iter = l_first = BM_FACE_FIRST_LOOP(f_pair[j]);
do {
BMO_vert_flag_enable(bm, l_iter->v, VERT_ISGC);
BMO_edge_flag_enable(bm, l_iter->e, EDGE_ISGC);
} while ((l_iter = l_iter->next) != l_first);
}
/* If using verts, and this edge is part of a chain that will be dissolved, then extend
* `EDGE_TAG` to both ends of the chain. This marks any edges that, even though they might
* not be selected, will also be dissolved when the face merge happens. This allows counting
* how many edges will remain after the dissolves are done later. */
if (use_verts && BMO_edge_flag_test(bm, e, EDGE_CHAIN)) {
bmo_find_end_of_chain(bm, e, e->v1, EDGE_TAG);
bmo_find_end_of_chain(bm, e, e->v2, EDGE_TAG);
}
}
}
if (use_verts) {
/* Mark all verts that are candidates to be dissolved. */
BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
/* Edges only dissolve if they are manifold, so if the edge won't be dissolved, then there's
* no reason to mark either of its ends for dissolve. */
BMFace *f_pair[2];
if (!BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
continue;
}
/* if `BM_faces_join_pair` will be done, mark the correct two verts at the ends for
* dissolve. */
for (int i = 0; i < 2; i++) {
BMVert *v_edge = *((&e->v1) + i);
/* An edge between two triangles should dissolve to a quad, akin to un-triangulate.
* Prevent dissolving either corner, if doing so would collapse the corner, converting
* the quad to a triangle or wire. This happens when two triangles join, and the vert
* has two untagged edges, and the _only_ other tagged edge is this edge that's about
* to be dissolved. When that case is found, skip it, do not tag it.
* The edge count test ensures that if we're dissolving a chain, the crossing loop cuts
* will still be dissolved, even if they happen to make an "un-triangulate" case.
* This is not done when face split is active, because face split often creates triangle
* pairs on edges that touch boundaries, resulting in the boundary vert not dissolving. */
if (use_preserve_quads && f_pair[0]->len == 3 && f_pair[1]->len == 3 &&
bmo_vert_tagged_edges_count_at_most(bm, v_edge, EDGE_TAG, 2) == 1)
{
continue;
}
/* If a chain, follow the chain until the end is found. The whole chain will dissolve, so
* the test needs to happen there, at the end of the chain, where it meets other geometry,
* not here, at the end of a selected edge that only touches other parts of the chain. */
if (BM_vert_is_edge_pair(v_edge)) {
v_edge = bmo_find_end_of_chain(bm, e, v_edge, EDGE_CHAIN);
}
/* If the end of the chain was searched for and was not located, take no action. */
if (v_edge == nullptr) {
continue;
}
/* When the user selected multiple edges that meet at one vert, and there are existing
* faces at that vert that are *not* selected, then remove that vert from consideration for
* dissolve.
*
* This logic implements the following:
* - When several dissolved edges cross a loop cut, the loop cut vert should be dissolved.
* (`bmo_vert_touches_unselected_face()` will be false).
* - When dissolve edges *end* at a T on a loop cut, the loop cut vert should be dissolved.
* (`bmo_vert_tagged_edges_count_at_most()` will be 1).
* - When multiple dissolve edges touch the corner of a quad or triangle, but leave in a
* different direction, regard that contact is 'incidental' and the face should stay.
* (both tests will be true).
*/
if (bmo_vert_touches_unselected_face(bm, v_edge) &&
bmo_vert_tagged_edges_count_at_most(bm, v_edge, EDGE_TAG, 2) != 1)
{
continue;
}
/* Mark for dissolve. */
BMO_vert_flag_enable(bm, v_edge, VERT_MARK);
}
}
}
if (use_face_split) {
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BMIter itersub;
int untag_count = 0;
BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, EDGE_TAG)) {
untag_count++;
}
}
/* check that we have 2 edges remaining after dissolve */
if (untag_count <= 2) {
BMO_vert_flag_enable(bm, v, VERT_TAG);
}
}
bm_face_split(bm, VERT_TAG, false);
}
/* Merge any face pairs that straddle a selected edge. */
BMO_ITER (e, &eiter, op->slots_in, "edges", BM_EDGE) {
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(e, &l_a, &l_b)) {
BM_faces_join_pair(bm, l_a, l_b, false, nullptr);
}
}
/* Cleanup geometry. Remove any edges that are garbage collectible and that have became
* irrelevant (no loops) because of face merges. */
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if ((e->l == nullptr) && BMO_edge_flag_test(bm, e, EDGE_ISGC)) {
BM_edge_kill(bm, e);
}
}
/* Cleanup geometry. Remove any verts that are garbage collectible and that have became
* isolated verts (no edges) because of edge dissolves. */
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if ((v->e == nullptr) && BMO_vert_flag_test(bm, v, VERT_ISGC)) {
BM_vert_kill(bm, v);
}
}
/* If dissolving verts, then evaluate each VERT_MARK vert. */
if (use_verts) {
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
/* If it is not an edge pair, it cannot be merged. */
BMEdge *e_pair[2];
if (BM_vert_edge_pair(v, &e_pair[0], &e_pair[1]) == false) {
BMO_vert_flag_disable(bm, v, VERT_MARK);
continue;
}
/* At an angle threshold of 180, dissolve everything, skip the math of the angle test. */
if (dissolve_all) {
/* VERT_MARK remains enabled. */
continue;
}
/* Verts in edge chains ignore the angle test. This maintains the previous behavior,
* where such verts were not subject to the angle threshold.
*
* When edge chains are selected for dissolve, all edge-pair verts at *both* ends of each
* selected edge will be dissolved, combining the selected edges into their neighbors.
*
* Note that when only *part* of a chain is selected, this *will* alter unselected edges,
* because selected edges will merge *into their unselected neighbors*. This too, has been
* maintained, for consistency with the previous (but possibly unintentional) behavior. */
if (BMO_edge_flag_test(bm, e_pair[0], EDGE_CHAIN) ||
BMO_edge_flag_test(bm, e_pair[1], EDGE_CHAIN))
{
/* VERT_MARK remains enabled. */
continue;
}
/* If the angle at the vert is larger than the threshold, it cannot be merged. */
if (bmo_vert_calc_edge_angle_blended(v) > angle_threshold - angle_epsilon) {
BMO_vert_flag_disable(bm, v, VERT_MARK);
continue;
}
}
/* Dissolve all verts that remain tagged. This is done in a separate iteration pass. Otherwise
* the early dissolves would alter the angles measured at neighboring verts tested later. */
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
/* Even though pairs were checked before, the process of performing edge merges
* might change a neighboring vert such that it is no longer an edge pair. */
if (!BM_vert_is_edge_pair(v)) {
continue;
}
bm_vert_collapse_edge_and_merge(bm, v, true);
}
}
}
void bmo_dissolve_verts_exec(BMesh *bm, BMOperator *op)
{
BMOIter oiter;
BMIter iter;
BMVert *v, *v_next;
BMEdge *e, *e_next;
const bool use_face_split = BMO_slot_bool_get(op->slots_in, "use_face_split");
const bool use_boundary_tear = BMO_slot_bool_get(op->slots_in, "use_boundary_tear");
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
BMO_vert_flag_enable(bm, v, VERT_MARK | VERT_ISGC);
}
if (use_face_split) {
bm_face_split(bm, VERT_MARK, false);
}
if (use_boundary_tear) {
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
if (!BM_vert_is_edge_pair(v)) {
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(e)) {
BMO_vert_flag_enable(bm, v, VERT_MARK_TEAR);
break;
}
}
}
}
bm_face_split(bm, VERT_MARK_TEAR, true);
}
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
BMIter itersub;
BMLoop *l_first;
BMEdge *e_first = nullptr;
BM_ITER_ELEM (l_first, &itersub, v, BM_LOOPS_OF_VERT) {
BMLoop *l_iter;
l_iter = l_first;
do {
BMO_vert_flag_enable(bm, l_iter->v, VERT_ISGC);
BMO_edge_flag_enable(bm, l_iter->e, EDGE_ISGC);
} while ((l_iter = l_iter->next) != l_first);
e_first = l_first->e;
}
/* important e_first won't be deleted */
if (e_first) {
e = e_first;
do {
e_next = BM_DISK_EDGE_NEXT(e, v);
if (BM_edge_is_wire(e)) {
BM_edge_kill(bm, e);
}
} while ((e = e_next) != e_first);
}
}
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
/* tag here so we avoid feedback loop (checking topology as we edit) */
if (BM_vert_is_edge_pair(v)) {
BMO_vert_flag_enable(bm, v, VERT_MARK_PAIR);
}
}
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
BMIter itersub;
/* Merge across every edge that touches `v`. This does a `BM_faces_join_pair()` for each edge.
* There may be a possible performance improvement available here, for high valence verts.
* Collecting a list of 20 faces and performing a single `BM_faces_join` would almost certainly
* more performant than doing 19 separate `BM_faces_join_pair()` of 2 faces each in sequence.
* Low valence verts would need benchmarking, to check that such a change isn't harmful. */
if (!BMO_vert_flag_test(bm, v, VERT_MARK_PAIR)) {
BM_ITER_ELEM (e, &itersub, v, BM_EDGES_OF_VERT) {
BMLoop *l_a, *l_b;
if (BM_edge_loop_pair(e, &l_a, &l_b)) {
BM_faces_join_pair(bm, l_a, l_b, false, nullptr);
}
}
}
}
/* Cleanup geometry (#BM_faces_join_pair, but it removes geometry we're looping on)
* so do this in a separate pass instead. */
BM_ITER_MESH_MUTABLE (e, e_next, &iter, bm, BM_EDGES_OF_MESH) {
if ((e->l == nullptr) && BMO_edge_flag_test(bm, e, EDGE_ISGC)) {
BM_edge_kill(bm, e);
}
}
/* final cleanup */
BMO_ITER (v, &oiter, op->slots_in, "verts", BM_VERT) {
if (BM_vert_is_edge_pair(v)) {
bm_vert_collapse_edge_and_merge(bm, v, false);
}
}
BM_ITER_MESH_MUTABLE (v, v_next, &iter, bm, BM_VERTS_OF_MESH) {
if ((v->e == nullptr) && BMO_vert_flag_test(bm, v, VERT_ISGC)) {
BM_vert_kill(bm, v);
}
}
/* done with cleanup */
}
void bmo_dissolve_limit_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *einput = BMO_slot_get(op->slots_in, "edges");
BMOpSlot *vinput = BMO_slot_get(op->slots_in, "verts");
const float angle_max = M_PI_2;
const float angle_limit = min_ff(angle_max, BMO_slot_float_get(op->slots_in, "angle_limit"));
const bool do_dissolve_boundaries = BMO_slot_bool_get(op->slots_in, "use_dissolve_boundaries");
const BMO_Delimit delimit = BMO_Delimit(BMO_slot_int_get(op->slots_in, "delimit"));
BM_mesh_decimate_dissolve_ex(bm,
angle_limit,
do_dissolve_boundaries,
delimit,
reinterpret_cast<BMVert **> BMO_SLOT_AS_BUFFER(vinput),
vinput->len,
reinterpret_cast<BMEdge **> BMO_SLOT_AS_BUFFER(einput),
einput->len,
FACE_NEW);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "region.out", BM_FACE, FACE_NEW);
}
#define EDGE_MARK 1
#define EDGE_COLLAPSE 2
static void bm_mesh_edge_collapse_flagged(BMesh *bm, const int flag, const short oflag)
{
BMO_op_callf(bm, flag, "collapse edges=%fe uvs=%b", oflag, true);
}
void bmo_dissolve_degenerate_exec(BMesh *bm, BMOperator *op)
{
const float dist = BMO_slot_float_get(op->slots_in, "dist");
const float dist_sq = dist * dist;
bool found;
BMIter eiter;
BMEdge *e;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
/* collapse zero length edges, this accounts for zero area faces too */
found = false;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm, e, EDGE_MARK)) {
if (BM_edge_calc_length_squared(e) < dist_sq) {
BMO_edge_flag_enable(bm, e, EDGE_COLLAPSE);
found = true;
}
}
/* clear all loop tags (checked later) */
if (e->l) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
BM_elem_flag_disable(l_iter, BM_ELEM_TAG);
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
if (found) {
bm_mesh_edge_collapse_flagged(bm, op->flag, EDGE_COLLAPSE);
}
/* clip degenerate ears from the face */
found = false;
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (e->l && BMO_edge_flag_test(bm, e, EDGE_MARK)) {
BMLoop *l_iter, *l_first;
l_iter = l_first = e->l;
do {
if (
/* check the loop hasn't already been tested (and flag not to test again) */
!BM_elem_flag_test(l_iter, BM_ELEM_TAG) &&
((void)BM_elem_flag_enable(l_iter, BM_ELEM_TAG),
/* check we're marked to tested (radial edge already tested) */
BMO_edge_flag_test(bm, l_iter->prev->e, EDGE_MARK) &&
/* check edges are not already going to be collapsed */
!BMO_edge_flag_test(bm, l_iter->e, EDGE_COLLAPSE) &&
!BMO_edge_flag_test(bm, l_iter->prev->e, EDGE_COLLAPSE)))
{
/* test if the faces loop (ear) is degenerate */
float dir_prev[3], len_prev;
float dir_next[3], len_next;
sub_v3_v3v3(dir_prev, l_iter->prev->v->co, l_iter->v->co);
sub_v3_v3v3(dir_next, l_iter->next->v->co, l_iter->v->co);
len_prev = normalize_v3(dir_prev);
len_next = normalize_v3(dir_next);
if ((len_v3v3(dir_prev, dir_next) * min_ff(len_prev, len_next)) <= dist) {
bool reset = false;
if (fabsf(len_prev - len_next) <= dist) {
/* both edges the same length */
if (l_iter->f->len == 3) {
/* ideally this would have been discovered with short edge test above */
BMO_edge_flag_enable(bm, l_iter->next->e, EDGE_COLLAPSE);
found = true;
}
else {
/* add a joining edge and tag for removal */
BMLoop *l_split;
if (BM_face_split(
bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
{
BMO_edge_flag_enable(bm, l_split->e, EDGE_COLLAPSE);
found = true;
reset = true;
}
}
}
else if (len_prev < len_next) {
/* split 'l_iter->e', then join the vert with next */
BMVert *v_new;
BMEdge *e_new;
BMLoop *l_split;
v_new = BM_edge_split(bm, l_iter->e, l_iter->v, &e_new, len_prev / len_next);
BLI_assert(v_new == l_iter->next->v);
(void)v_new;
if (BM_face_split(
bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
{
BMO_edge_flag_enable(bm, l_split->e, EDGE_COLLAPSE);
found = true;
}
reset = true;
}
else if (len_next < len_prev) {
/* split 'l_iter->prev->e', then join the vert with next */
BMVert *v_new;
BMEdge *e_new;
BMLoop *l_split;
v_new = BM_edge_split(bm, l_iter->prev->e, l_iter->v, &e_new, len_next / len_prev);
BLI_assert(v_new == l_iter->prev->v);
(void)v_new;
if (BM_face_split(
bm, l_iter->f, l_iter->prev, l_iter->next, &l_split, nullptr, true))
{
BMO_edge_flag_enable(bm, l_split->e, EDGE_COLLAPSE);
found = true;
}
reset = true;
}
if (reset) {
/* we can't easily track where we are on the radial edge, reset! */
l_first = l_iter;
}
}
}
} while ((l_iter = l_iter->radial_next) != l_first);
}
}
if (found) {
bm_mesh_edge_collapse_flagged(bm, op->flag, EDGE_COLLAPSE);
}
}
/** \} */
} // namespace blender

View File

@@ -0,0 +1,747 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Duplicate, Split, Split operators.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/* local flag define */
#define DUPE_INPUT 1 /* input from operator */
#define DUPE_NEW 2
#define DUPE_DONE 4
// #define DUPE_MAPPED 8 // UNUSED
/**
* COPY VERTEX
*
* Copy an existing vertex from one bmesh to another.
*/
static BMVert *bmo_vert_copy(BMOperator *op,
BMOpSlot *slot_vertmap_out,
BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_vert_map,
BMVert *v_src,
Map<BMVert *, BMVert *> &vhash)
{
BMVert *v_dst;
/* Create a new vertex */
v_dst = BM_vert_create(bm_dst, v_src->co, nullptr, BM_CREATE_SKIP_CD);
BMO_slot_map_elem_insert(op, slot_vertmap_out, v_src, v_dst);
BMO_slot_map_elem_insert(op, slot_vertmap_out, v_dst, v_src);
/* Insert new vertex into the vert hash */
vhash.add(v_src, v_dst);
/* Copy attributes */
if (cd_vert_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_vert_map.value(), v_src, v_dst);
}
else {
BM_elem_attrs_copy(bm_dst, v_src, v_dst);
}
/* Mark the vert for output */
BMO_vert_flag_enable(bm_dst, v_dst, DUPE_NEW);
return v_dst;
}
/**
* COPY EDGE
*
* Copy an existing edge from one bmesh to another.
*/
static BMEdge *bmo_edge_copy(BMOperator *op,
BMOpSlot *slot_edgemap_out,
BMOpSlot *slot_boundarymap_out,
BMesh *bm_dst,
BMesh *bm_src,
const std::optional<BMCustomDataCopyMap> &cd_edge_map,
BMEdge *e_src,
Map<BMVert *, BMVert *> &vhash,
Map<BMEdge *, BMEdge *> &ehash,
const bool use_edge_flip_from_face)
{
BMEdge *e_dst;
BMVert *e_dst_v1, *e_dst_v2;
uint rlen;
/* see if any of the neighboring faces are
* not being duplicated. in that case,
* add it to the new/old map. */
/* lookup edge */
rlen = 0;
if (e_src->l) {
BMLoop *l_iter_src, *l_first_src;
l_iter_src = l_first_src = e_src->l;
do {
if (BMO_face_flag_test(bm_src, l_iter_src->f, DUPE_INPUT)) {
rlen++;
}
} while ((l_iter_src = l_iter_src->radial_next) != l_first_src);
}
/* Lookup v1 and v2 */
e_dst_v1 = vhash.lookup(e_src->v1);
e_dst_v2 = vhash.lookup(e_src->v2);
/* Create a new edge */
e_dst = BM_edge_create(bm_dst, e_dst_v1, e_dst_v2, nullptr, BM_CREATE_SKIP_CD);
BMO_slot_map_elem_insert(op, slot_edgemap_out, e_src, e_dst);
BMO_slot_map_elem_insert(op, slot_edgemap_out, e_dst, e_src);
/* Add to new/old edge map if necessary. */
if (rlen < 2) {
/* not sure what non-manifold cases of greater than three
* radial should do. */
BMO_slot_map_elem_insert(op, slot_boundarymap_out, e_src, e_dst);
}
/* Insert new edge into the edge hash */
ehash.add(e_src, e_dst);
/* Copy attributes */
if (cd_edge_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_edge_map.value(), e_src, e_dst);
}
else {
BM_elem_attrs_copy(bm_dst, e_src, e_dst);
}
/* Mark the edge for output */
BMO_edge_flag_enable(bm_dst, e_dst, DUPE_NEW);
if (use_edge_flip_from_face) {
/* Take winding from previous face (if we had one),
* otherwise extruding a duplicated edges gives bad normals, see: #62487. */
if (BM_edge_is_boundary(e_src) && (e_src->l->v == e_src->v1)) {
BM_edge_verts_swap(e_dst);
}
}
return e_dst;
}
/**
* COPY FACE
*
* Copy an existing face from one bmesh to another.
*/
static BMFace *bmo_face_copy(BMOperator *op,
BMOpSlot *slot_facemap_out,
BMesh *bm_dst,
const std::optional<BMCustomDataCopyMap> &cd_face_map,
const std::optional<BMCustomDataCopyMap> &cd_loop_map,
BMFace *f_src,
Map<BMVert *, BMVert *> &vhash,
Map<BMEdge *, BMEdge *> &ehash)
{
BMFace *f_dst;
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> vtar(f_src->len);
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edar(f_src->len);
BMLoop *l_iter_src, *l_iter_dst, *l_first_src;
int i;
l_first_src = BM_FACE_FIRST_LOOP(f_src);
/* lookup edge */
l_iter_src = l_first_src;
i = 0;
do {
vtar[i] = vhash.lookup(l_iter_src->v);
edar[i] = ehash.lookup(l_iter_src->e);
i++;
} while ((l_iter_src = l_iter_src->next) != l_first_src);
/* create new face */
f_dst = BM_face_create(bm_dst, vtar.data(), edar.data(), f_src->len, nullptr, BM_CREATE_SKIP_CD);
BMO_slot_map_elem_insert(op, slot_facemap_out, f_src, f_dst);
BMO_slot_map_elem_insert(op, slot_facemap_out, f_dst, f_src);
/* Copy attributes */
if (cd_face_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_face_map.value(), f_src, f_dst);
}
else {
BM_elem_attrs_copy(bm_dst, f_src, f_dst);
}
/* copy per-loop custom data */
l_iter_src = l_first_src;
l_iter_dst = BM_FACE_FIRST_LOOP(f_dst);
do {
if (cd_loop_map.has_value()) {
BM_elem_attrs_copy(bm_dst, cd_loop_map.value(), l_iter_src, l_iter_dst);
}
else {
BM_elem_attrs_copy(bm_dst, l_iter_src, l_iter_dst);
}
} while ((void)(l_iter_dst = l_iter_dst->next), (l_iter_src = l_iter_src->next) != l_first_src);
/* Mark the face for output */
BMO_face_flag_enable(bm_dst, f_dst, DUPE_NEW);
return f_dst;
}
/**
* COPY MESH
*
* Internal Copy function.
*/
static void bmo_mesh_copy(BMOperator *op, BMesh *bm_dst, BMesh *bm_src)
{
const bool use_select_history = BMO_slot_bool_get(op->slots_in, "use_select_history");
const bool use_edge_flip_from_face = BMO_slot_bool_get(op->slots_in, "use_edge_flip_from_face");
BMVert *v = nullptr, *v2;
BMEdge *e = nullptr;
BMFace *f = nullptr;
BMIter viter, eiter, fiter;
BMOpSlot *slot_boundary_map_out = BMO_slot_get(op->slots_out, "boundary_map.out");
BMOpSlot *slot_isovert_map_out = BMO_slot_get(op->slots_out, "isovert_map.out");
BMOpSlot *slot_vert_map_out = BMO_slot_get(op->slots_out, "vert_map.out");
BMOpSlot *slot_edge_map_out = BMO_slot_get(op->slots_out, "edge_map.out");
BMOpSlot *slot_face_map_out = BMO_slot_get(op->slots_out, "face_map.out");
/* initialize pointer hashes */
Map<BMVert *, BMVert *> vhash;
Map<BMEdge *, BMEdge *> ehash;
const std::optional<BMCustomDataCopyMap> cd_vert_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->vdata, bm_dst->vdata)};
const std::optional<BMCustomDataCopyMap> cd_edge_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->edata, bm_dst->edata)};
const std::optional<BMCustomDataCopyMap> cd_face_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->pdata, bm_dst->pdata)};
const std::optional<BMCustomDataCopyMap> cd_loop_map =
(bm_src == bm_dst) ? std::nullopt :
std::optional<BMCustomDataCopyMap>{
CustomData_bmesh_copy_map_calc(bm_src->ldata, bm_dst->ldata)};
/* duplicate flagged vertices */
BM_ITER_MESH (v, &viter, bm_src, BM_VERTS_OF_MESH) {
if (BMO_vert_flag_test(bm_src, v, DUPE_INPUT) &&
BMO_vert_flag_test(bm_src, v, DUPE_DONE) == false)
{
BMIter iter;
bool isolated = true;
v2 = bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, v, vhash);
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (BMO_face_flag_test(bm_src, f, DUPE_INPUT)) {
isolated = false;
break;
}
}
if (isolated) {
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm_src, e, DUPE_INPUT)) {
isolated = false;
break;
}
}
}
if (isolated) {
BMO_slot_map_elem_insert(op, slot_isovert_map_out, v, v2);
}
BMO_vert_flag_enable(bm_src, v, DUPE_DONE);
}
}
/* now we dupe all the edges */
BM_ITER_MESH (e, &eiter, bm_src, BM_EDGES_OF_MESH) {
if (BMO_edge_flag_test(bm_src, e, DUPE_INPUT) &&
BMO_edge_flag_test(bm_src, e, DUPE_DONE) == false)
{
/* make sure that verts are copied */
if (!BMO_vert_flag_test(bm_src, e->v1, DUPE_DONE)) {
bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, e->v1, vhash);
BMO_vert_flag_enable(bm_src, e->v1, DUPE_DONE);
}
if (!BMO_vert_flag_test(bm_src, e->v2, DUPE_DONE)) {
bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, e->v2, vhash);
BMO_vert_flag_enable(bm_src, e->v2, DUPE_DONE);
}
/* now copy the actual edge */
bmo_edge_copy(op,
slot_edge_map_out,
slot_boundary_map_out,
bm_dst,
bm_src,
cd_edge_map,
e,
vhash,
ehash,
use_edge_flip_from_face);
BMO_edge_flag_enable(bm_src, e, DUPE_DONE);
}
}
/* first we dupe all flagged faces and their elements from source */
BM_ITER_MESH (f, &fiter, bm_src, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm_src, f, DUPE_INPUT)) {
/* vertex pass */
BM_ITER_ELEM (v, &viter, f, BM_VERTS_OF_FACE) {
if (!BMO_vert_flag_test(bm_src, v, DUPE_DONE)) {
bmo_vert_copy(op, slot_vert_map_out, bm_dst, cd_vert_map, v, vhash);
BMO_vert_flag_enable(bm_src, v, DUPE_DONE);
}
}
/* edge pass */
BM_ITER_ELEM (e, &eiter, f, BM_EDGES_OF_FACE) {
if (!BMO_edge_flag_test(bm_src, e, DUPE_DONE)) {
bmo_edge_copy(op,
slot_edge_map_out,
slot_boundary_map_out,
bm_dst,
bm_src,
cd_edge_map,
e,
vhash,
ehash,
use_edge_flip_from_face);
BMO_edge_flag_enable(bm_src, e, DUPE_DONE);
}
}
bmo_face_copy(op, slot_face_map_out, bm_dst, cd_face_map, cd_loop_map, f, vhash, ehash);
BMO_face_flag_enable(bm_src, f, DUPE_DONE);
}
}
if (use_select_history) {
BLI_assert(bm_src == bm_dst);
BMO_mesh_selected_remap(
bm_dst, slot_vert_map_out, slot_edge_map_out, slot_face_map_out, false);
}
}
/**
* Duplicate Operator
*
* Duplicates verts, edges and faces of a mesh.
*
* INPUT SLOTS:
*
* BMOP_DUPE_VINPUT: Buffer containing pointers to mesh vertices to be duplicated
* BMOP_DUPE_EINPUT: Buffer containing pointers to mesh edges to be duplicated
* BMOP_DUPE_FINPUT: Buffer containing pointers to mesh faces to be duplicated
*
* OUTPUT SLOTS:
*
* BMOP_DUPE_VORIGINAL: Buffer containing pointers to the original mesh vertices
* BMOP_DUPE_EORIGINAL: Buffer containing pointers to the original mesh edges
* BMOP_DUPE_FORIGINAL: Buffer containing pointers to the original mesh faces
* BMOP_DUPE_VNEW: Buffer containing pointers to the new mesh vertices
* BMOP_DUPE_ENEW: Buffer containing pointers to the new mesh edges
* BMOP_DUPE_FNEW: Buffer containing pointers to the new mesh faces
*/
void bmo_duplicate_exec(BMesh *bm, BMOperator *op)
{
BMOperator *dupeop = op;
BMesh *bm_dst = static_cast<BMesh *>(BMO_slot_ptr_get(op->slots_in, "dest"));
if (!bm_dst) {
bm_dst = bm;
}
/* flag input */
BMO_slot_buffer_flag_enable(bm, dupeop->slots_in, "geom", BM_ALL_NOLOOP, DUPE_INPUT);
/* use the internal copy function */
bmo_mesh_copy(dupeop, bm_dst, bm);
/* Output */
/* First copy the input buffers to output buffers - original data */
BMO_slot_copy(dupeop, slots_in, "geom", dupeop, slots_out, "geom_orig.out");
/* Now alloc the new output buffers */
BMO_slot_buffer_from_enabled_flag(
bm, dupeop, dupeop->slots_out, "geom.out", BM_ALL_NOLOOP, DUPE_NEW);
}
#if 0 /* UNUSED */
/**
* executes the duplicate operation, feeding elements of
* type flag etypeflag and header flag to it.
* \note to get more useful information (such as the mapping from
* original to new elements) you should run the dupe op manually.
*/
void BMO_dupe_from_flag(BMesh *bm, int htype, const char hflag)
{
BMOperator dupeop;
BMO_op_init(bm, &dupeop, "duplicate");
BMO_slot_buffer_from_enabled_hflag(bm, &dupeop, "geom", htype, hflag);
BMO_op_exec(bm, &dupeop);
BMO_op_finish(bm, &dupeop);
}
#endif
/**
* Split Operator
*
* Duplicates verts, edges and faces of a mesh but also deletes the originals.
*
* INPUT SLOTS:
*
* BMOP_DUPE_VINPUT: Buffer containing pointers to mesh vertices to be split
* BMOP_DUPE_EINPUT: Buffer containing pointers to mesh edges to be split
* BMOP_DUPE_FINPUT: Buffer containing pointers to mesh faces to be split
*
* OUTPUT SLOTS:
*
* BMOP_DUPE_VOUTPUT: Buffer containing pointers to the split mesh vertices
* BMOP_DUPE_EOUTPUT: Buffer containing pointers to the split mesh edges
* BMOP_DUPE_FOUTPUT: Buffer containing pointers to the split mesh faces
*
* \note Lower level uses of this operator may want to use #BM_mesh_separate_faces
* Since it's faster for the 'use_only_faces' case.
*/
void bmo_split_exec(BMesh *bm, BMOperator *op)
{
#define SPLIT_INPUT 1
BMOperator *splitop = op;
BMOperator dupeop;
const bool use_only_faces = BMO_slot_bool_get(op->slots_in, "use_only_faces");
/* initialize our sub-operator */
BMO_op_init(bm, &dupeop, op->flag, "duplicate");
BMO_slot_copy(splitop, slots_in, "geom", &dupeop, slots_in, "geom");
BMO_op_exec(bm, &dupeop);
BMFace *new_act_face = static_cast<BMFace *>(
BMO_slot_map_elem_get(BMO_slot_get(dupeop.slots_out, "face_map.out"), bm->act_face));
if (new_act_face) {
bm->act_face = new_act_face;
}
BMO_slot_buffer_flag_enable(bm, splitop->slots_in, "geom", BM_ALL_NOLOOP, SPLIT_INPUT);
if (use_only_faces) {
BMVert *v;
BMEdge *e;
BMFace *f;
BMIter iter, iter2;
/* make sure to remove edges and verts we don't need */
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
bool found = false;
BM_ITER_ELEM (f, &iter2, e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f, SPLIT_INPUT)) {
found = true;
break;
}
}
if (found == false) {
BMO_edge_flag_enable(bm, e, SPLIT_INPUT);
}
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
bool found = false;
BM_ITER_ELEM (e, &iter2, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, SPLIT_INPUT)) {
found = true;
break;
}
}
if (found == false) {
BMO_vert_flag_enable(bm, v, SPLIT_INPUT);
}
}
}
BMO_slot_copy(&dupeop, slots_out, "geom.out", splitop, slots_out, "geom.out");
BMO_slot_copy(&dupeop, slots_out, "isovert_map.out", splitop, slots_out, "isovert_map.out");
/* connect outputs of dupe to delete, excluding keep geometry */
BMO_mesh_delete_oflag_context(
bm,
SPLIT_INPUT,
DEL_FACES,
/* Call before deletion so deleted geometry isn't copied. */
[&bm, &dupeop, &splitop]() {
/* Now we make our outputs by copying the dupe output. */
/* NOTE: `boundary_map.out` can't use #BMO_slot_copy because some of the "source"
* geometry has been removed. In this case the (source -> destination) map doesn't work.
* In this case there isn't an especially good option.
* The geometry needs to be included so the boundary is accessible.
* Use the "destination" as the key and the value since it avoids adding freed
* geometry into the map and can be easily detected by other operators.
* See: #142633. */
const char *slot_name_boundary_map = "boundary_map.out";
BMOpSlot *splitop_boundary_map = BMO_slot_get(splitop->slots_out, slot_name_boundary_map);
BMOIter siter;
BMElem *ele_key;
BMO_ITER (ele_key, &siter, dupeop.slots_out, slot_name_boundary_map, 0) {
BMElem *ele_val = static_cast<BMElem *>(BMO_iter_map_value_ptr(&siter));
if (BMO_elem_flag_test(bm, ele_key, SPLIT_INPUT)) {
ele_key = ele_val;
}
BMO_slot_map_elem_insert(splitop, splitop_boundary_map, ele_key, ele_val);
}
});
/* cleanup */
BMO_op_finish(bm, &dupeop);
#undef SPLIT_INPUT
}
void bmo_delete_exec(BMesh *bm, BMOperator *op)
{
#define DEL_INPUT 1
BMOperator *delop = op;
/* Mark Buffer */
BMO_slot_buffer_flag_enable(bm, delop->slots_in, "geom", BM_ALL_NOLOOP, DEL_INPUT);
BMO_mesh_delete_oflag_context(bm, DEL_INPUT, BMO_slot_int_get(op->slots_in, "context"), nullptr);
#undef DEL_INPUT
}
/**
* Spin Operator
*
* Extrude or duplicate geometry a number of times,
* rotating and possibly translating after each step
*/
void bmo_spin_exec(BMesh *bm, BMOperator *op)
{
BMOperator dupop, extop;
float cent[3], dvec[3];
float axis[3];
BMO_slot_vec_get(op->slots_in, "cent", cent);
BMO_slot_vec_get(op->slots_in, "axis", axis);
normalize_v3(axis);
BMO_slot_vec_get(op->slots_in, "dvec", dvec);
const bool use_dvec = !is_zero_v3(dvec);
const int steps = BMO_slot_int_get(op->slots_in, "steps");
const float angle_total = BMO_slot_float_get(op->slots_in, "angle");
const bool do_dupli = BMO_slot_bool_get(op->slots_in, "use_duplicate");
const bool use_normal_flip = BMO_slot_bool_get(op->slots_in, "use_normal_flip");
/* Caller needs to perform other sanity checks (such as the spin being 360d). */
const bool use_merge = BMO_slot_bool_get(op->slots_in, "use_merge") &&
/* Don't create duplicate geometry. */
(steps >= 3) &&
/* Only the "extrude" code path supports merging. */
(do_dupli == false);
BMVert **vtable = nullptr;
float (*vtable_coords)[3] = nullptr;
/* When merging, store the original vertices to splice them back together. */
if (use_merge) {
vtable = MEM_new_array_uninitialized<BMVert *>(bm->totvert, __func__);
}
/* When extruding, always restore the original location before rotating. */
if (do_dupli == false) {
vtable_coords = MEM_new_array_uninitialized<float[3]>(bm->totvert, __func__);
}
if (vtable || vtable_coords) {
int i = 0;
BMIter iter;
BMVert *v;
BM_ITER_MESH_INDEX (v, &iter, bm, BM_VERTS_OF_MESH, i) {
if (vtable) {
vtable[i] = v;
}
if (vtable_coords) {
copy_v3_v3(vtable_coords[i], v->co);
}
/* Evil! store original index in normal,
* this is duplicated into every other vertex.
* So we can read the original from the final.
*
* The normals must be recalculated anyway. */
*(reinterpret_cast<int *>(&v->no[0])) = i;
}
}
BMO_slot_copy(op, slots_in, "geom", op, slots_out, "geom_last.out");
for (int a = 0; a < steps; a++) {
/* Calculate rotation matrix for this step independently to avoid floating-point error
* accumulation. */
float rmat[3][3];
{
const float step_angle = angle_total * (float(a + 1) / float(steps));
axis_angle_normalized_to_mat3(rmat, axis, step_angle);
}
if (do_dupli) {
/* For duplicate mode, duplicate from original geometry
* and rotate by total angle for this step. */
BMO_op_initf(bm, &dupop, op->flag, "duplicate geom=%s", op, "geom");
BMO_op_exec(bm, &dupop);
BMO_op_callf(bm,
op->flag,
"rotate cent=%v matrix=%m3 space=%s verts=%S",
cent,
rmat,
op,
"space",
&dupop,
"geom.out");
BMO_slot_copy(&dupop, slots_out, "geom.out", op, slots_out, "geom_last.out");
BMO_op_finish(bm, &dupop);
}
else {
BMO_op_initf(bm,
&extop,
op->flag,
"extrude_face_region "
"geom=%S "
"use_keep_orig=%b "
"use_normal_flip=%b "
"use_normal_from_adjacent=%b "
"skip_input_flip=%b",
op,
"geom_last.out",
use_merge,
use_normal_flip && (a == 0),
(a != 0),
true);
BMO_op_exec(bm, &extop);
if ((use_merge && (a == steps - 1)) == false) {
/* Reset each new vert's location to its un-rotated origin so the rotate below
* runs as a single fresh rotation from the original position
* (avoids precision loss from chained rotations, see: #148890). */
if (a != 0) {
BMOpSlot *slot_geom_out = BMO_slot_get(extop.slots_out, "geom.out");
BMElem **elem_array = reinterpret_cast<BMElem **>(slot_geom_out->data.buf);
const int elem_array_len = slot_geom_out->len;
for (int i = 0; i < elem_array_len; i++) {
if (elem_array[i]->head.htype == BM_VERT) {
BMVert *v_src = reinterpret_cast<BMVert *>(elem_array[i]);
const int index = *(reinterpret_cast<const int *>(&v_src->no[0]));
copy_v3_v3(v_src->co, vtable_coords[index]);
}
}
}
BMO_op_callf(bm,
op->flag,
"rotate cent=%v matrix=%m3 space=%s verts=%S",
cent,
rmat,
op,
"space",
&extop,
"geom.out");
BMO_slot_copy(&extop, slots_out, "geom.out", op, slots_out, "geom_last.out");
}
else {
/* Merge first/last vertices and edges (maintaining 'geom.out' state). */
BMOpSlot *slot_geom_out = BMO_slot_get(extop.slots_out, "geom.out");
BMElem **elem_array = reinterpret_cast<BMElem **>(slot_geom_out->data.buf);
int elem_array_len = slot_geom_out->len;
for (int i = 0; i < elem_array_len;) {
if (elem_array[i]->head.htype == BM_VERT) {
BMVert *v_src = reinterpret_cast<BMVert *>(elem_array[i]);
BMVert *v_dst = vtable[*(reinterpret_cast<const int *>(&v_src->no[0]))];
BM_vert_splice(bm, v_dst, v_src);
elem_array_len--;
elem_array[i] = elem_array[elem_array_len];
}
else {
i++;
}
}
for (int i = 0; i < elem_array_len;) {
if (elem_array[i]->head.htype == BM_EDGE) {
BMEdge *e_src = reinterpret_cast<BMEdge *>(elem_array[i]);
BMEdge *e_dst = BM_edge_find_double(e_src);
if (e_dst != nullptr) {
BM_edge_splice(bm, e_dst, e_src);
elem_array_len--;
elem_array[i] = elem_array[elem_array_len];
continue;
}
}
i++;
}
/* Full copies of faces may cause overlap. */
for (int i = 0; i < elem_array_len;) {
if (elem_array[i]->head.htype == BM_FACE) {
BMFace *f_src = reinterpret_cast<BMFace *>(elem_array[i]);
BMFace *f_dst = BM_face_find_double(f_src);
if (f_dst != nullptr) {
BM_face_kill(bm, f_src);
elem_array_len--;
elem_array[i] = elem_array[elem_array_len];
continue;
}
}
i++;
}
slot_geom_out->len = elem_array_len;
}
BMO_op_finish(bm, &extop);
}
if (use_dvec) {
float dvec_step[3];
mul_v3_m3v3(dvec_step, rmat, dvec);
mul_v3_fl(dvec_step, float(a + 1));
BMO_op_callf(bm,
op->flag,
"translate vec=%v space=%s verts=%S",
dvec_step,
op,
"space",
op,
"geom_last.out");
}
}
if (vtable) {
MEM_delete(vtable);
}
if (vtable_coords) {
MEM_delete(vtable_coords);
}
}
} // namespace blender

View File

@@ -0,0 +1,239 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Edge-Net for filling in open edge-loops.
*/
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_MARK 1
#define EDGE_VIS 2
#define ELE_NEW 1
void bmo_edgenet_fill_exec(BMesh *bm, BMOperator *op)
{
BMOperator op_attr;
BMOIter siter;
BMFace *f;
const short mat_nr = BMO_slot_int_get(op->slots_in, "mat_nr");
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
// const int sides = BMO_slot_int_get(op->slots_in, "sides");
if (!bm->totvert || !bm->totedge) {
return;
}
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "edges", BM_EDGE, BM_ELEM_TAG, false);
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
BM_mesh_edgenet(bm, true, true); /* TODO: sides. */
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
BMO_ITER (f, &siter, op->slots_out, "faces.out", BM_FACE) {
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
/* Normals are zeroed. */
BM_face_normal_update(f);
}
/* --- Attribute Fill --- */
/* may as well since we have the faces already in a buffer */
BMO_op_initf(bm,
&op_attr,
op->flag,
"face_attribute_fill faces=%S use_normals=%b use_data=%b",
op,
"faces.out",
true,
true);
BMO_op_exec(bm, &op_attr);
/* check if some faces couldn't be touched */
if (BMO_slot_buffer_len(op_attr.slots_out, "faces_fail.out")) {
BMO_op_callf(bm, op->flag, "recalc_face_normals faces=%S", &op_attr, "faces_fail.out");
}
BMO_op_finish(bm, &op_attr);
}
static BMEdge *edge_next(BMesh *bm, BMEdge *e)
{
BMIter iter;
BMEdge *e2;
int i;
for (i = 0; i < 2; i++) {
BM_ITER_ELEM (e2, &iter, i ? e->v2 : e->v1, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e2, EDGE_MARK) &&
(BMO_edge_flag_test(bm, e2, EDGE_VIS) == false) && (e2 != e))
{
return e2;
}
}
}
return nullptr;
}
void bmo_edgenet_prepare_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMEdge *e;
bool ok = true;
int i, count;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
/* validate that each edge has at most one other tagged edge in the
* disk cycle around each of its vertices */
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
for (i = 0; i < 2; i++) {
count = BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, (i ? e->v2 : e->v1), EDGE_MARK, true);
if (count > 2) {
ok = false;
break;
}
}
if (!ok) {
break;
}
}
/* we don't have valid edge layouts, return */
if (!ok) {
return;
}
Vector<BMEdge *> edges1;
Vector<BMEdge *> edges2;
Vector<BMEdge *> *edges;
/* find connected loops within the input edge */
count = 0;
while (true) {
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
if (!BMO_edge_flag_test(bm, e, EDGE_VIS)) {
if (BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, e->v1, EDGE_MARK, true) == 1 ||
BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, e->v2, EDGE_MARK, true) == 1)
{
break;
}
}
}
if (!e) {
break;
}
if (!count) {
edges = &edges1;
}
else if (count == 1) {
edges = &edges2;
}
else {
break;
}
i = 0;
while (e) {
BMO_edge_flag_enable(bm, e, EDGE_VIS);
edges->append(e);
e = edge_next(bm, e);
i++;
}
count++;
}
if (edges1.size() > 2 && BM_edge_share_vert_check(edges1.first(), edges1.last())) {
if (edges2.size() > 2 && BM_edge_share_vert_check(edges2.first(), edges2.last())) {
return;
}
edges1 = edges2;
edges2.clear();
}
if (edges2.size() > 2 && BM_edge_share_vert_check(edges2.first(), edges2.last())) {
edges2.clear();
}
/* two unconnected loops, connect the */
if (!edges1.is_empty() && !edges2.is_empty()) {
BMVert *v1, *v2, *v3, *v4;
float dvec1[3];
float dvec2[3];
if (edges1.size() == 1) {
v1 = edges1[0]->v1;
v2 = edges1[0]->v2;
}
else {
v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1;
i = edges1.size() - 1;
v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1;
}
if (edges2.size() == 1) {
v3 = edges2[0]->v1;
v4 = edges2[0]->v2;
}
else {
v3 = BM_vert_in_edge(edges2[1], edges2[0]->v1) ? edges2[0]->v2 : edges2[0]->v1;
i = edges2.size() - 1;
v4 = BM_vert_in_edge(edges2[i - 1], edges2[i]->v1) ? edges2[i]->v2 : edges2[i]->v1;
}
/* Avoid bow tie quads using most planar the triangle pair, see: #30367 & #143905. */
normal_tri_v3(dvec1, v1->co, v2->co, v4->co);
normal_tri_v3(dvec2, v1->co, v4->co, v3->co);
const float dot_24 = dot_v3v3(dvec1, dvec2);
normal_tri_v3(dvec1, v1->co, v2->co, v3->co);
normal_tri_v3(dvec2, v1->co, v3->co, v4->co);
const float dot_13 = dot_v3v3(dvec1, dvec2);
if (dot_24 < dot_13) {
std::swap(v3, v4);
}
e = BM_edge_create(bm, v1, v3, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
e = BM_edge_create(bm, v2, v4, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
}
else if (!edges1.is_empty()) {
BMVert *v1, *v2;
if (edges1.size() > 1) {
v1 = BM_vert_in_edge(edges1[1], edges1[0]->v1) ? edges1[0]->v2 : edges1[0]->v1;
i = edges1.size() - 1;
v2 = BM_vert_in_edge(edges1[i - 1], edges1[i]->v1) ? edges1[i]->v2 : edges1[i]->v1;
e = BM_edge_create(bm, v1, v2, nullptr, BM_CREATE_NO_DOUBLE);
BMO_edge_flag_enable(bm, e, ELE_NEW);
}
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,866 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Extrude faces and solidify.
*/
#include "MEM_guardedalloc.h"
#include "DNA_meshdata_types.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BLI_vector.hh"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define USE_EDGE_REGION_FLAGS
enum {
EXT_INPUT = 1,
EXT_KEEP = 2,
EXT_DEL = 4,
EXT_TAG = 8,
};
#define VERT_MARK 1
#define EDGE_MARK 1
#define FACE_MARK 1
#define VERT_NONMAN 2
#define EDGE_NONMAN 2
void bmo_extrude_discrete_faces_exec(BMesh *bm, BMOperator *op)
{
const bool use_select_history = BMO_slot_bool_get(op->slots_in, "use_select_history");
GHash *select_history_map = nullptr;
BMOIter siter;
BMFace *f_org;
if (use_select_history) {
select_history_map = BM_select_history_map_create(bm);
}
BMO_ITER (f_org, &siter, op->slots_in, "faces", BM_FACE) {
BMFace *f_new;
BMLoop *l_org, *l_org_first;
BMLoop *l_new;
BMO_face_flag_enable(bm, f_org, EXT_DEL);
f_new = BM_face_copy(bm, f_org, true, true);
BMO_face_flag_enable(bm, f_new, EXT_KEEP);
if (select_history_map) {
BMEditSelection *ese;
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, f_org));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(f_new);
}
}
l_org = l_org_first = BM_FACE_FIRST_LOOP(f_org);
l_new = BM_FACE_FIRST_LOOP(f_new);
do {
BMFace *f_side;
BMLoop *l_side_iter;
BM_elem_attrs_copy(bm, l_org, l_new);
f_side = BM_face_create_quad_tri(
bm, l_org->next->v, l_new->next->v, l_new->v, l_org->v, f_org, BM_CREATE_NOP);
l_side_iter = BM_FACE_FIRST_LOOP(f_side);
BM_elem_attrs_copy(bm, l_org->next, l_side_iter);
l_side_iter = l_side_iter->next;
BM_elem_attrs_copy(bm, l_org->next, l_side_iter);
l_side_iter = l_side_iter->next;
BM_elem_attrs_copy(bm, l_org, l_side_iter);
l_side_iter = l_side_iter->next;
BM_elem_attrs_copy(bm, l_org, l_side_iter);
if (select_history_map) {
BMEditSelection *ese;
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, l_org->v));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(l_new->v);
}
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, l_org->e));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(l_new->e);
}
}
} while (((void)(l_new = l_new->next), (l_org = l_org->next)) != l_org_first);
}
if (select_history_map) {
BLI_ghash_free(select_history_map, nullptr, nullptr);
}
BMO_op_callf(bm, op->flag, "delete geom=%ff context=%i", EXT_DEL, DEL_ONLYFACES);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, EXT_KEEP);
}
/**
* \brief Copy the loop pair from an adjacent face to both sides of this quad.
*
* The face is assumed to be a quad, created by extruding.
* This function won't crash if its not but won't work right either.
* \a e_b is the new edge.
*
* \note The edge this face comes from needs to be from the first and second verts to the face.
* The caller must ensure this else we will copy from the wrong source.
*/
static void bm_extrude_copy_face_loop_attributes(BMesh *bm, BMFace *f)
{
/* edge we are extruded from */
BMLoop *l_first_0 = BM_FACE_FIRST_LOOP(f);
BMLoop *l_first_1 = l_first_0->next;
BMLoop *l_first_2 = l_first_1->next;
BMLoop *l_first_3 = l_first_2->next;
BMLoop *l_other_0;
BMLoop *l_other_1;
if (UNLIKELY(l_first_0 == l_first_0->radial_next)) {
return;
}
l_other_0 = BM_edge_other_loop(l_first_0->e, l_first_0);
l_other_1 = BM_edge_other_loop(l_first_0->e, l_first_1);
/* copy data */
BM_elem_attrs_copy(bm, l_other_0->f, f);
BM_elem_flag_disable(f, BM_ELEM_HIDDEN); /* possibly we copy from a hidden face */
BM_elem_attrs_copy(bm, l_other_0, l_first_0);
BM_elem_attrs_copy(bm, l_other_0, l_first_3);
BM_elem_attrs_copy(bm, l_other_1, l_first_1);
BM_elem_attrs_copy(bm, l_other_1, l_first_2);
}
/* Disable the skin root flag on the input vert, assumes that the vert
* data includes an CD_MVERT_SKIN layer */
static void bm_extrude_disable_skin_root(BMesh *bm, BMVert *v)
{
MVertSkin *vs;
vs = static_cast<MVertSkin *>(CustomData_bmesh_get(&bm->vdata, v->head.data, CD_MVERT_SKIN));
vs->flag &= ~MVERT_SKIN_ROOT;
}
void bmo_extrude_edge_only_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMOperator dupeop;
BMFace *f;
BMEdge *e, *e_new;
const bool use_normal_flip = BMO_slot_bool_get(op->slots_in, "use_normal_flip");
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
BMO_edge_flag_enable(bm, e, EXT_INPUT);
BMO_vert_flag_enable(bm, e->v1, EXT_INPUT);
BMO_vert_flag_enable(bm, e->v2, EXT_INPUT);
}
BMO_op_initf(bm,
&dupeop,
op->flag,
"duplicate geom=%fve use_select_history=%b",
EXT_INPUT,
BMO_slot_bool_get(op->slots_in, "use_select_history"));
BMO_op_exec(bm, &dupeop);
/* disable root flag on all new skin nodes */
if (CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) {
BMVert *v;
BMO_ITER (v, &siter, dupeop.slots_out, "geom.out", BM_VERT) {
bm_extrude_disable_skin_root(bm, v);
}
}
for (e = static_cast<BMEdge *>(BMO_iter_new(&siter, dupeop.slots_out, "boundary_map.out", 0)); e;
e = static_cast<BMEdge *>(BMO_iter_step(&siter)))
{
BMVert *f_verts[4];
e_new = static_cast<BMEdge *>(BMO_iter_map_value_ptr(&siter));
const bool edge_normal_flip = !(e->l && e->v1 != e->l->v);
if (edge_normal_flip == use_normal_flip) {
f_verts[0] = e->v1;
f_verts[1] = e->v2;
f_verts[2] = e_new->v2;
f_verts[3] = e_new->v1;
}
else {
f_verts[0] = e->v2;
f_verts[1] = e->v1;
f_verts[2] = e_new->v1;
f_verts[3] = e_new->v2;
}
/* not sure what to do about example face, pass nullptr for now */
f = BM_face_create_verts(bm, f_verts, 4, nullptr, BM_CREATE_NOP, true);
bm_extrude_copy_face_loop_attributes(bm, f);
if (BMO_edge_flag_test(bm, e, EXT_INPUT)) {
e = e_new;
}
BMO_face_flag_enable(bm, f, EXT_KEEP);
BMO_edge_flag_enable(bm, e, EXT_KEEP);
BMO_vert_flag_enable(bm, e->v1, EXT_KEEP);
BMO_vert_flag_enable(bm, e->v2, EXT_KEEP);
}
BMO_op_finish(bm, &dupeop);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, EXT_KEEP);
}
void bmo_extrude_vert_indiv_exec(BMesh *bm, BMOperator *op)
{
const bool use_select_history = BMO_slot_bool_get(op->slots_in, "use_select_history");
BMOIter siter;
BMVert *v, *dupev;
BMEdge *e;
const bool has_vskin = CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN);
GHash *select_history_map = nullptr;
if (use_select_history) {
select_history_map = BM_select_history_map_create(bm);
}
for (v = static_cast<BMVert *>(BMO_iter_new(&siter, op->slots_in, "verts", BM_VERT)); v;
v = static_cast<BMVert *>(BMO_iter_step(&siter)))
{
dupev = BM_vert_create(bm, v->co, v, BM_CREATE_NOP);
BMO_vert_flag_enable(bm, dupev, EXT_KEEP);
if (has_vskin) {
bm_extrude_disable_skin_root(bm, v);
}
if (select_history_map) {
BMEditSelection *ese;
ese = static_cast<BMEditSelection *>(BLI_ghash_lookup(select_history_map, v));
if (ese) {
ese->ele = reinterpret_cast<BMElem *>(dupev);
}
}
/* not essential, but ensures face normals from extruded edges are contiguous */
if (BM_vert_is_wire_endpoint(v)) {
if (v->e->v1 == v) {
std::swap(v, dupev);
}
}
e = BM_edge_create(bm, v, dupev, nullptr, BM_CREATE_NOP);
BMO_edge_flag_enable(bm, e, EXT_KEEP);
}
if (select_history_map) {
BLI_ghash_free(select_history_map, nullptr, nullptr);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "verts.out", BM_VERT, EXT_KEEP);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EXT_KEEP);
}
#ifdef USE_EDGE_REGION_FLAGS
/**
* When create an edge for an extruded face region
* check surrounding edge flags before creating a new edge.
*/
static bool bm_extrude_region_edge_flag(const BMVert *v, char r_e_hflag[2])
{
BMEdge *e_iter;
const char hflag_enable = BM_ELEM_SEAM;
const char hflag_disable = BM_ELEM_SMOOTH;
bool ok = false;
r_e_hflag[0] = 0x0;
r_e_hflag[1] = 0xff;
/* clear flags on both disks */
e_iter = v->e;
do {
if (e_iter->l && !BM_edge_is_boundary(e_iter)) {
r_e_hflag[0] |= e_iter->head.hflag;
r_e_hflag[1] &= e_iter->head.hflag;
ok = true;
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v)) != v->e);
if (ok) {
r_e_hflag[0] &= hflag_enable;
r_e_hflag[1] = hflag_disable & ~r_e_hflag[1];
}
return ok;
}
#endif /* USE_EDGE_REGION_FLAGS */
void bmo_extrude_face_region_exec(BMesh *bm, BMOperator *op)
{
BMOperator dupeop, delop;
BMOIter siter;
BMIter iter, fiter, viter;
BMEdge *e, *e_new;
BMVert *v;
BMFace *f;
bool found, delorig = false;
BMOpSlot *slot_facemap_out;
BMOpSlot *slot_edges_exclude;
const bool use_normal_flip = BMO_slot_bool_get(op->slots_in, "use_normal_flip");
const bool use_normal_from_adjacent = BMO_slot_bool_get(op->slots_in,
"use_normal_from_adjacent");
const bool use_dissolve_ortho_edges = BMO_slot_bool_get(op->slots_in,
"use_dissolve_ortho_edges");
/* initialize our sub-operators */
BMO_op_initf(bm,
&dupeop,
op->flag,
"duplicate use_select_history=%b",
BMO_slot_bool_get(op->slots_in, "use_select_history"));
BMO_slot_buffer_flag_enable(bm, op->slots_in, "geom", BM_EDGE | BM_FACE, EXT_INPUT);
/* if one flagged face is bordered by an un-flagged face, then we delete
* original geometry unless caller explicitly asked to keep it. */
if (!BMO_slot_bool_get(op->slots_in, "use_keep_orig")) {
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
int edge_face_tot;
if (!BMO_edge_flag_test(bm, e, EXT_INPUT)) {
continue;
}
found = false; /* found a face that isn't input? */
edge_face_tot = 0; /* edge/face count */
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f, EXT_INPUT)) {
found = true;
delorig = true;
break;
}
edge_face_tot++;
}
if ((edge_face_tot > 1) && (found == false)) {
/* edge has a face user, that face isn't extrude input */
BMO_edge_flag_enable(bm, e, EXT_DEL);
}
}
}
/* calculate verts to delete */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
if (v->e) { /* only deal with verts attached to geometry #33651. */
found = false;
BM_ITER_ELEM (e, &viter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, EXT_INPUT) || !BMO_edge_flag_test(bm, e, EXT_DEL)) {
found = true;
break;
}
}
/* avoid an extra loop */
if (found == false) {
BM_ITER_ELEM (f, &viter, v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f, EXT_INPUT)) {
found = true;
break;
}
}
}
if (found == false) {
BMO_vert_flag_enable(bm, v, EXT_DEL);
}
}
}
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, EXT_INPUT)) {
BMO_face_flag_enable(bm, f, EXT_DEL);
}
}
if (delorig == true) {
BMO_op_initf(bm, &delop, op->flag, "delete geom=%fvef context=%i", EXT_DEL, DEL_ONLYTAGGED);
}
BMO_slot_copy(op, slots_in, "geom", &dupeop, slots_in, "geom");
BMO_op_exec(bm, &dupeop);
/* disable root flag on all new skin nodes */
if (CustomData_has_layer(&bm->vdata, CD_MVERT_SKIN)) {
BMO_ITER (v, &siter, dupeop.slots_out, "geom.out", BM_VERT) {
bm_extrude_disable_skin_root(bm, v);
}
}
slot_facemap_out = BMO_slot_get(dupeop.slots_out, "face_map.out");
if (bm->act_face && BMO_face_flag_test(bm, bm->act_face, EXT_INPUT)) {
bm->act_face = static_cast<BMFace *>(BMO_slot_map_elem_get(slot_facemap_out, bm->act_face));
}
if (delorig) {
BMO_op_exec(bm, &delop);
}
const bool skip_input_flip = BMO_slot_bool_get(op->slots_in, "skip_input_flip");
/* Flip input faces only when originals are kept (!delorig)
* and the caller didn't request to skip flipping (!skip_input_flip).*/
if (!delorig && !skip_input_flip) {
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, EXT_INPUT)) {
BM_face_normal_flip(bm, f);
}
}
}
BMVert **dissolve_verts = nullptr;
int dissolve_verts_len = 0;
float average_normal[3];
if (use_dissolve_ortho_edges) {
/* Calc average normal. */
zero_v3(average_normal);
BMO_ITER (f, &siter, dupeop.slots_out, "geom.out", BM_FACE) {
add_v3_v3(average_normal, f->no);
}
if (normalize_v3(average_normal) == 0.0f) {
average_normal[2] = 1.0f;
}
/* Allocate array to store possible vertices that will be dissolved. */
int boundary_edges_len = BMO_slot_map_len(dupeop.slots_out, "boundary_map.out");
/* We do not know the real number of boundary vertices. */
int boundary_verts_len_maybe = 2 * boundary_edges_len;
dissolve_verts = MEM_new_array_uninitialized<BMVert *>(boundary_verts_len_maybe, __func__);
}
BMO_slot_copy(&dupeop, slots_out, "geom.out", op, slots_out, "geom.out");
slot_edges_exclude = BMO_slot_get(op->slots_in, "edges_exclude");
for (e = static_cast<BMEdge *>(BMO_iter_new(&siter, dupeop.slots_out, "boundary_map.out", 0)); e;
e = static_cast<BMEdge *>(BMO_iter_step(&siter)))
{
BMVert *f_verts[4];
#ifdef USE_EDGE_REGION_FLAGS
BMEdge *f_edges[4];
#endif
/* this should always be wire, so this is mainly a speedup to avoid map lookup */
if (BM_edge_is_wire(e) && BMO_slot_map_contains(slot_edges_exclude, e)) {
BMVert *v1 = e->v1, *v2 = e->v2;
/* The original edge was excluded,
* this would result in a standalone wire edge - see #30399. */
BM_edge_kill(bm, e);
/* kill standalone vertices from this edge - see #32341. */
if (!v1->e) {
BM_vert_kill(bm, v1);
}
if (!v2->e) {
BM_vert_kill(bm, v2);
}
continue;
}
/* skip creating face for excluded edges see #35503. */
if (BMO_slot_map_contains(slot_edges_exclude, e)) {
/* simply skip creating the face */
continue;
}
e_new = static_cast<BMEdge *>(BMO_iter_map_value_ptr(&siter));
if (!e_new) {
continue;
}
BMFace *join_face = nullptr;
if (use_dissolve_ortho_edges) {
if (BM_edge_is_boundary(e)) {
join_face = e->l->f;
if (fabs(dot_v3v3(average_normal, join_face->no)) > 0.0001f) {
join_face = nullptr;
}
}
}
bool edge_normal_flip;
if (use_normal_from_adjacent == false) {
/* Orient loop to give same normal as a loop of 'e_new'
* if it exists (will be one of the faces from the region),
* else same normal as a loop of e, if it exists. */
edge_normal_flip = !(e_new->l ? (e_new->l->v == e_new->v1) : (!e->l || !(e->l->v == e->v1)));
}
else {
/* Special case, needed for repetitive extrusions
* that use the normals from the previously created faces. */
edge_normal_flip = !(e->l && e->v1 != e->l->v);
}
if (edge_normal_flip == use_normal_flip) {
f_verts[0] = e->v1;
f_verts[1] = e->v2;
f_verts[2] = e_new->v2;
f_verts[3] = e_new->v1;
}
else {
f_verts[0] = e->v2;
f_verts[1] = e->v1;
f_verts[2] = e_new->v1;
f_verts[3] = e_new->v2;
}
#ifdef USE_EDGE_REGION_FLAGS
/* handle new edges */
f_edges[0] = e;
f_edges[2] = e_new;
f_edges[1] = BM_edge_exists(f_verts[1], f_verts[2]);
if (f_edges[1] == nullptr) {
char e_hflag[2];
bool e_hflag_ok = bm_extrude_region_edge_flag(f_verts[2], e_hflag);
f_edges[1] = BM_edge_create(bm, f_verts[1], f_verts[2], nullptr, BM_CREATE_NOP);
if (e_hflag_ok) {
BM_elem_flag_enable(f_edges[1], e_hflag[0]);
BM_elem_flag_disable(f_edges[1], e_hflag[1]);
}
}
f_edges[3] = BM_edge_exists(f_verts[3], f_verts[0]);
if (f_edges[3] == nullptr) {
char e_hflag[2];
bool e_hflag_ok = bm_extrude_region_edge_flag(f_verts[3], e_hflag);
f_edges[3] = BM_edge_create(bm, f_verts[3], f_verts[0], nullptr, BM_CREATE_NOP);
if (e_hflag_ok) {
BM_elem_flag_enable(f_edges[3], e_hflag[0]);
BM_elem_flag_disable(f_edges[3], e_hflag[1]);
}
}
f = BM_face_create(bm, f_verts, f_edges, 4, nullptr, BM_CREATE_NOP);
#else
f = BM_face_create_verts(bm, f_verts, 4, nullptr, BM_CREATE_NOP, true);
#endif
bm_extrude_copy_face_loop_attributes(bm, f);
if (join_face) {
BMVert *v1 = e->v1;
BMVert *v2 = e->v2;
if (!BMO_elem_flag_test(bm, v1, EXT_TAG)) {
BMO_elem_flag_enable(bm, v1, EXT_TAG);
dissolve_verts[dissolve_verts_len++] = v1;
}
if (!BMO_elem_flag_test(bm, v2, EXT_TAG)) {
BMO_elem_flag_enable(bm, v2, EXT_TAG);
dissolve_verts[dissolve_verts_len++] = v2;
}
/* Tag the edges that can collapse. */
BMO_elem_flag_enable(bm, f_edges[0], EXT_TAG);
BMO_elem_flag_enable(bm, f_edges[1], EXT_TAG);
bmesh_kernel_join_face_kill_edge(bm, join_face, f, e);
}
}
/* link isolated vert */
for (v = static_cast<BMVert *>(BMO_iter_new(&siter, dupeop.slots_out, "isovert_map.out", 0)); v;
v = static_cast<BMVert *>(BMO_iter_step(&siter)))
{
BMVert *v2 = static_cast<BMVert *>(BMO_iter_map_value_ptr(&siter));
/* not essential, but ensures face normals from extruded edges are contiguous */
if (BM_vert_is_wire_endpoint(v)) {
if (v->e->v1 == v) {
std::swap(v, v2);
}
}
BM_edge_create(bm, v, v2, nullptr, BM_CREATE_NO_DOUBLE);
}
if (dissolve_verts) {
BMVert **v_iter = &dissolve_verts[0];
for (int i = dissolve_verts_len; i--; v_iter++) {
v = *v_iter;
e = v->e;
BMEdge *e_other = BM_DISK_EDGE_NEXT(e, v);
if ((e_other == e) || (BM_DISK_EDGE_NEXT(e_other, v) == e)) {
/* Loose edge or BMVert is edge pair. */
BM_edge_collapse(bm, BMO_elem_flag_test(bm, e, EXT_TAG) ? e : e_other, v, true, true);
}
else {
BLI_assert(!BM_vert_is_edge_pair(v));
}
}
MEM_delete(dissolve_verts);
}
/* cleanup */
if (delorig) {
BMO_op_finish(bm, &delop);
}
BMO_op_finish(bm, &dupeop);
}
/*
* Compute higher-quality vertex normals used by solidify.
* Only considers geometry in the marked solidify region.
* Note that this does not work so well for non-manifold
* regions.
*/
static void calc_solidify_normals(BMesh *bm)
{
BMIter viter, eiter, fiter;
BMVert *v;
BMEdge *e;
BMFace *f, *f1, *f2;
float edge_normal[3];
int i;
/* can't use BM_edge_face_count because we need to count only marked faces */
int *edge_face_count = MEM_new_array_zeroed<int>(bm->totedge, __func__);
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
BM_elem_flag_enable(v, BM_ELEM_TAG);
}
BM_mesh_elem_index_ensure(bm, BM_EDGE);
BM_ITER_MESH (f, &fiter, bm, BM_FACES_OF_MESH) {
if (!BMO_face_flag_test(bm, f, FACE_MARK)) {
continue;
}
BM_ITER_ELEM (e, &eiter, f, BM_EDGES_OF_FACE) {
/* And mark all edges and vertices on the
* marked faces */
BMO_edge_flag_enable(bm, e, EDGE_MARK);
BMO_vert_flag_enable(bm, e->v1, VERT_MARK);
BMO_vert_flag_enable(bm, e->v2, VERT_MARK);
edge_face_count[BM_elem_index_get(e)]++;
}
}
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
continue;
}
i = edge_face_count[BM_elem_index_get(e)];
if (i == 0 || i > 2) {
/* Edge & vertices are non-manifold even when considering
* only marked faces */
BMO_edge_flag_enable(bm, e, EDGE_NONMAN);
BMO_vert_flag_enable(bm, e->v1, VERT_NONMAN);
BMO_vert_flag_enable(bm, e->v2, VERT_NONMAN);
}
}
MEM_delete(edge_face_count);
edge_face_count = nullptr; /* don't re-use */
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
if (!BM_vert_is_manifold(v)) {
BMO_vert_flag_enable(bm, v, VERT_NONMAN);
continue;
}
if (BMO_vert_flag_test(bm, v, VERT_MARK)) {
zero_v3(v->no);
}
}
BM_ITER_MESH (e, &eiter, bm, BM_EDGES_OF_MESH) {
/* If the edge is not part of the solidify region
* its normal should not be considered */
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
continue;
}
/* If the edge joins more than two marked faces high
* quality normal computation won't work */
if (BMO_edge_flag_test(bm, e, EDGE_NONMAN)) {
continue;
}
f1 = f2 = nullptr;
BM_ITER_ELEM (f, &fiter, e, BM_FACES_OF_EDGE) {
if (BMO_face_flag_test(bm, f, FACE_MARK)) {
if (f1 == nullptr) {
f1 = f;
}
else {
BLI_assert(f2 == nullptr);
f2 = f;
}
}
}
BLI_assert(f1 != nullptr);
if (f2 != nullptr) {
const float angle = angle_normalized_v3v3(f1->no, f2->no);
if (angle > 0.0f) {
/* two faces using this edge, calculate the edge normal
* using the angle between the faces as a weighting */
add_v3_v3v3(edge_normal, f1->no, f2->no);
normalize_v3_length(edge_normal, angle);
}
else {
/* can't do anything useful here!
* Set the face index for a vert in case it gets a zero normal */
BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
continue;
}
}
else {
/* only one face attached to that edge */
/* an edge without another attached- the weight on this is undefined,
* M_PI_2 is 90d in radians and that seems good enough */
copy_v3_v3(edge_normal, f1->no);
mul_v3_fl(edge_normal, M_PI_2);
}
add_v3_v3(e->v1->no, edge_normal);
add_v3_v3(e->v2->no, edge_normal);
}
/* normalize accumulated vertex normal */
BM_ITER_MESH (v, &viter, bm, BM_VERTS_OF_MESH) {
if (!BMO_vert_flag_test(bm, v, VERT_MARK)) {
continue;
}
if (BMO_vert_flag_test(bm, v, VERT_NONMAN)) {
/* use standard normals for vertices connected to non-manifold edges */
BM_vert_normal_update(v);
}
else if (normalize_v3(v->no) == 0.0f && !BM_elem_flag_test(v, BM_ELEM_TAG)) {
/* exceptional case, totally flat. use the normal
* of any marked face around the vertex */
BM_ITER_ELEM (f, &fiter, v, BM_FACES_OF_VERT) {
if (BMO_face_flag_test(bm, f, FACE_MARK)) {
break;
}
}
copy_v3_v3(v->no, f->no);
}
}
}
static void solidify_add_thickness(BMesh *bm, const float dist)
{
BMFace *f;
BMVert *v;
BMLoop *l;
BMIter iter, loopIter;
float *vert_angles = MEM_new_array_zeroed<float>(size_t(bm->totvert) * 2,
"solidify"); /* 2 in 1 */
float *vert_accum = vert_angles + bm->totvert;
int i, index;
Vector<float, BM_DEFAULT_NGON_STACK_SIZE> face_angles;
Vector<float *, BM_DEFAULT_NGON_STACK_SIZE> verts;
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BMO_face_flag_test(bm, f, FACE_MARK)) {
/* array for passing verts to angle_poly_v3 */
face_angles.resize(f->len);
/* array for receiving angles from angle_poly_v3 */
verts.resize(f->len);
BM_ITER_ELEM_INDEX (l, &loopIter, f, BM_LOOPS_OF_FACE, i) {
verts[i] = l->v->co;
}
angle_poly_v3(face_angles.data(), (const float **)verts.data(), f->len);
i = 0;
BM_ITER_ELEM (l, &loopIter, f, BM_LOOPS_OF_FACE) {
v = l->v;
index = BM_elem_index_get(v);
vert_accum[index] += face_angles[i];
vert_angles[index] += shell_v3v3_normalized_to_dist(v->no, f->no) * face_angles[i];
i++;
}
}
}
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
index = BM_elem_index_get(v);
if (vert_accum[index]) { /* zero if unselected */
madd_v3_v3fl(v->co, v->no, dist * (vert_angles[index] / vert_accum[index]));
}
}
MEM_delete(vert_angles);
}
void bmo_solidify_face_region_exec(BMesh *bm, BMOperator *op)
{
BMOperator extrudeop;
BMOperator reverseop;
float thickness;
thickness = BMO_slot_float_get(op->slots_in, "thickness");
/* Flip original faces (so the shell is extruded inward) */
BMO_op_init(bm, &reverseop, op->flag, "reverse_faces");
BMO_slot_bool_set(reverseop.slots_in, "flip_multires", true);
BMO_slot_copy(op, slots_in, "geom", &reverseop, slots_in, "faces");
BMO_op_exec(bm, &reverseop);
BMO_op_finish(bm, &reverseop);
/* Extrude the region */
BMO_op_initf(bm, &extrudeop, op->flag, "extrude_face_region use_keep_orig=%b", true);
BMO_slot_copy(op, slots_in, "geom", &extrudeop, slots_in, "geom");
BMO_op_exec(bm, &extrudeop);
/* Push the verts of the extruded faces inward to create thickness */
BMO_slot_buffer_flag_enable(bm, extrudeop.slots_out, "geom.out", BM_FACE, FACE_MARK);
calc_solidify_normals(bm);
solidify_add_thickness(bm, thickness);
BMO_slot_copy(&extrudeop, slots_out, "geom.out", op, slots_out, "geom.out");
BMO_op_finish(bm, &extrudeop);
}
} // namespace blender

View File

@@ -0,0 +1,160 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill in geometry with the attributes of their adjacent data.
*/
#include "BLI_linklist_stack.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/**
* Check if all other loops are tagged.
*/
static bool bm_loop_is_all_radial_tag(BMLoop *l)
{
BMLoop *l_iter;
l_iter = l->radial_next;
do {
if (BM_elem_flag_test(l_iter->f, BM_ELEM_TAG) == 0) {
return false;
}
} while ((l_iter = l_iter->radial_next) != l);
return true;
}
/**
* Callback to run on source-loops for #BM_face_copy_shared
*/
static bool bm_loop_is_face_untag(const BMLoop *l, void * /*user_data*/)
{
return (BM_elem_flag_test(l->f, BM_ELEM_TAG) == 0);
}
/**
* Copy all attributes from adjacent untagged faces.
*/
static void bm_face_copy_shared_all(BMesh *bm,
BMLoop *l,
const bool use_normals,
const bool use_data)
{
BMLoop *l_other = l->radial_next;
BMFace *f = l->f, *f_other;
while (BM_elem_flag_test(l_other->f, BM_ELEM_TAG)) {
l_other = l_other->radial_next;
}
f_other = l_other->f;
if (use_data) {
/* copy face-attrs */
BM_elem_attrs_copy(bm, f_other, f);
/* copy loop-attrs */
BM_face_copy_shared(bm, f, bm_loop_is_face_untag, nullptr);
}
if (use_normals) {
/* copy winding (flipping) */
if (l->v == l_other->v) {
BM_face_normal_flip(bm, f);
}
}
}
/**
* Flood fill attributes.
*/
static uint bmesh_face_attribute_fill(BMesh *bm, const bool use_normals, const bool use_data)
{
BLI_LINKSTACK_DECLARE(loop_queue_prev, BMLoop *);
BLI_LINKSTACK_DECLARE(loop_queue_next, BMLoop *);
BMFace *f;
BMIter iter;
BMLoop *l;
uint face_tot = 0;
BLI_LINKSTACK_INIT(loop_queue_prev);
BLI_LINKSTACK_INIT(loop_queue_next);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
if (BM_elem_flag_test(f, BM_ELEM_TAG)) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (bm_loop_is_all_radial_tag(l_iter) == false) {
BLI_LINKSTACK_PUSH(loop_queue_prev, l_iter);
}
} while ((l_iter = l_iter->next) != l_first);
}
}
while (BLI_LINKSTACK_SIZE(loop_queue_prev)) {
while ((l = BLI_LINKSTACK_POP(loop_queue_prev))) {
/* check we're still un-assigned */
if (BM_elem_flag_test(l->f, BM_ELEM_TAG)) {
BMLoop *l_iter;
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
l_iter = l->next;
do {
BMLoop *l_radial_iter = l_iter->radial_next;
if (l_radial_iter != l_iter) {
do {
if (BM_elem_flag_test(l_radial_iter->f, BM_ELEM_TAG)) {
BLI_LINKSTACK_PUSH(loop_queue_next, l_radial_iter);
}
} while ((l_radial_iter = l_radial_iter->radial_next) != l_iter);
}
} while ((l_iter = l_iter->next) != l);
/* do last because of face flipping */
bm_face_copy_shared_all(bm, l, use_normals, use_data);
face_tot += 1;
}
}
BLI_LINKSTACK_SWAP(loop_queue_prev, loop_queue_next);
}
BLI_LINKSTACK_FREE(loop_queue_prev);
BLI_LINKSTACK_FREE(loop_queue_next);
return face_tot;
}
void bmo_face_attribute_fill_exec(BMesh *bm, BMOperator *op)
{
const bool use_normals = BMO_slot_bool_get(op->slots_in, "use_normals");
const bool use_data = BMO_slot_bool_get(op->slots_in, "use_data");
int face_tot;
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
/* do inline */
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "faces", BM_FACE, BM_ELEM_TAG, false);
/* now we can copy adjacent data */
face_tot = bmesh_face_attribute_fill(bm, use_normals, use_data);
if (face_tot != BMO_slot_buffer_len(op->slots_in, "faces")) {
/* any remaining tags will be skipped */
BMO_slot_buffer_from_enabled_hflag(
bm, op, op->slots_out, "faces_fail.out", BM_FACE, BM_ELEM_TAG);
}
}
} // namespace blender

View File

@@ -0,0 +1,142 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill discrete edge loop(s) with faces.
*/
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define VERT_USED 1
#define EDGE_MARK 2
#define ELE_OUT 4
void bmo_edgeloop_fill_exec(BMesh *bm, BMOperator *op)
{
/* first collect an array of unique from the edges */
const int tote = BMO_slot_buffer_len(op->slots_in, "edges");
const int totv = tote; /* these should be the same */
BMVert **verts = MEM_new_array_uninitialized<BMVert *>(totv, __func__);
BMVert *v;
BMEdge *e;
int i;
bool ok = true;
BMOIter oiter;
const short mat_nr = BMO_slot_int_get(op->slots_in, "mat_nr");
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
/* 'VERT_USED' will be disabled, so enable and fill the array */
i = 0;
BMO_ITER (e, &oiter, op->slots_in, "edges", BM_EDGE) {
BMIter viter;
BMO_edge_flag_enable(bm, e, EDGE_MARK);
BM_ITER_ELEM (v, &viter, e, BM_VERTS_OF_EDGE) {
if (BMO_vert_flag_test(bm, v, VERT_USED) == false) {
if (i == tote) {
goto cleanup;
}
BMO_vert_flag_enable(bm, v, VERT_USED);
verts[i++] = v;
}
}
}
/* we have a different number of verts to edges */
if (i != tote) {
goto cleanup;
}
/* loop over connected flagged edges and fill in faces, this is made slightly more
* complicated because there may be multiple disconnected loops to fill. */
/* sanity check - that each vertex has 2 edge users */
for (i = 0; i < totv; i++) {
v = verts[i];
/* count how many flagged edges this vertex uses */
if (BMO_iter_elem_count_flag(bm, BM_EDGES_OF_VERT, v, EDGE_MARK, true) != 2) {
ok = false;
break;
}
}
if (ok) {
/* NOTE: in the case of multiple loops, this over-allocates (which is fine). */
BMVert **f_verts = MEM_new_array_uninitialized<BMVert *>(totv, __func__);
BMIter eiter;
/* build array of connected verts and edges */
BMEdge *e_prev = nullptr;
BMEdge *e_next = nullptr;
int totv_used = 0;
while (totv_used < totv) {
for (i = 0; i < totv; i++) {
v = verts[i];
if (BMO_vert_flag_test(bm, v, VERT_USED)) {
break;
}
}
/* this should never fail, as long as (totv_used < totv)
* we should have marked verts available */
BLI_assert(BMO_vert_flag_test(bm, v, VERT_USED));
/* watch it, 'i' is used for final face length */
i = 0;
do {
/* we know that there are 2 edges per vertex so no need to check */
BM_ITER_ELEM (e, &eiter, v, BM_EDGES_OF_VERT) {
if (BMO_edge_flag_test(bm, e, EDGE_MARK)) {
if (e != e_prev) {
e_next = e;
break;
}
}
}
/* fill in the array */
f_verts[i] = v;
BMO_vert_flag_disable(bm, v, VERT_USED);
totv_used++;
/* step over the edges */
v = BM_edge_other_vert(e_next, v);
e_prev = e_next;
i++;
} while (v != f_verts[0]);
if (!BM_face_exists(f_verts, i)) {
BMFace *f;
/* don't use calc_edges option because we already have the edges */
f = BM_face_create_ngon_verts(bm, f_verts, i, nullptr, BM_CREATE_NOP, true, false);
BMO_face_flag_enable(bm, f, ELE_OUT);
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
}
}
MEM_delete(f_verts);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, ELE_OUT);
}
cleanup:
MEM_delete(verts);
}
} // namespace blender

View File

@@ -0,0 +1,734 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill 2 isolated, open edge loops with a grid of quads.
*/
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#include "BLI_strict_flags.h" /* IWYU pragma: keep. Keep last. */
namespace blender {
#define EDGE_MARK 4
#define FACE_OUT 16
#define BARYCENTRIC_INTERP
#ifdef BARYCENTRIC_INTERP
/**
* 2 edge vectors to normal.
*/
static void quad_edges_to_normal(float no[3],
const float co_a1[3],
const float co_a2[3],
const float co_b1[3],
const float co_b2[3])
{
float diff_a[3];
float diff_b[3];
sub_v3_v3v3(diff_a, co_a2, co_a1);
sub_v3_v3v3(diff_b, co_b2, co_b1);
normalize_v3(diff_a);
normalize_v3(diff_b);
add_v3_v3v3(no, diff_a, diff_b);
normalize_v3(no);
}
static void quad_verts_to_barycentric_tri(float tri[3][3],
const float co_a[3],
const float co_b[3],
const float co_a_next[3],
const float co_b_next[3],
const float co_a_prev[3],
const float co_b_prev[3],
const bool is_flip)
{
float no[3];
copy_v3_v3(tri[0], co_a);
copy_v3_v3(tri[1], co_b);
quad_edges_to_normal(no, co_a, co_a_next, co_b, co_b_next);
if (co_a_prev) {
float no_t[3];
quad_edges_to_normal(no_t, co_a_prev, co_a, co_b_prev, co_b);
add_v3_v3(no, no_t);
normalize_v3(no);
}
if (is_flip) {
negate_v3(no);
}
mul_v3_fl(no, len_v3v3(tri[0], tri[1]));
mid_v3_v3v3(tri[2], tri[0], tri[1]);
add_v3_v3(tri[2], no);
}
#endif
/* -------------------------------------------------------------------- */
/** \name Handle Loop Pairs
* \{ */
/**
* Assign a loop pair from 2 verts (which _must_ share an edge)
*/
static void bm_loop_pair_from_verts(BMVert *v_a, BMVert *v_b, BMLoop *l_pair[2])
{
BMEdge *e = BM_edge_exists(v_a, v_b);
if (e->l) {
if (e->l->v == v_a) {
l_pair[0] = e->l;
l_pair[1] = e->l->next;
}
else {
l_pair[0] = e->l->next;
l_pair[1] = e->l;
}
}
else {
l_pair[0] = nullptr;
l_pair[1] = nullptr;
}
}
/**
* Copy loop pair from one side to the other if either is missing,
* this simplifies interpolation code so we only need to check if x/y are missing,
* rather than checking each loop.
*/
static void bm_loop_pair_test_copy(BMLoop *l_pair_a[2], BMLoop *l_pair_b[2])
{
/* if the first one is set, we know the second is too */
if (l_pair_a[0] && l_pair_b[0] == nullptr) {
l_pair_b[0] = l_pair_a[1];
l_pair_b[1] = l_pair_a[0];
}
else if (l_pair_b[0] && l_pair_a[0] == nullptr) {
l_pair_a[0] = l_pair_b[1];
l_pair_a[1] = l_pair_b[0];
}
}
/**
* Interpolate from boundary loops.
*
* \note These weights will be calculated multiple times per vertex.
*/
static void bm_loop_interp_from_grid_boundary_4(BMesh *bm,
BMLoop *l,
BMLoop *l_bound[4],
const float w[4])
{
const void *l_cdata[4] = {
l_bound[0]->head.data, l_bound[1]->head.data, l_bound[2]->head.data, l_bound[3]->head.data};
CustomData_bmesh_interp(&bm->ldata, l_cdata, w, 4, l->head.data);
}
static void bm_loop_interp_from_grid_boundary_2(BMesh *bm,
BMLoop *l,
BMLoop *l_bound[2],
const float t)
{
const void *l_cdata[2] = {l_bound[0]->head.data, l_bound[1]->head.data};
const float w[2] = {1.0f - t, t};
CustomData_bmesh_interp(&bm->ldata, l_cdata, w, 2, l->head.data);
}
/** \} */
/**
* Avoids calling #barycentric_weights_v2_quad often by caching weights into an array.
*/
static void barycentric_weights_v2_grid_cache(const uint xtot,
const uint ytot,
float (*weight_table)[4])
{
float x_step = 1.0f / float(xtot - 1);
float y_step = 1.0f / float(ytot - 1);
uint i = 0;
float xy_fl[2];
uint x, y;
for (y = 0; y < ytot; y++) {
xy_fl[1] = y_step * float(y);
for (x = 0; x < xtot; x++) {
xy_fl[0] = x_step * float(x);
{
const float cos[4][2] = {
{xy_fl[0], 0.0f}, {0.0f, xy_fl[1]}, {xy_fl[0], 1.0f}, {1.0f, xy_fl[1]}};
barycentric_weights_v2_quad(UNPACK4(cos), xy_fl, weight_table[i++]);
}
}
}
}
/**
* This may be useful outside the bmesh operator.
*
* \param v_grid: 2d array of verts, all boundary verts must be set, we fill in the middle.
*/
static void bm_grid_fill_array(BMesh *bm,
BMVert **v_grid,
const uint xtot,
const uint ytot,
const short mat_nr,
const bool use_smooth,
const bool use_flip,
const bool use_interp_simple)
{
const bool use_vert_interp = CustomData_has_interp(&bm->vdata);
const bool use_loop_interp = CustomData_has_interp(&bm->ldata);
uint x, y;
/* for use_loop_interp */
BMLoop *(*larr_x_a)[2], *(*larr_x_b)[2], *(*larr_y_a)[2], *(*larr_y_b)[2];
float (*weight_table)[4];
#define XY(_x, _y) ((_x) + ((_y) * (xtot)))
#ifdef BARYCENTRIC_INTERP
float tri_a[3][3];
float tri_b[3][3];
float tri_t[3][3]; /* temp */
quad_verts_to_barycentric_tri(tri_a,
v_grid[XY(0, 0)]->co,
v_grid[XY(xtot - 1, 0)]->co,
v_grid[XY(0, 1)]->co,
v_grid[XY(xtot - 1, 1)]->co,
nullptr,
nullptr,
false);
quad_verts_to_barycentric_tri(tri_b,
v_grid[XY(0, (ytot - 1))]->co,
v_grid[XY(xtot - 1, (ytot - 1))]->co,
v_grid[XY(0, (ytot - 2))]->co,
v_grid[XY(xtot - 1, (ytot - 2))]->co,
nullptr,
nullptr,
true);
#endif
if (use_interp_simple || use_vert_interp || use_loop_interp) {
weight_table = MEM_new_array_uninitialized<float[4]>(xtot * ytot, __func__);
barycentric_weights_v2_grid_cache(xtot, ytot, weight_table);
}
else {
weight_table = nullptr;
}
/* Store loops */
if (use_loop_interp) {
/* x2 because each edge connects 2 loops */
larr_x_a = MEM_new_array_uninitialized<BMLoop *[2]>((xtot - 1), __func__);
larr_x_b = MEM_new_array_uninitialized<BMLoop *[2]>((xtot - 1), __func__);
larr_y_a = MEM_new_array_uninitialized<BMLoop *[2]>((ytot - 1), __func__);
larr_y_b = MEM_new_array_uninitialized<BMLoop *[2]>((ytot - 1), __func__);
/* fill in the loops */
for (x = 0; x < xtot - 1; x++) {
bm_loop_pair_from_verts(v_grid[XY(x, 0)], v_grid[XY(x + 1, 0)], larr_x_a[x]);
bm_loop_pair_from_verts(v_grid[XY(x, ytot - 1)], v_grid[XY(x + 1, ytot - 1)], larr_x_b[x]);
bm_loop_pair_test_copy(larr_x_a[x], larr_x_b[x]);
}
for (y = 0; y < ytot - 1; y++) {
bm_loop_pair_from_verts(v_grid[XY(0, y)], v_grid[XY(0, y + 1)], larr_y_a[y]);
bm_loop_pair_from_verts(v_grid[XY(xtot - 1, y)], v_grid[XY(xtot - 1, y + 1)], larr_y_b[y]);
bm_loop_pair_test_copy(larr_y_a[y], larr_y_b[y]);
}
}
/* Build Verts */
for (y = 1; y < ytot - 1; y++) {
#ifdef BARYCENTRIC_INTERP
quad_verts_to_barycentric_tri(tri_t,
v_grid[XY(0, y + 0)]->co,
v_grid[XY(xtot - 1, y + 0)]->co,
v_grid[XY(0, y + 1)]->co,
v_grid[XY(xtot - 1, y + 1)]->co,
v_grid[XY(0, y - 1)]->co,
v_grid[XY(xtot - 1, y - 1)]->co,
false);
#endif
for (x = 1; x < xtot - 1; x++) {
float co[3];
BMVert *v;
/* we may want to allow sparse filled arrays, but for now, ensure its empty */
BLI_assert(v_grid[(y * xtot) + x] == nullptr);
/* place the vertex */
#ifdef BARYCENTRIC_INTERP
if (use_interp_simple == false) {
float co_a[3], co_b[3];
transform_point_by_tri_v3(
co_a, v_grid[x]->co, tri_t[0], tri_t[1], tri_t[2], tri_a[0], tri_a[1], tri_a[2]);
transform_point_by_tri_v3(co_b,
v_grid[(xtot * ytot) + (x - xtot)]->co,
tri_t[0],
tri_t[1],
tri_t[2],
tri_b[0],
tri_b[1],
tri_b[2]);
interp_v3_v3v3(co, co_a, co_b, float(y) / (float(ytot) - 1));
}
else
#endif
{
const float *w = weight_table[XY(x, y)];
zero_v3(co);
madd_v3_v3fl(co, v_grid[XY(x, 0)]->co, w[0]);
madd_v3_v3fl(co, v_grid[XY(0, y)]->co, w[1]);
madd_v3_v3fl(co, v_grid[XY(x, ytot - 1)]->co, w[2]);
madd_v3_v3fl(co, v_grid[XY(xtot - 1, y)]->co, w[3]);
}
v = BM_vert_create(bm, co, nullptr, BM_CREATE_NOP);
v_grid[(y * xtot) + x] = v;
/* Interpolate only along one axis, this could be changed
* but from user POV gives predictable results since these are selected loop. */
if (use_vert_interp) {
const float *w = weight_table[XY(x, y)];
const void *v_cdata[4] = {
v_grid[XY(x, 0)]->head.data,
v_grid[XY(0, y)]->head.data,
v_grid[XY(x, ytot - 1)]->head.data,
v_grid[XY(xtot - 1, y)]->head.data,
};
CustomData_bmesh_interp(&bm->vdata, v_cdata, w, 4, v->head.data);
}
}
}
/* Build Faces */
for (x = 0; x < xtot - 1; x++) {
for (y = 0; y < ytot - 1; y++) {
BMFace *f;
if (use_flip) {
f = BM_face_create_quad_tri(bm,
v_grid[XY(x, y + 0)], /* BL */
v_grid[XY(x, y + 1)], /* TL */
v_grid[XY(x + 1, y + 1)], /* TR */
v_grid[XY(x + 1, y + 0)], /* BR */
nullptr,
BM_CREATE_NOP);
}
else {
f = BM_face_create_quad_tri(bm,
v_grid[XY(x + 1, y + 0)], /* BR */
v_grid[XY(x + 1, y + 1)], /* TR */
v_grid[XY(x, y + 1)], /* TL */
v_grid[XY(x, y + 0)], /* BL */
nullptr,
BM_CREATE_NOP);
}
if (use_loop_interp && (larr_x_a[x][0] || larr_y_a[y][0])) {
/* bottom/left/top/right */
BMLoop *l_quad[4];
BMLoop *l_bound[4];
BMLoop *l_tmp;
uint x_side, y_side, i;
char interp_from;
if (larr_x_a[x][0] && larr_y_a[y][0]) {
interp_from = 'B'; /* B == both */
l_tmp = larr_x_a[x][0];
}
else if (larr_x_a[x][0]) {
interp_from = 'X';
l_tmp = larr_x_a[x][0];
}
else {
interp_from = 'Y';
l_tmp = larr_y_a[y][0];
}
BM_elem_attrs_copy(bm, l_tmp->f, f);
BM_face_as_array_loop_quad(f, l_quad);
l_tmp = BM_FACE_FIRST_LOOP(f);
if (use_flip) {
l_quad[0] = l_tmp;
l_tmp = l_tmp->next;
l_quad[1] = l_tmp;
l_tmp = l_tmp->next;
l_quad[3] = l_tmp;
l_tmp = l_tmp->next;
l_quad[2] = l_tmp;
}
else {
l_quad[2] = l_tmp;
l_tmp = l_tmp->next;
l_quad[3] = l_tmp;
l_tmp = l_tmp->next;
l_quad[1] = l_tmp;
l_tmp = l_tmp->next;
l_quad[0] = l_tmp;
}
i = 0;
for (x_side = 0; x_side < 2; x_side++) {
for (y_side = 0; y_side < 2; y_side++) {
if (interp_from == 'B') {
const float *w = weight_table[XY(x + x_side, y + y_side)];
l_bound[0] = larr_x_a[x][x_side]; /* B */
l_bound[1] = larr_y_a[y][y_side]; /* L */
l_bound[2] = larr_x_b[x][x_side]; /* T */
l_bound[3] = larr_y_b[y][y_side]; /* R */
bm_loop_interp_from_grid_boundary_4(bm, l_quad[i++], l_bound, w);
}
else if (interp_from == 'X') {
const float t = float(y + y_side) / float(ytot - 1);
l_bound[0] = larr_x_a[x][x_side]; /* B */
l_bound[1] = larr_x_b[x][x_side]; /* T */
bm_loop_interp_from_grid_boundary_2(bm, l_quad[i++], l_bound, t);
}
else if (interp_from == 'Y') {
const float t = float(x + x_side) / float(xtot - 1);
l_bound[0] = larr_y_a[y][y_side]; /* L */
l_bound[1] = larr_y_b[y][y_side]; /* R */
bm_loop_interp_from_grid_boundary_2(bm, l_quad[i++], l_bound, t);
}
else {
BLI_assert(0);
}
}
}
}
/* end interp */
BMO_face_flag_enable(bm, f, FACE_OUT);
f->mat_nr = mat_nr;
if (use_smooth) {
BM_elem_flag_enable(f, BM_ELEM_SMOOTH);
}
}
}
if (use_loop_interp) {
MEM_delete(larr_x_a);
MEM_delete(larr_y_a);
MEM_delete(larr_x_b);
MEM_delete(larr_y_b);
}
if (weight_table) {
MEM_delete(weight_table);
}
#undef XY
}
static void bm_grid_fill(BMesh *bm,
BMEdgeLoopStore *estore_a,
BMEdgeLoopStore *estore_b,
BMEdgeLoopStore *estore_rail_a,
BMEdgeLoopStore *estore_rail_b,
const short mat_nr,
const bool use_smooth,
const bool use_interp_simple)
{
#define USE_FLIP_DETECT
const uint xtot = uint(BM_edgeloop_length_get(estore_a));
const uint ytot = uint(BM_edgeloop_length_get(estore_rail_a));
// BMVert *v;
uint i;
#ifndef NDEBUG
uint x, y;
#endif
LinkData *el;
bool use_flip = false;
ListBaseT<LinkData> *lb_a = BM_edgeloop_verts_get(estore_a);
ListBaseT<LinkData> *lb_b = BM_edgeloop_verts_get(estore_b);
ListBaseT<LinkData> *lb_rail_a = BM_edgeloop_verts_get(estore_rail_a);
ListBaseT<LinkData> *lb_rail_b = BM_edgeloop_verts_get(estore_rail_b);
BMVert **v_grid = MEM_new_array_zeroed<BMVert *>(size_t(xtot * ytot), __func__);
/**
* <pre>
* estore_b
* +------------------+
* ^ | |
* end | | |
* | | |
* | |estore_rail_a |estore_rail_b
* | | |
* start | | |
* |estore_a |
* +------------------+
* --->
* start -> end
* </pre>
*/
BLI_assert(((LinkData *)lb_a->first)->data == ((LinkData *)lb_rail_a->first)->data); /* BL */
BLI_assert(((LinkData *)lb_b->first)->data == ((LinkData *)lb_rail_a->last)->data); /* TL */
BLI_assert(((LinkData *)lb_b->last)->data == ((LinkData *)lb_rail_b->last)->data); /* TR */
BLI_assert(((LinkData *)lb_a->last)->data == ((LinkData *)lb_rail_b->first)->data); /* BR */
for (el = static_cast<LinkData *>(lb_a->first), i = 0; el; el = el->next, i++) {
v_grid[i] = static_cast<BMVert *>(el->data);
}
for (el = static_cast<LinkData *>(lb_b->first), i = 0; el; el = el->next, i++) {
v_grid[(ytot * xtot) + (i - xtot)] = static_cast<BMVert *>(el->data);
}
for (el = static_cast<LinkData *>(lb_rail_a->first), i = 0; el; el = el->next, i++) {
v_grid[xtot * i] = static_cast<BMVert *>(el->data);
}
for (el = static_cast<LinkData *>(lb_rail_b->first), i = 0; el; el = el->next, i++) {
v_grid[(xtot * i) + (xtot - 1)] = static_cast<BMVert *>(el->data);
}
#ifndef NDEBUG
for (x = 1; x < xtot - 1; x++) {
for (y = 1; y < ytot - 1; y++) {
BLI_assert(v_grid[(y * xtot) + x] == nullptr);
}
}
#endif
#ifdef USE_FLIP_DETECT
{
ListBaseT<LinkData> *lb_iter[4] = {lb_a, lb_b, lb_rail_a, lb_rail_b};
const int lb_iter_dir[4] = {-1, 1, 1, -1};
int winding_votes = 0;
for (i = 0; i < 4; i++) {
LinkData *el_next;
for (el = static_cast<LinkData *>(lb_iter[i]->first); el && (el_next = el->next);
el = el->next)
{
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(el->data),
static_cast<BMVert *>(el_next->data));
if (BM_edge_is_boundary(e)) {
winding_votes += (e->l->v == el->data) ? lb_iter_dir[i] : -lb_iter_dir[i];
}
}
}
use_flip = (winding_votes < 0);
}
#endif
bm_grid_fill_array(bm, v_grid, xtot, ytot, mat_nr, use_smooth, use_flip, use_interp_simple);
MEM_delete(v_grid);
#undef USE_FLIP_DETECT
}
static void bm_edgeloop_flag_set(BMEdgeLoopStore *estore, char hflag, bool set)
{
/* only handle closed loops in this case */
LinkData *link = static_cast<LinkData *>(BM_edgeloop_verts_get(estore)->first);
link = link->next;
while (link) {
BMEdge *e = BM_edge_exists(static_cast<BMVert *>(link->data),
static_cast<BMVert *>(link->prev->data));
if (e) {
BM_elem_flag_set(e, hflag, set);
}
link = link->next;
}
}
static bool bm_edge_test_cb(BMEdge *e, void *bm_v)
{
return BMO_edge_flag_test_bool((BMesh *)bm_v, e, EDGE_MARK);
}
static bool bm_edge_test_rail_cb(BMEdge *e, void * /*bm_v*/)
{
/* Normally operators don't check for hidden state
* but alternative would be to pass slot of rail edges. */
if (BM_elem_flag_test(e, BM_ELEM_HIDDEN)) {
return false;
}
return BM_edge_is_wire(e) || BM_edge_is_boundary(e);
}
void bmo_grid_fill_exec(BMesh *bm, BMOperator *op)
{
ListBaseT<BMEdgeLoopStore> eloops = {nullptr, nullptr};
ListBaseT<BMEdgeLoopStore> eloops_rail = {nullptr, nullptr};
BMEdgeLoopStore *estore_a, *estore_b;
BMEdgeLoopStore *estore_rail_a, *estore_rail_b;
BMVert *v_a_first, *v_a_last;
BMVert *v_b_first, *v_b_last;
const short mat_nr = short(BMO_slot_int_get(op->slots_in, "mat_nr"));
const bool use_smooth = BMO_slot_bool_get(op->slots_in, "use_smooth");
const bool use_interp_simple = BMO_slot_bool_get(op->slots_in, "use_interp_simple");
std::unique_ptr<Set<BMEdge *>> split_edges;
int count;
bool changed = false;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
count = BM_mesh_edgeloops_find(bm, &eloops, bm_edge_test_cb, static_cast<void *>(bm));
if (count != 2) {
/* Note that this error message has been adjusted to make sense when called
* from the operator `MESH_OT_fill_grid` which has a 'prepare' pass which can
* extract two 'rail' loops from a single edge loop, see #72075. */
BMO_error_raise(bm,
op,
BMO_ERROR_CANCEL,
"Select two edge loops "
"or a single closed edge loop from which two edge loops can be calculated");
goto cleanup;
}
estore_a = static_cast<BMEdgeLoopStore *>(eloops.first);
estore_b = static_cast<BMEdgeLoopStore *>(eloops.last);
v_a_first = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_a)->first))->data);
v_a_last = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_a)->last))->data);
v_b_first = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_b)->first))->data);
v_b_last = static_cast<BMVert *>(
(static_cast<LinkData *>(BM_edgeloop_verts_get(estore_b)->last))->data);
if (BM_edgeloop_is_closed(estore_a) || BM_edgeloop_is_closed(estore_b)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Closed loops unsupported");
goto cleanup;
}
/* ok. all error checking done, now we can find the rail edges */
/* cheat here, temp hide all edges so they won't be included in rails
* this puts the mesh in an invalid state for a short time. */
bm_edgeloop_flag_set(estore_a, BM_ELEM_HIDDEN, true);
bm_edgeloop_flag_set(estore_b, BM_ELEM_HIDDEN, true);
if (BM_mesh_edgeloops_find_path(
bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_first, v_b_first) &&
BM_mesh_edgeloops_find_path(bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_last, v_b_last))
{
estore_rail_a = static_cast<BMEdgeLoopStore *>(eloops_rail.first);
estore_rail_b = static_cast<BMEdgeLoopStore *>(eloops_rail.last);
}
else {
BM_mesh_edgeloops_free(&eloops_rail);
if (BM_mesh_edgeloops_find_path(
bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_first, v_b_last) &&
BM_mesh_edgeloops_find_path(
bm, &eloops_rail, bm_edge_test_rail_cb, bm, v_a_last, v_b_first))
{
estore_rail_a = static_cast<BMEdgeLoopStore *>(eloops_rail.first);
estore_rail_b = static_cast<BMEdgeLoopStore *>(eloops_rail.last);
BM_edgeloop_flip(bm, estore_b);
}
else {
BM_mesh_edgeloops_free(&eloops_rail);
}
}
bm_edgeloop_flag_set(estore_a, BM_ELEM_HIDDEN, false);
bm_edgeloop_flag_set(estore_b, BM_ELEM_HIDDEN, false);
if (eloops_rail.is_empty()) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Loops are not connected by wire/boundary edges");
goto cleanup;
}
BLI_assert(estore_a != estore_b);
BLI_assert(v_a_last != v_b_last);
if (BM_edgeloop_overlap_check(estore_rail_a, estore_rail_b)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Connecting edge loops overlap");
goto cleanup;
}
/* add vertices if needed */
{
BMEdgeLoopStore *estore_pairs[2][2] = {
{estore_a, estore_b},
{estore_rail_a, estore_rail_b},
};
int i;
for (i = 0; i < 2; i++) {
const int len_a = BM_edgeloop_length_get(estore_pairs[i][0]);
const int len_b = BM_edgeloop_length_get(estore_pairs[i][1]);
if (len_a != len_b) {
if (split_edges == nullptr) {
split_edges = std::make_unique<Set<BMEdge *>>();
}
if (len_a < len_b) {
BM_edgeloop_expand(bm, estore_pairs[i][0], len_b, true, split_edges.get());
}
else {
BM_edgeloop_expand(bm, estore_pairs[i][1], len_a, true, split_edges.get());
}
}
}
}
/* finally we have all edge loops needed */
bm_grid_fill(
bm, estore_a, estore_b, estore_rail_a, estore_rail_b, mat_nr, use_smooth, use_interp_simple);
changed = true;
if (split_edges) {
for (BMEdge *e : *split_edges) {
BM_edge_collapse(bm, e, e->v2, true, true);
}
}
cleanup:
BM_mesh_edgeloops_free(&eloops);
BM_mesh_edgeloops_free(&eloops_rail);
if (changed) {
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, FACE_OUT);
}
}
} // namespace blender

View File

@@ -0,0 +1,70 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Fill boundary edge loop(s) with faces.
*/
#include "bmesh.hh"
#include "bmesh_tools.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
void bmo_holes_fill_exec(BMesh *bm, BMOperator *op)
{
BMOperator op_attr;
const uint sides = BMO_slot_int_get(op->slots_in, "sides");
BM_mesh_elem_hflag_disable_all(bm, BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "edges", BM_EDGE, BM_ELEM_TAG, false);
BM_mesh_edgenet(bm, true, true); /* TODO: sides. */
/* bad - remove faces after as a workaround */
if (sides != 0) {
BMOIter siter;
BMFace *f;
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
BMO_ITER (f, &siter, op->slots_out, "faces.out", BM_FACE) {
if (f->len > sides) {
BM_face_kill(bm, f);
}
}
}
BMO_slot_buffer_from_enabled_hflag(bm, op, op->slots_out, "faces.out", BM_FACE, BM_ELEM_TAG);
/* --- Attribute Fill --- */
/* may as well since we have the faces already in a buffer */
BMO_op_initf(bm,
&op_attr,
op->flag,
"face_attribute_fill faces=%S use_normals=%b use_data=%b",
op,
"faces.out",
true,
true);
BMO_op_exec(bm, &op_attr);
/* check if some faces couldn't be touched */
if (BMO_slot_buffer_len(op_attr.slots_out, "faces_fail.out")) {
BMOIter siter;
BMFace *f;
BMO_ITER (f, &siter, op_attr.slots_out, "faces_fail.out", BM_FACE) {
BM_face_normal_update(f); /* Normals are zeroed. */
}
BMO_op_callf(bm, op->flag, "recalc_face_normals faces=%S", &op_attr, "faces_fail.out");
}
BMO_op_finish(bm, &op_attr);
}
} // namespace blender

View File

@@ -0,0 +1,128 @@
/* SPDX-FileCopyrightText: 2026 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Flattens vertices on a best-fitting plane.
*/
#include "BLI_array.hh"
#include "BLI_math_vector.h"
#include "BLI_math_vector.hh"
#include "BLI_vector.hh"
#include "BLI_vector_set.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
static float3 compute_centroid(Span<BMVert *> verts)
{
float3 center(0.0f);
for (BMVert *v : verts) {
center += float3(v->co);
}
center /= float(verts.size());
return center;
}
static float3 compute_average_face_normal(Span<BMFace *> faces)
{
float3 normal(0.0f);
for (BMFace *f : faces) {
normal += float3(f->no) * BM_face_calc_area(f);
}
return (normalize_v3(normal) != 0.0f) ? normal : float3(0.0f, 0.0f, 1.0f);
}
static Vector<BMVert *> collect_verts_from_faces(Span<BMFace *> faces)
{
VectorSet<BMVert *> verts;
for (BMFace *f : faces) {
BMIter viter;
BMVert *v;
BM_ITER_ELEM (v, &viter, f, BM_VERTS_OF_FACE) {
verts.add(v);
}
}
return verts.extract_vector();
}
void bmo_flatten_exec(BMesh *bm, BMOperator *op)
{
const float factor = BMO_slot_float_get(op->slots_in, "factor");
const FlattenMethod method = static_cast<FlattenMethod>(
BMO_slot_int_get(op->slots_in, "method"));
const bool lock_x = BMO_slot_bool_get(op->slots_in, "lock_x");
const bool lock_y = BMO_slot_bool_get(op->slots_in, "lock_y");
const bool lock_z = BMO_slot_bool_get(op->slots_in, "lock_z");
float3 view_direction(0.0f, 0.0f, 1.0f);
if (method == FLATTEN_VIEW) {
BMO_slot_vec_get(op->slots_in, "view_normal", view_direction);
}
BM_mesh_elem_hflag_disable_all(bm, BM_FACE, BM_ELEM_TAG, false);
BMO_slot_buffer_hflag_enable(bm, op->slots_in, "geom", BM_FACE, BM_ELEM_TAG, false);
Array<int> groups_array(bm->totface);
int (*group_index)[2];
const int group_num = BM_mesh_calc_face_groups(
bm, groups_array.data(), &group_index, nullptr, nullptr, nullptr, BM_ELEM_TAG, BM_EDGE);
BM_mesh_elem_table_ensure(bm, BM_FACE);
for (const int g : IndexRange(group_num)) {
const int start = group_index[g][0];
const int length = group_index[g][1];
Vector<BMFace *> faces;
faces.reserve(length);
for (const int i : IndexRange(start, length)) {
faces.append(BM_face_at_index(bm, groups_array[i]));
}
Vector<BMVert *> verts = collect_verts_from_faces(faces);
float3 center;
float3 normal(0.0f);
switch (method) {
case FLATTEN_BEST_FIT:
BM_verts_calc_normal_from_cloud_ex(
verts.data(), int(verts.size()), normal, center, nullptr);
break;
case FLATTEN_NORMAL:
normal = compute_average_face_normal(faces);
center = compute_centroid(verts);
break;
case FLATTEN_VIEW:
normal = view_direction;
center = compute_centroid(verts);
break;
}
BLI_assert(!math::is_zero(normal));
for (BMVert *v : verts) {
float3 co(v->co);
float3 projected = co - math::dot(co - center, normal) * normal;
if (lock_x) {
projected.x = co.x;
}
if (lock_y) {
projected.y = co.y;
}
if (lock_z) {
projected.z = co.z;
}
float3 co_final = math::interpolate(co, projected, factor);
copy_v3_v3(v->co, co_final);
}
}
MEM_delete(group_index);
}
} // namespace blender

View File

@@ -0,0 +1,606 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Create a convex hull using bullet physics library.
*/
#ifdef WITH_BULLET
# include "MEM_guardedalloc.h"
# include "BLI_listbase.h"
# include "BLI_math_geom.h"
# include "BLI_vector.hh"
# include "RBI_hull_api.h"
/* XXX: using 128 for totelem and `pchunk` of `mempool`, no idea what good
* values would be though */
# include "bmesh.hh"
# include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/* Internal operator flags */
enum {
HULL_FLAG_INPUT = (1 << 0),
HULL_FLAG_INTERIOR_ELE = (1 << 1),
HULL_FLAG_OUTPUT_GEOM = (1 << 2),
HULL_FLAG_DEL = (1 << 3),
HULL_FLAG_HOLE = (1 << 4),
};
/* Store hull triangles separate from BMesh faces until the end; this
* way we don't have to worry about cleaning up extraneous edges or
* incorrectly deleting existing geometry. */
struct HullTriangle {
BMVert *v[3];
float no[3];
int skip;
};
/*************************** Hull Triangles ***************************/
static void hull_add_triangle(
BMesh *bm, BLI_mempool *hull_triangles, BMVert *v1, BMVert *v2, BMVert *v3)
{
HullTriangle *t;
int i;
t = static_cast<HullTriangle *>(BLI_mempool_calloc(hull_triangles));
t->v[0] = v1;
t->v[1] = v2;
t->v[2] = v3;
/* Mark triangles vertices as not interior */
for (i = 0; i < 3; i++) {
BMO_vert_flag_disable(bm, t->v[i], HULL_FLAG_INTERIOR_ELE);
}
normal_tri_v3(t->no, v1->co, v2->co, v3->co);
}
static BMFace *hull_find_example_face(BMesh *bm, BMEdge *e)
{
BMIter iter;
BMFace *f;
BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
if (BMO_face_flag_test(bm, f, HULL_FLAG_INPUT) ||
BMO_face_flag_test(bm, f, HULL_FLAG_OUTPUT_GEOM) == false)
{
return f;
}
}
return nullptr;
}
static void hull_output_triangles(BMesh *bm, BLI_mempool *hull_triangles)
{
BLI_mempool_iter iter;
BLI_mempool_iternew(hull_triangles, &iter);
HullTriangle *t;
while ((t = static_cast<HullTriangle *>(BLI_mempool_iterstep(&iter)))) {
int i;
if (!t->skip) {
BMEdge *edges[3] = {
BM_edge_create(bm, t->v[0], t->v[1], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, t->v[1], t->v[2], nullptr, BM_CREATE_NO_DOUBLE),
BM_edge_create(bm, t->v[2], t->v[0], nullptr, BM_CREATE_NO_DOUBLE),
};
BMFace *f, *example = nullptr;
f = BM_face_exists(t->v, 3);
if (f != nullptr) {
/* If the operator is run with "use_existing_faces"
* disabled, but an output face in the hull is the
* same as a face in the existing mesh, it should not
* be marked as unused or interior. */
BMO_face_flag_enable(bm, f, HULL_FLAG_OUTPUT_GEOM);
BMO_face_flag_disable(bm, f, HULL_FLAG_HOLE);
BMO_face_flag_disable(bm, f, HULL_FLAG_INTERIOR_ELE);
}
else {
/* Look for an adjacent face that existed before the hull */
for (i = 0; i < 3; i++) {
if (!example) {
example = hull_find_example_face(bm, edges[i]);
}
}
/* Create new hull face */
f = BM_face_create_verts(bm, t->v, 3, example, BM_CREATE_NO_DOUBLE, true);
BM_face_copy_shared(bm, f, nullptr, nullptr);
}
/* Mark face for 'geom.out' slot and select */
BMO_face_flag_enable(bm, f, HULL_FLAG_OUTPUT_GEOM);
BM_face_select_set(bm, f, true);
/* Mark edges for 'geom.out' slot */
for (i = 0; i < 3; i++) {
BMO_edge_flag_enable(bm, edges[i], HULL_FLAG_OUTPUT_GEOM);
}
}
else {
/* Mark input edges for 'geom.out' slot */
for (i = 0; i < 3; i++) {
const int next = (i == 2 ? 0 : i + 1);
BMEdge *e = BM_edge_exists(t->v[i], t->v[next]);
if (e && BMO_edge_flag_test(bm, e, HULL_FLAG_INPUT) &&
!BMO_edge_flag_test(bm, e, HULL_FLAG_HOLE))
{
BMO_edge_flag_enable(bm, e, HULL_FLAG_OUTPUT_GEOM);
}
}
}
/* Mark verts for 'geom.out' slot */
for (i = 0; i < 3; i++) {
BMO_vert_flag_enable(bm, t->v[i], HULL_FLAG_OUTPUT_GEOM);
}
}
}
/***************************** Final Edges ****************************/
struct HullFinalEdges {
Map<BMVert *, ListBaseT<LinkData> *> *edges;
BLI_mempool *base_pool, *link_pool;
};
static LinkData *final_edges_find_link(ListBaseT<LinkData> *adj, BMVert *v)
{
for (LinkData &link : *adj) {
if (link.data == v) {
return &link;
}
}
return nullptr;
}
static int hull_final_edges_lookup(HullFinalEdges *final_edges, BMVert *v1, BMVert *v2)
{
ListBaseT<LinkData> *adj;
/* Use lower vertex pointer for hash key */
if (v1 > v2) {
std::swap(v1, v2);
}
adj = final_edges->edges->lookup_default(v1, nullptr);
if (!adj) {
return false;
}
return !!final_edges_find_link(adj, v2);
}
/* Used for checking whether a pre-existing edge lies on the hull */
static HullFinalEdges *hull_final_edges(BLI_mempool *hull_triangles)
{
HullFinalEdges *final_edges;
final_edges = MEM_new_zeroed<HullFinalEdges>("HullFinalEdges");
final_edges->edges = MEM_new<Map<BMVert *, ListBaseT<LinkData> *>>("final edges map");
final_edges->base_pool = BLI_mempool_create(
sizeof(ListBaseT<LinkData>), 0, 128, BLI_MEMPOOL_NOP);
final_edges->link_pool = BLI_mempool_create(sizeof(LinkData), 0, 128, BLI_MEMPOOL_NOP);
BLI_mempool_iter iter;
BLI_mempool_iternew(hull_triangles, &iter);
HullTriangle *t;
while ((t = static_cast<HullTriangle *>(BLI_mempool_iterstep(&iter)))) {
LinkData *link;
int i;
for (i = 0; i < 3; i++) {
BMVert *v1 = t->v[i];
BMVert *v2 = t->v[(i + 1) % 3];
/* Use lower vertex pointer for hash key */
if (v1 > v2) {
std::swap(v1, v2);
}
ListBaseT<LinkData> *adj = final_edges->edges->lookup_or_add_cb(v1, [&]() {
return static_cast<ListBaseT<LinkData> *>(BLI_mempool_calloc(final_edges->base_pool));
});
if (!final_edges_find_link(adj, v2)) {
link = static_cast<LinkData *>(BLI_mempool_calloc(final_edges->link_pool));
link->data = v2;
BLI_addtail(adj, link);
}
}
}
return final_edges;
}
static void hull_final_edges_free(HullFinalEdges *final_edges)
{
MEM_delete(final_edges->edges);
BLI_mempool_destroy(final_edges->base_pool);
BLI_mempool_destroy(final_edges->link_pool);
MEM_delete(final_edges);
}
/**************************** Final Output ****************************/
static void hull_remove_overlapping(BMesh *bm,
BLI_mempool *hull_triangles,
HullFinalEdges *final_edges)
{
BLI_mempool_iter iter;
BLI_mempool_iternew(hull_triangles, &iter);
HullTriangle *t;
while ((t = static_cast<HullTriangle *>(BLI_mempool_iterstep(&iter)))) {
BMIter bm_iter1, bm_iter2;
BMFace *f;
bool f_on_hull;
BM_ITER_ELEM (f, &bm_iter1, t->v[0], BM_FACES_OF_VERT) {
BMEdge *e;
/* Check that all the face's edges are on the hull,
* otherwise can't reuse it */
f_on_hull = true;
BM_ITER_ELEM (e, &bm_iter2, f, BM_EDGES_OF_FACE) {
if (!hull_final_edges_lookup(final_edges, e->v1, e->v2)) {
f_on_hull = false;
break;
}
}
/* NOTE: can't change ghash while iterating, so mark
* with 'skip' flag rather than deleting triangles */
if (BM_vert_in_face(t->v[1], f) && BM_vert_in_face(t->v[2], f) && f_on_hull) {
t->skip = true;
BMO_face_flag_disable(bm, f, HULL_FLAG_INTERIOR_ELE);
BMO_face_flag_enable(bm, f, HULL_FLAG_HOLE);
}
}
}
}
static void hull_mark_interior_elements(BMesh *bm, BMOperator *op, HullFinalEdges *final_edges)
{
BMEdge *e;
BMFace *f;
BMOIter oiter;
/* Check for interior edges too */
BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
if (!hull_final_edges_lookup(final_edges, e->v1, e->v2)) {
BMO_edge_flag_enable(bm, e, HULL_FLAG_INTERIOR_ELE);
}
}
/* Mark all input faces as interior, some may be unmarked in
* hull_remove_overlapping() */
BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
BMO_face_flag_enable(bm, f, HULL_FLAG_INTERIOR_ELE);
}
}
static void hull_tag_unused(BMesh *bm, BMOperator *op)
{
BMIter iter;
BMOIter oiter;
BMVert *v;
BMEdge *e;
BMFace *f;
/* Mark vertices, edges, and faces that are already marked
* interior (i.e. were already part of the input, but not part of
* the hull), but that aren't also used by elements outside the
* input set */
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
if (BMO_vert_flag_test(bm, v, HULL_FLAG_INTERIOR_ELE)) {
bool del = true;
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
if (!BMO_edge_flag_test(bm, e, HULL_FLAG_INPUT)) {
del = false;
break;
}
}
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (!BMO_face_flag_test(bm, f, HULL_FLAG_INPUT)) {
del = false;
break;
}
}
if (del) {
BMO_vert_flag_enable(bm, v, HULL_FLAG_DEL);
}
}
}
BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
if (BMO_edge_flag_test(bm, e, HULL_FLAG_INTERIOR_ELE)) {
bool del = true;
BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
if (!BMO_face_flag_test(bm, f, HULL_FLAG_INPUT)) {
del = false;
break;
}
}
if (del) {
BMO_edge_flag_enable(bm, e, HULL_FLAG_DEL);
}
}
}
BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
if (BMO_face_flag_test(bm, f, HULL_FLAG_INTERIOR_ELE)) {
BMO_face_flag_enable(bm, f, HULL_FLAG_DEL);
}
}
}
static void hull_tag_holes(BMesh *bm, BMOperator *op)
{
BMIter iter;
BMOIter oiter;
BMFace *f;
BMEdge *e;
/* Unmark any hole faces if they are isolated or part of a
* border */
BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
if (BMO_face_flag_test(bm, f, HULL_FLAG_HOLE)) {
BM_ITER_ELEM (e, &iter, f, BM_EDGES_OF_FACE) {
if (BM_edge_is_boundary(e)) {
BMO_face_flag_disable(bm, f, HULL_FLAG_HOLE);
break;
}
}
}
}
/* Mark edges too if all adjacent faces are holes and the edge is
* not already isolated */
BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
bool hole = true;
bool any_faces = false;
BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
any_faces = true;
if (!BMO_face_flag_test(bm, f, HULL_FLAG_HOLE)) {
hole = false;
break;
}
}
if (hole && any_faces) {
BMO_edge_flag_enable(bm, e, HULL_FLAG_HOLE);
}
}
}
static int hull_input_vert_count(BMOperator *op)
{
BMOIter oiter;
BMVert *v;
int count = 0;
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
count++;
}
return count;
}
static BMVert **hull_input_verts_copy(BMOperator *op, const int num_input_verts)
{
BMOIter oiter;
BMVert *v;
BMVert **input_verts = MEM_new_array_zeroed<BMVert *>(num_input_verts, AT);
int i = 0;
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
input_verts[i++] = v;
}
return input_verts;
}
static float (*hull_verts_for_bullet(BMVert **input_verts, const int num_input_verts))[3]
{
float (*coords)[3] = MEM_new_array_zeroed<float[3]>(num_input_verts, __func__);
int i;
for (i = 0; i < num_input_verts; i++) {
copy_v3_v3(coords[i], input_verts[i]->co);
}
return coords;
}
static BMVert **hull_verts_from_bullet(plConvexHull hull,
BMVert **input_verts,
const int num_input_verts)
{
const int num_verts = plConvexHullNumVertices(hull);
BMVert **hull_verts = MEM_new_array_uninitialized<BMVert *>(num_verts, AT);
int i;
for (i = 0; i < num_verts; i++) {
float co[3];
int original_index;
plConvexHullGetVertex(hull, i, co, &original_index);
if (original_index >= 0 && original_index < num_input_verts) {
hull_verts[i] = input_verts[original_index];
}
else {
BLI_assert_msg(0, "Unexpected new vertex in hull output");
}
}
return hull_verts;
}
static void hull_from_bullet(BMesh *bm, BMOperator *op, BLI_mempool *hull_triangles)
{
BMVert **input_verts;
float (*coords)[3];
BMVert **hull_verts;
plConvexHull hull;
int i, count = 0;
const int num_input_verts = hull_input_vert_count(op);
input_verts = hull_input_verts_copy(op, num_input_verts);
coords = hull_verts_for_bullet(input_verts, num_input_verts);
hull = plConvexHullCompute(coords, num_input_verts);
hull_verts = hull_verts_from_bullet(hull, input_verts, num_input_verts);
count = plConvexHullNumFaces(hull);
Vector<int> fvi;
for (i = 0; i < count; i++) {
const int len = plConvexHullGetFaceSize(hull, i);
if (len > 2) {
BMVert *fv[3];
int j;
/* Get face vertex indices */
fvi.reinitialize(len);
plConvexHullGetFaceVertices(hull, i, fvi.data());
/* NOTE: here we throw away any NGons from Bullet and turn
* them into triangle fans. Would be nice to use these
* directly, but will have to wait until HullTriangle goes
* away (TODO) */
fv[0] = hull_verts[fvi[0]];
for (j = 2; j < len; j++) {
fv[1] = hull_verts[fvi[j - 1]];
fv[2] = hull_verts[fvi[j]];
hull_add_triangle(bm, hull_triangles, fv[0], fv[1], fv[2]);
}
}
}
plConvexHullDelete(hull);
MEM_delete(hull_verts);
MEM_delete(coords);
MEM_delete(input_verts);
}
/* Check that there are at least three vertices in the input */
static bool hull_num_input_verts_is_ok(BMOperator *op)
{
BMOIter oiter;
BMVert *v;
int partial_num_verts = 0;
BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
partial_num_verts++;
if (partial_num_verts >= 3) {
break;
}
}
return (partial_num_verts >= 3);
}
void bmo_convex_hull_exec(BMesh *bm, BMOperator *op)
{
HullFinalEdges *final_edges;
BLI_mempool *hull_triangles;
BMElemF *ele;
BMOIter oiter;
/* Verify that at least three verts in the input */
if (!hull_num_input_verts_is_ok(op)) {
BMO_error_raise(bm, op, BMO_ERROR_CANCEL, "Requires at least three vertices");
return;
}
/* Tag input elements */
BMO_ITER (ele, &oiter, op->slots_in, "input", BM_ALL) {
/* Mark all vertices as interior to begin with */
if (ele->head.htype == BM_VERT) {
BMO_vert_flag_enable(bm, (BMVert *)ele, HULL_FLAG_INPUT | HULL_FLAG_INTERIOR_ELE);
}
else if (ele->head.htype == BM_EDGE) {
BMO_edge_flag_enable(bm, (BMEdge *)ele, HULL_FLAG_INPUT);
}
else {
BMO_face_flag_enable(bm, (BMFace *)ele, HULL_FLAG_INPUT);
}
}
hull_triangles = BLI_mempool_create(sizeof(HullTriangle), 0, 128, BLI_MEMPOOL_ALLOW_ITER);
hull_from_bullet(bm, op, hull_triangles);
final_edges = hull_final_edges(hull_triangles);
hull_mark_interior_elements(bm, op, final_edges);
/* Remove hull triangles covered by an existing face */
if (BMO_slot_bool_get(op->slots_in, "use_existing_faces")) {
hull_remove_overlapping(bm, hull_triangles, final_edges);
hull_tag_holes(bm, op);
}
/* Done with edges */
hull_final_edges_free(final_edges);
/* Convert hull triangles to BMesh faces */
hull_output_triangles(bm, hull_triangles);
BLI_mempool_destroy(hull_triangles);
hull_tag_unused(bm, op);
/* Output slot of input elements that ended up inside the hull
* rather than part of it */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_interior.out", BM_ALL_NOLOOP, HULL_FLAG_INTERIOR_ELE);
/* Output slot of input elements that ended up inside the hull and
* are unused by other geometry. */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_unused.out", BM_ALL_NOLOOP, HULL_FLAG_DEL);
/* Output slot of faces and edges that were in the input and on
* the hull (useful for cases like bridging where you want to
* delete some input geometry) */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom_holes.out", BM_ALL_NOLOOP, HULL_FLAG_HOLE);
/* Output slot of all hull vertices, faces, and edges */
BMO_slot_buffer_from_enabled_flag(
bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, HULL_FLAG_OUTPUT_GEOM);
}
} // namespace blender
#endif /* WITH_BULLET */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* This file contains functions
* for converting a Mesh
* into a Bmesh, and back again.
*/
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_object_types.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh"
#include "BKE_global.hh"
namespace blender {
void bmo_mesh_to_bmesh_exec(BMesh *bm, BMOperator *op)
{
Object *ob = static_cast<Object *>(BMO_slot_ptr_get(op->slots_in, "object"));
Mesh *mesh = static_cast<Mesh *>(BMO_slot_ptr_get(op->slots_in, "mesh"));
bool set_key = BMO_slot_bool_get(op->slots_in, "use_shapekey");
BMeshFromMeshParams params{};
params.use_shapekey = set_key;
params.active_shapekey = ob->shapenr;
BM_mesh_bm_from_me(bm, mesh, &params);
if (mesh->key && ob->shapenr > mesh->key->totkey) {
ob->shapenr = mesh->key->totkey - 1;
}
}
void bmo_object_load_bmesh_exec(BMesh *bm, BMOperator *op)
{
Object *ob = static_cast<Object *>(BMO_slot_ptr_get(op->slots_in, "object"));
// Scene *scene = BMO_slot_ptr_get(op, "scene");
Mesh *mesh = id_cast<Mesh *>(ob->data);
BMO_op_callf(bm, op->flag, "bmesh_to_mesh mesh=%p object=%p", mesh, ob);
}
void bmo_bmesh_to_mesh_exec(BMesh *bm, BMOperator *op)
{
Mesh *mesh = static_cast<Mesh *>(BMO_slot_ptr_get(op->slots_in, "mesh"));
// Object *ob = BMO_slot_ptr_get(op, "object");
BMeshToMeshParams params{};
params.calc_object_remap = true;
BM_mesh_bm_to_me(G.main, bm, mesh, &params);
}
} // namespace blender

View File

@@ -0,0 +1,110 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Basic mirror, optionally with UVs's.
*/
#include "BLI_math_base.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_NEW 1
void bmo_mirror_exec(BMesh *bm, BMOperator *op)
{
BMOperator dupeop, weldop;
BMOIter siter;
BMVert *v;
float scale[3] = {1.0f, 1.0f, 1.0f};
float dist = BMO_slot_float_get(op->slots_in, "merge_dist");
int i;
int axis = BMO_slot_int_get(op->slots_in, "axis");
bool mirror_u = BMO_slot_bool_get(op->slots_in, "mirror_u");
bool mirror_v = BMO_slot_bool_get(op->slots_in, "mirror_v");
bool mirror_udim = BMO_slot_bool_get(op->slots_in, "mirror_udim");
BMOpSlot *slot_targetmap;
BMOpSlot *slot_vertmap;
BMO_op_initf(bm, &dupeop, op->flag, "duplicate geom=%s", op, "geom");
BMO_op_exec(bm, &dupeop);
BMO_slot_buffer_flag_enable(bm, dupeop.slots_out, "geom.out", BM_ALL_NOLOOP, ELE_NEW);
/* feed old data to transform bmo */
scale[axis] = -1.0f;
BMO_op_callf(bm,
op->flag,
"scale verts=%fv vec=%v space=%s use_shapekey=%s",
ELE_NEW,
scale,
op,
"matrix",
op,
"use_shapekey");
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
slot_vertmap = BMO_slot_get(dupeop.slots_out, "vert_map.out");
BMO_ITER (v, &siter, op->slots_in, "geom", BM_VERT) {
if (fabsf(v->co[axis]) <= dist) {
BMVert *v_new = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_vertmap, v));
BLI_assert(v_new != nullptr);
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_new, v);
}
}
if (mirror_u || mirror_v) {
BMFace *f;
BMLoop *l;
float *luv;
const int totlayer = CustomData_number_of_layers(&bm->ldata, CD_PROP_FLOAT2);
BMIter liter;
BMO_ITER (f, &siter, dupeop.slots_out, "geom.out", BM_FACE) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
for (i = 0; i < totlayer; i++) {
luv = static_cast<float *>(
CustomData_bmesh_get_n(&bm->ldata, l->head.data, CD_PROP_FLOAT2, i));
if (mirror_u) {
float uv_u = luv[0];
if (mirror_udim) {
luv[0] = ceilf(uv_u) - fmodf(uv_u, 1.0f);
}
else {
luv[0] = 1.0f - uv_u;
}
}
if (mirror_v) {
float uv_v = luv[1];
if (mirror_udim) {
luv[1] = ceilf(uv_v) - fmodf(uv_v, 1.0f);
}
else {
luv[1] = 1.0f - uv_v;
}
}
}
}
}
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
BMO_op_finish(bm, &dupeop);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out", BM_ALL_NOLOOP, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,303 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Functionality for flipping faces to make normals consistent.
*/
#include "MEM_guardedalloc.h"
#include "BLI_linklist_stack.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
/********* Right-hand faces implementation ****** */
#define FACE_FLAG (1 << 0)
#define FACE_FLIP (1 << 1)
#define FACE_TEMP (1 << 2)
static bool bmo_recalc_normal_loop_filter_cb(const BMLoop *l, void * /*user_data*/)
{
return BM_edge_is_manifold(l->e);
}
/**
* This uses a more comprehensive test to see if the furthest face from the center
* is pointing towards the center or not.
*
* A simple test could just check the dot product
* of the faces-normal and the direction from the center,
* however this can fail for faces which make a sharp spike. eg:
*
* <pre>
* +
* |\ <- face
* + +
* \ \
* \ \
* \ +--------------+
* \ |
* \ center -> + |
* \ |
* +------------+
* </pre>
*
* In the example above, the \a face can point towards the \a center
* which would end up flipping the normals inwards.
*
* To take these spikes into account, find the furthest face-loop-vertex.
*/
/**
* \return a face index in \a faces and set \a r_is_flip
* if the face is flipped away from the center.
*/
static int recalc_face_normals_find_index(BMesh *bm,
BMFace **faces,
const int faces_len,
bool *r_is_flip)
{
const float eps = FLT_EPSILON;
float cent_area_accum = 0.0f;
float cent[3];
const float cent_fac = 1.0f / float(faces_len);
bool is_flip = false;
int f_start_index;
int i;
/** Search for the best loop. Members are compared in-order defined here. */
struct {
/**
* Squared distance from the center to the loops vertex 'l->v'.
* The normalized direction between the center and this vertex
* is also used for the dot-products below.
*/
float dist_sq;
/**
* Signed dot product using the normalized edge vector,
* (best of 'l->prev->v' or 'l->next->v').
*/
float edge_dot;
/**
* Unsigned dot product using the loop-normal
* (sign is used to check if we need to flip).
*/
float loop_dot;
} best, test;
UNUSED_VARS_NDEBUG(bm);
zero_v3(cent);
/* first calculate the center */
for (i = 0; i < faces_len; i++) {
float f_cent[3];
const float f_area = BM_face_calc_area(faces[i]);
BM_face_calc_center_median_weighted(faces[i], f_cent);
madd_v3_v3fl(cent, f_cent, cent_fac * f_area);
cent_area_accum += f_area;
BLI_assert(BMO_face_flag_test(bm, faces[i], FACE_TEMP) == 0);
BLI_assert(BM_face_is_normal_valid(faces[i]));
}
if (cent_area_accum != 0.0f) {
mul_v3_fl(cent, 1.0f / cent_area_accum);
}
/* Distances must start above zero,
* or we can't do meaningful calculations based on the direction to the center */
best.dist_sq = eps;
best.edge_dot = best.loop_dot = -FLT_MAX;
/* used in degenerate cases only */
f_start_index = 0;
/**
* Find the outer-most vertex, comparing distance to the center,
* then the outer-most loop attached to that vertex.
*
* Important this is correctly detected,
* where casting a ray from the center won't hit any loops past this one.
* Otherwise the result may be incorrect.
*/
for (i = 0; i < faces_len; i++) {
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(faces[i]);
do {
bool is_best_dist_sq;
float dir[3];
sub_v3_v3v3(dir, l_iter->v->co, cent);
test.dist_sq = len_squared_v3(dir);
is_best_dist_sq = (test.dist_sq > best.dist_sq);
if (is_best_dist_sq || (test.dist_sq == best.dist_sq)) {
float edge_dir_pair[2][3];
mul_v3_fl(dir, 1.0f / sqrtf(test.dist_sq));
sub_v3_v3v3(edge_dir_pair[0], l_iter->next->v->co, l_iter->v->co);
sub_v3_v3v3(edge_dir_pair[1], l_iter->prev->v->co, l_iter->v->co);
if ((normalize_v3(edge_dir_pair[0]) > eps) && (normalize_v3(edge_dir_pair[1]) > eps)) {
bool is_best_edge_dot;
test.edge_dot = max_ff(dot_v3v3(dir, edge_dir_pair[0]), dot_v3v3(dir, edge_dir_pair[1]));
is_best_edge_dot = (test.edge_dot > best.edge_dot);
if (is_best_dist_sq || is_best_edge_dot || (test.edge_dot == best.edge_dot)) {
float loop_dir[3];
cross_v3_v3v3(loop_dir, edge_dir_pair[0], edge_dir_pair[1]);
if (normalize_v3(loop_dir) > eps) {
float loop_dir_dot;
/* Highly unlikely the furthest loop is also the concave part of an ngon,
* but it can be contrived with _very_ non-planar faces - so better check. */
if (UNLIKELY(dot_v3v3(loop_dir, l_iter->f->no) < 0.0f)) {
negate_v3(loop_dir);
}
loop_dir_dot = dot_v3v3(dir, loop_dir);
test.loop_dot = fabsf(loop_dir_dot);
if (is_best_dist_sq || is_best_edge_dot || (test.loop_dot > best.loop_dot)) {
best = test;
f_start_index = i;
is_flip = (loop_dir_dot < 0.0f);
}
}
}
}
}
} while ((l_iter = l_iter->next) != l_first);
}
*r_is_flip = is_flip;
return f_start_index;
}
/**
* Given an array of faces, recalculate their normals.
* this functions assumes all faces in the array are connected by edges.
*
* \param bm:
* \param faces: Array of connected faces.
* \param faces_len: Length of \a faces
* \param oflag: Flag to check before doing the actual face flipping.
*/
static void bmo_recalc_face_normals_array(BMesh *bm,
BMFace **faces,
const int faces_len,
const short oflag)
{
int i, f_start_index;
const short oflag_flip = oflag | FACE_FLIP;
bool is_flip;
BMFace *f;
BLI_LINKSTACK_DECLARE(fstack, BMFace *);
f_start_index = recalc_face_normals_find_index(bm, faces, faces_len, &is_flip);
if (is_flip) {
BMO_face_flag_enable(bm, faces[f_start_index], FACE_FLIP);
}
/* now that we've found our starting face, make all connected faces
* have the same winding. this is done recursively, using a manual
* stack (if we use simple function recursion, we'd end up overloading
* the stack on large meshes). */
BLI_LINKSTACK_INIT(fstack);
BLI_LINKSTACK_PUSH(fstack, faces[f_start_index]);
BMO_face_flag_enable(bm, faces[f_start_index], FACE_TEMP);
while ((f = BLI_LINKSTACK_POP(fstack))) {
const bool flip_state = BMO_face_flag_test_bool(bm, f, FACE_FLIP);
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMLoop *l_other = l_iter->radial_next;
if ((l_other != l_iter) && bmo_recalc_normal_loop_filter_cb(l_iter, nullptr)) {
if (!BMO_face_flag_test(bm, l_other->f, FACE_TEMP)) {
BMO_face_flag_enable(bm, l_other->f, FACE_TEMP);
BMO_face_flag_set(bm, l_other->f, FACE_FLIP, (l_other->v == l_iter->v) != flip_state);
BLI_LINKSTACK_PUSH(fstack, l_other->f);
}
}
} while ((l_iter = l_iter->next) != l_first);
}
BLI_LINKSTACK_FREE(fstack);
/* apply flipping to oflag'd faces */
for (i = 0; i < faces_len; i++) {
if (BMO_face_flag_test(bm, faces[i], oflag_flip) == oflag_flip) {
BM_face_normal_flip(bm, faces[i]);
}
BMO_face_flag_disable(bm, faces[i], FACE_TEMP);
}
}
/**
* Put normal to the outside, and set the first direction flags in edges
*
* then check the object, and set directions / direction-flags:
* but only for edges with 1 or 2 faces this is in fact the 'select connected'
*
* in case all faces were not done: start over with 'find the ultimate ...'.
*/
void bmo_recalc_face_normals_exec(BMesh *bm, BMOperator *op)
{
int *groups_array = MEM_new_array_uninitialized<int>(bm->totface, __func__);
BMFace **faces_grp = MEM_new_array_uninitialized<BMFace *>(bm->totface, __func__);
int (*group_index)[2];
const int group_tot = BM_mesh_calc_face_groups(bm,
groups_array,
&group_index,
bmo_recalc_normal_loop_filter_cb,
nullptr,
nullptr,
0,
BM_EDGE);
int i;
BMO_slot_buffer_flag_enable(bm, op->slots_in, "faces", BM_FACE, FACE_FLAG);
BM_mesh_elem_table_ensure(bm, BM_FACE);
for (i = 0; i < group_tot; i++) {
const int fg_sta = group_index[i][0];
const int fg_len = group_index[i][1];
int j;
bool is_calc = false;
for (j = 0; j < fg_len; j++) {
faces_grp[j] = BM_face_at_index(bm, groups_array[fg_sta + j]);
if (is_calc == false) {
is_calc = BMO_face_flag_test_bool(bm, faces_grp[j], FACE_FLAG);
}
}
if (is_calc) {
bmo_recalc_face_normals_array(bm, faces_grp, fg_len, FACE_FLAG);
}
}
MEM_delete(faces_grp);
MEM_delete(groups_array);
MEM_delete(group_index);
}
} // namespace blender

View File

@@ -0,0 +1,278 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Simple edge offset functionality.
*
* \note Actual offset is done by edge-slide.
* (this only changes topology)
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_math_vector.h"
#include "BLI_utildefines_stack.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define USE_CAP_OPTION
#define ELE_NEW (1 << 0)
#ifdef USE_CAP_OPTION
# define ELE_VERT_ENDPOINT (1 << 1)
#endif
/* set for debugging */
#define OFFSET 0.0f
static BMFace *bm_face_split_walk_back(BMesh *bm, BMLoop *l_src, BMLoop **r_l)
{
float (*cos)[3];
BMLoop *l_dst;
BMFace *f;
int num, i;
for (l_dst = l_src->prev, num = 0; BM_elem_index_get(l_dst->prev->v) != -1;
l_dst = l_dst->prev, num++)
{
/* pass */
}
BLI_assert(num != 0);
Array<float3, BM_DEFAULT_NGON_STACK_SIZE> cos_buf(num);
cos = reinterpret_cast<float (*)[3]>(cos_buf.data());
for (l_dst = l_src->prev, i = 0; BM_elem_index_get(l_dst->prev->v) != -1;
l_dst = l_dst->prev, i++)
{
copy_v3_v3(cos[num - (i + 1)], l_dst->v->co);
}
f = BM_face_split_n(bm, l_src->f, l_dst->prev, l_src->next, cos, num, r_l, nullptr);
return f;
}
void bmo_offset_edgeloops_exec(BMesh *bm, BMOperator *op)
{
const int edges_num = BMO_slot_buffer_len(op->slots_in, "edges");
BMVert **verts;
STACK_DECLARE(verts);
int i;
#ifdef USE_CAP_OPTION
bool use_cap_endpoint = BMO_slot_bool_get(op->slots_in, "use_cap_endpoint");
int v_edges_max = 0;
#endif
BMOIter oiter;
/* only so we can detect new verts (index == -1) */
BM_mesh_elem_index_ensure(bm, BM_VERT);
BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
/* over alloc */
verts = MEM_new_array_uninitialized<BMVert *>((edges_num * 2), __func__);
STACK_INIT(verts, (edges_num * 2));
{
BMEdge *e;
BMO_ITER (e, &oiter, op->slots_in, "edges", BM_EDGE) {
int j;
BM_elem_flag_enable(e, BM_ELEM_TAG);
for (j = 0; j < 2; j++) {
BMVert *v_edge = *(&(e->v1) + j);
if (!BM_elem_flag_test(v_edge, BM_ELEM_TAG)) {
BM_elem_flag_enable(v_edge, BM_ELEM_TAG);
STACK_PUSH(verts, v_edge);
}
}
}
}
/* -------------------------------------------------------------------- */
/* Remove verts only used by tagged edges */
for (i = 0; i < STACK_SIZE(verts); i++) {
BMIter iter;
int flag = 0;
BMEdge *e;
BM_ITER_ELEM (e, &iter, verts[i], BM_EDGES_OF_VERT) {
flag |= BM_elem_flag_test(e, BM_ELEM_TAG) ? 1 : 2;
if (flag == (1 | 2)) {
break;
}
}
/* only boundary verts are interesting */
if (flag != (1 | 2)) {
STACK_REMOVE(verts, i);
}
}
/* possible but unlikely we have no mixed vertices */
if (UNLIKELY(STACK_SIZE(verts) == 0)) {
MEM_delete(verts);
return;
}
/* main loop */
for (i = 0; i < STACK_SIZE(verts); i++) {
int v_edges_num = 0;
int v_edges_num_untag = 0;
BMVert *v = verts[i];
BMIter iter;
BMEdge *e;
BM_ITER_ELEM (e, &iter, verts[i], BM_EDGES_OF_VERT) {
if (!BM_elem_flag_test(e, BM_ELEM_TAG)) {
BMVert *v_other;
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, e, BM_LOOPS_OF_EDGE) {
BM_elem_flag_enable(l->f, BM_ELEM_TAG);
}
v_other = BM_edge_other_vert(e, v);
BM_edge_split(bm, e, v_other, nullptr, 1.0f - OFFSET);
}
else {
v_edges_num_untag += 1;
}
v_edges_num += 1;
}
#ifdef USE_CAP_OPTION
if (v_edges_num_untag == 1) {
BMO_vert_flag_enable(bm, v, ELE_VERT_ENDPOINT);
}
CLAMP_MIN(v_edges_max, v_edges_num);
#endif
}
for (i = 0; i < STACK_SIZE(verts); i++) {
BMVert *v = verts[i];
BMIter liter;
BMLoop *l;
BM_ITER_ELEM (l, &liter, v, BM_LOOPS_OF_VERT) {
if (BM_elem_flag_test(l->f, BM_ELEM_TAG) && (l->f->len != 3)) {
BMFace *f_cmp = l->f;
if ((BM_elem_index_get(l->next->v) == -1) && (BM_elem_index_get(l->prev->v) == -1)) {
#ifdef USE_CAP_OPTION
if (use_cap_endpoint || (BMO_vert_flag_test(bm, v, ELE_VERT_ENDPOINT) == 0))
#endif
{
BMLoop *l_new;
if (!BM_face_split_check_double_face(l->prev, l->next, 3) &&
(BM_face_split(bm, l->f, l->prev, l->next, &l_new, nullptr, true) != nullptr))
{
BLI_assert(f_cmp == l->f);
BLI_assert(f_cmp != l_new->f);
UNUSED_VARS_NDEBUG(f_cmp);
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
}
}
}
else if (l->f->len > 4) {
if (BM_elem_flag_test(l->e, BM_ELEM_TAG) != BM_elem_flag_test(l->prev->e, BM_ELEM_TAG)) {
if (BM_elem_index_get(l->next->v) == -1) {
if (BM_elem_index_get(l->prev->prev->v) == -1) {
BMLoop *l_new;
if (!BM_face_split_check_double_face(l->prev->prev, l->next, 4) &&
(BM_face_split(bm, l->f, l->prev->prev, l->next, &l_new, nullptr, true) !=
nullptr))
{
BLI_assert(f_cmp == l->f);
BLI_assert(f_cmp != l_new->f);
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
}
}
else {
/* walk backwards */
BMLoop *l_new;
bm_face_split_walk_back(bm, l, &l_new);
do {
BMO_edge_flag_enable(bm, l_new->e, ELE_NEW);
l_new = l_new->next;
} while (BM_vert_is_edge_pair(l_new->v));
BM_elem_flag_disable(l->f, BM_ELEM_TAG);
}
}
/* NOTE: instead of duplicate code in alternate direction,
* we can be sure to hit the other vertex, so the code above runs. */
#if 0
else if (BM_elem_index_get(l->prev->v) == -1) {
if (BM_elem_index_get(l->next->next->v) == -1) {
/* pass */
}
}
#endif
}
}
}
}
}
#ifdef USE_CAP_OPTION
if (use_cap_endpoint == false) {
Array<BMVert *, BM_DEFAULT_TOPOLOGY_STACK_SIZE> varr_buf(v_edges_max);
BMVert **varr = varr_buf.data();
STACK_DECLARE(varr);
BMVert *v;
for (i = 0; i < STACK_SIZE(verts); i++) {
BMIter iter;
BMEdge *e;
v = verts[i];
STACK_INIT(varr, v_edges_max);
BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
BMVert *v_other;
v_other = BM_edge_other_vert(e, v);
if (BM_elem_index_get(v_other) == -1) {
if (BM_vert_is_edge_pair(v_other)) {
/* defer bmesh_kernel_join_edge_kill_vert to avoid looping over data we're removing */
v_other->e = e;
STACK_PUSH(varr, v_other);
}
}
}
while ((v = STACK_POP(varr))) {
bmesh_kernel_join_edge_kill_vert(bm, v->e, v, true, false, false, true);
}
}
}
#endif
MEM_delete(verts);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, ELE_NEW);
}
} // namespace blender

View File

@@ -0,0 +1,134 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Iteratively flatten 4+ sided faces.
*/
#include "MEM_guardedalloc.h"
#include "BLI_ghash.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define ELE_VERT_ADJUST (1 << 0)
#define ELE_FACE_ADJUST (1 << 1)
struct VertAccum {
float co[3] = {};
int co_tot = 0;
};
void bmo_planar_faces_exec(BMesh *bm, BMOperator *op)
{
const float fac = BMO_slot_float_get(op->slots_in, "factor");
const int iterations = BMO_slot_int_get(op->slots_in, "iterations");
const int faces_num = BMO_slot_buffer_len(op->slots_in, "faces");
const float eps = 0.00001f;
const float eps_sq = square_f(eps);
BMOIter oiter;
BMFace *f;
float (*faces_center)[3];
int i, iter_step, shared_vert_num;
faces_center = MEM_new_array_uninitialized<float[3]>(faces_num, __func__);
shared_vert_num = 0;
BMO_ITER_INDEX (f, &oiter, op->slots_in, "faces", BM_FACE, i) {
BMLoop *l_iter, *l_first;
if (f->len == 3) {
continue;
}
BM_face_calc_center_median_weighted(f, faces_center[i]);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
if (!BMO_vert_flag_test(bm, l_iter->v, ELE_VERT_ADJUST)) {
BMO_vert_flag_enable(bm, l_iter->v, ELE_VERT_ADJUST);
shared_vert_num += 1;
}
} while ((l_iter = l_iter->next) != l_first);
BMO_face_flag_enable(bm, f, ELE_FACE_ADJUST);
}
Map<BMVert *, VertAccum> vaccum_map;
vaccum_map.reserve(shared_vert_num);
for (iter_step = 0; iter_step < iterations; iter_step++) {
bool changed = false;
BMO_ITER_INDEX (f, &oiter, op->slots_in, "faces", BM_FACE, i) {
BMLoop *l_iter, *l_first;
float plane[4];
if (!BMO_face_flag_test(bm, f, ELE_FACE_ADJUST)) {
continue;
}
BMO_face_flag_disable(bm, f, ELE_FACE_ADJUST);
BLI_assert(f->len != 3);
/* keep original face data (else we 'move' the face) */
#if 0
BM_face_normal_update(f);
BM_face_calc_center_median_weighted(f, f_center);
#endif
plane_from_point_normal_v3(plane, faces_center[i], f->no);
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
VertAccum &va = vaccum_map.lookup_or_add_default(l_iter->v);
float co[3];
closest_to_plane_normalized_v3(co, plane, l_iter->v->co);
va.co_tot += 1;
interp_v3_v3v3(va.co, va.co, co, 1.0f / float(va.co_tot));
} while ((l_iter = l_iter->next) != l_first);
}
for (const auto &item : vaccum_map.items()) {
BMVert *v = item.key;
const VertAccum &va = item.value;
BMIter iter;
if (len_squared_v3v3(v->co, va.co) > eps_sq) {
BMO_vert_flag_enable(bm, v, ELE_VERT_ADJUST);
interp_v3_v3v3(v->co, v->co, va.co, fac);
changed = true;
}
/* tag for re-calculation */
BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
if (f->len != 3) {
BMO_face_flag_enable(bm, f, ELE_FACE_ADJUST);
}
}
}
/* if nothing changed, break out early */
if (changed == false) {
break;
}
vaccum_map.clear();
}
MEM_delete(faces_center);
}
} // namespace blender

View File

@@ -0,0 +1,135 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Pokes a face.
*
* Splits a face into a triangle fan.
*/
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
#include "BLI_math_vector.h"
#include "BKE_customdata.hh"
namespace blender {
#define ELE_NEW 1
/**
* Pokes a face
*
* Splits a face into a triangle fan.
* Iterate over all selected faces, create a new center vertex and
* create triangles between original face edges and new center vertex.
*/
void bmo_poke_exec(BMesh *bm, BMOperator *op)
{
const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
BMOIter oiter;
BMFace *f;
const float offset = BMO_slot_float_get(op->slots_in, "offset");
const bool use_relative_offset = BMO_slot_bool_get(op->slots_in, "use_relative_offset");
const int center_mode = BMO_slot_int_get(op->slots_in, "center_mode");
void (*bm_face_calc_center_fn)(const BMFace *f, float r_cent[3]);
switch (center_mode) {
case BMOP_POKE_MEDIAN_WEIGHTED:
bm_face_calc_center_fn = BM_face_calc_center_median_weighted;
break;
case BMOP_POKE_BOUNDS:
bm_face_calc_center_fn = BM_face_calc_center_bounds;
break;
case BMOP_POKE_MEDIAN:
bm_face_calc_center_fn = BM_face_calc_center_median;
break;
default:
BLI_assert(0);
return;
}
BMO_ITER (f, &oiter, op->slots_in, "faces", BM_FACE) {
float f_center[3];
BMVert *v_center = nullptr;
BMLoop *l_iter, *l_first;
/* only interpolate the central loop from the face once,
* then copy to all others in the fan */
BMLoop *l_center_example;
/* 1.0 or the average length from the center to the face verts */
float offset_fac;
int i;
bm_face_calc_center_fn(f, f_center);
v_center = BM_vert_create(bm, f_center, nullptr, BM_CREATE_NOP);
BMO_vert_flag_enable(bm, v_center, ELE_NEW);
/* handled by BM_loop_interp_from_face */
// BM_vert_interp_from_face(bm, v_center, f);
if (use_relative_offset) {
offset_fac = 0.0f;
}
else {
offset_fac = 1.0f;
}
i = 0;
l_iter = l_first = BM_FACE_FIRST_LOOP(f);
do {
BMLoop *l_new;
BMFace *f_new = BM_face_create_quad_tri(
bm, l_iter->v, l_iter->next->v, v_center, nullptr, f, BM_CREATE_NOP);
l_new = BM_FACE_FIRST_LOOP(f_new);
if (i == 0) {
l_center_example = l_new->prev;
BM_loop_interp_from_face(bm, l_center_example, f, true, false);
}
else {
BM_elem_attrs_copy(bm, l_center_example, l_new->prev);
}
/* Copy Loop Data */
BM_elem_attrs_copy(bm, l_iter, l_new);
BM_elem_attrs_copy(bm, l_iter->next, l_new->next);
BMO_face_flag_enable(bm, f_new, ELE_NEW);
if (cd_loop_mdisp_offset != -1) {
float f_new_center[3];
BM_face_calc_center_median(f_new, f_new_center);
BM_face_interp_multires_ex(bm, f_new, f, f_new_center, f_center, cd_loop_mdisp_offset);
}
if (use_relative_offset) {
offset_fac += len_v3v3(f_center, l_iter->v->co);
}
} while ((void)i++, (l_iter = l_iter->next) != l_first);
if (use_relative_offset) {
offset_fac /= float(f->len);
}
/* else remain at 1.0 */
copy_v3_v3(v_center->no, f->no);
madd_v3_v3fl(v_center->co, v_center->no, offset * offset_fac);
/* Kill Face */
BM_face_kill(bm, f);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "verts.out", BM_VERT, ELE_NEW);
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "faces.out", BM_FACE, ELE_NEW);
}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,930 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Welding and merging functionality.
*/
#include "MEM_guardedalloc.h"
#include "BLI_array.hh"
#include "BLI_kdtree.hh"
#include "BLI_listbase.h"
#include "BLI_map.hh"
#include "BLI_math_base.hh"
#include "BLI_math_vector.h"
#include "BLI_multi_value_map.hh"
#include "BLI_stack.h"
#include "BLI_stack.hh"
#include "BLI_utildefines_stack.h"
#include "BKE_customdata.hh"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh"
namespace blender {
static void remdoubles_splitface(BMFace *f, BMesh *bm, BMOperator *op, BMOpSlot *slot_targetmap)
{
BMIter liter;
BMLoop *l, *l_tar, *l_double;
bool split = false;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
BMVert *v_tar = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, l->v));
/* Ok: if `v_tar` is nullptr (e.g. not in the map) then it's
* a target vert, otherwise it's a double. */
if (v_tar) {
l_tar = BM_face_vert_share_loop(f, v_tar);
if (l_tar && (l_tar != l) && !BM_loop_is_adjacent(l_tar, l)) {
l_double = l;
split = true;
break;
}
}
}
if (split) {
BMLoop *l_new;
BMFace *f_new;
f_new = BM_face_split(bm, f, l_double, l_tar, &l_new, nullptr, false);
remdoubles_splitface(f, bm, op, slot_targetmap);
remdoubles_splitface(f_new, bm, op, slot_targetmap);
}
}
#define ELE_DEL 1
#define EDGE_COL 2
#define VERT_IN_FACE 4
/**
* Helper function for #bmo_weld_verts_exec so we can use stack memory.
*/
static BMFace *remdoubles_createface(BMesh *bm,
BMFace *f,
BMOpSlot *slot_targetmap,
bool *r_created)
{
BMEdge *e_new;
/* New ordered edges. */
Array<BMEdge *, BM_DEFAULT_NGON_STACK_SIZE> edges_buf(f->len);
BMEdge **edges = edges_buf.data();
/* New ordered verts. */
Array<BMVert *, BM_DEFAULT_NGON_STACK_SIZE> verts_buf(f->len);
BMVert **verts = verts_buf.data();
/* Original ordered loops to copy attributes into the new face. */
Array<BMLoop *, BM_DEFAULT_NGON_STACK_SIZE> loops_buf(f->len);
BMLoop **loops = loops_buf.data();
STACK_DECLARE(edges);
STACK_DECLARE(loops);
STACK_DECLARE(verts);
STACK_INIT(edges, f->len);
STACK_INIT(loops, f->len);
STACK_INIT(verts, f->len);
*r_created = false;
{
#define LOOP_MAP_VERT_INIT(l_init, v_map, is_del) \
v_map = l_init->v; \
is_del = BMO_vert_flag_test_bool(bm, v_map, ELE_DEL); \
if (is_del) { \
v_map = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v_map)); \
} \
((void)0)
BMLoop *l_first, *l_curr, *l_next;
BMVert *v_curr;
bool is_del_v_curr;
l_curr = l_first = BM_FACE_FIRST_LOOP(f);
LOOP_MAP_VERT_INIT(l_curr, v_curr, is_del_v_curr);
do {
BMVert *v_next;
bool is_del_v_next;
l_next = l_curr->next;
LOOP_MAP_VERT_INIT(l_next, v_next, is_del_v_next);
/* Only search for a new edge if one of the verts is mapped. */
if ((is_del_v_curr || is_del_v_next) == 0) {
e_new = l_curr->e;
}
else if (v_curr == v_next) {
e_new = nullptr; /* Skip. */
}
else {
e_new = BM_edge_exists(v_curr, v_next);
BLI_assert(e_new); /* Never fails. */
}
if (e_new) {
if (UNLIKELY(BMO_vert_flag_test(bm, v_curr, VERT_IN_FACE))) {
/* We can't make the face, bail out. */
STACK_CLEAR(edges);
goto finally;
}
BMO_vert_flag_enable(bm, v_curr, VERT_IN_FACE);
STACK_PUSH(edges, e_new);
STACK_PUSH(loops, l_curr);
STACK_PUSH(verts, v_curr);
}
v_curr = v_next;
is_del_v_curr = is_del_v_next;
} while ((l_curr = l_next) != l_first);
#undef LOOP_MAP_VERT_INIT
}
finally: {
uint i;
for (i = 0; i < STACK_SIZE(verts); i++) {
BMO_vert_flag_disable(bm, verts[i], VERT_IN_FACE);
}
}
if (STACK_SIZE(edges) >= 3) {
BMFace *f_new = BM_face_exists(verts, STACK_SIZE(verts));
if (f_new) {
return f_new;
}
f_new = BM_face_create(bm, verts, edges, STACK_SIZE(edges), f, BM_CREATE_NOP);
BLI_assert(f_new != f);
if (f_new) {
uint i = 0;
BMLoop *l_iter, *l_first;
l_iter = l_first = BM_FACE_FIRST_LOOP(f_new);
do {
BM_elem_attrs_copy(bm, loops[i], l_iter);
} while ((void)i++, (l_iter = l_iter->next) != l_first);
*r_created = true;
return f_new;
}
}
return nullptr;
}
/**
* \note with 'targetmap', multiple 'keys' are currently supported,
* though no callers should be using.
* (because slot maps currently use GHash without the GHASH_FLAG_ALLOW_DUPES flag set)
*/
void bmo_weld_verts_exec(BMesh *bm, BMOperator *op)
{
BMIter iter, liter;
BMVert *v;
BMEdge *e;
BMLoop *l;
BMFace *f;
BMOpSlot *slot_targetmap = BMO_slot_get(op->slots_in, "targetmap");
const bool use_centroid = BMO_slot_bool_get(op->slots_in, "use_centroid");
const bool average_vert_data = BMO_slot_bool_get(op->slots_in, "average_vert_data") ||
use_centroid;
/* Maintain selection history. */
const bool has_selected = !bm->selected.is_empty();
const bool use_targetmap_all = has_selected;
Map<void *, void *> targetmap_all;
/* Used when use_centroid or average_vert_data is true. */
MultiValueMap<BMVert *, BMVert *> clusters;
/* Mark merge verts for deletion. */
BM_ITER_MESH (v, &iter, bm, BM_VERTS_OF_MESH) {
BMVert *v_dst = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v));
if (v_dst == nullptr) {
continue;
}
BMO_vert_flag_enable(bm, v, ELE_DEL);
/* Merge the vertex flags, else we get randomly selected/unselected verts. */
BM_elem_flag_merge_ex(v, v_dst, BM_ELEM_HIDDEN);
if (use_targetmap_all) {
BLI_assert(v != v_dst);
targetmap_all.add(v, v_dst);
}
/* Group vertices by their survivor. */
if (average_vert_data && LIKELY(v_dst != v)) {
clusters.add(v_dst, v);
}
}
if (use_centroid) {
/* Compute centroid for each survivor. */
for (const auto &item : clusters.items()) {
BMVert *v_dst = item.key;
Span<BMVert *> cluster = item.value;
float centroid[3];
copy_v3_v3(centroid, v_dst->co);
int count = 1; /* Include `v_dst`. */
for (BMVert *v_duplicate : cluster) {
add_v3_v3(centroid, v_duplicate->co);
count++;
}
mul_v3_fl(centroid, 1.0f / float(count));
copy_v3_v3(v_dst->co, centroid);
}
}
if (average_vert_data) {
for (const auto &item : clusters.items()) {
BMVert *v_dst = item.key;
Span<BMVert *> merged_verts = item.value;
Array<const void *> src_blocks(merged_verts.size() + 1);
src_blocks[0] = v_dst->head.data;
for (const int i : merged_verts.index_range()) {
src_blocks[i + 1] = merged_verts[i]->head.data;
}
CustomData_bmesh_interp(
&bm->vdata, src_blocks.data(), nullptr, src_blocks.size(), v_dst->head.data);
}
}
/* Check if any faces are getting their own corners merged
* together, split face if so. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
remdoubles_splitface(f, bm, op, slot_targetmap);
}
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BMVert *v1, *v2;
const bool is_del_v1 = BMO_vert_flag_test_bool(bm, (v1 = e->v1), ELE_DEL);
const bool is_del_v2 = BMO_vert_flag_test_bool(bm, (v2 = e->v2), ELE_DEL);
if (is_del_v1 || is_del_v2) {
if (is_del_v1) {
v1 = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v1));
}
if (is_del_v2) {
v2 = static_cast<BMVert *>(BMO_slot_map_elem_get(slot_targetmap, v2));
}
if (v1 == v2) {
BMO_edge_flag_enable(bm, e, EDGE_COL);
}
else {
/* Always merge flags, even for edges we already created. */
BMEdge *e_new = BM_edge_exists(v1, v2);
if (e_new == nullptr) {
e_new = BM_edge_create(bm, v1, v2, e, BM_CREATE_NOP);
}
BM_elem_flag_merge_ex(e_new, e, BM_ELEM_HIDDEN);
if (use_targetmap_all) {
BLI_assert(e != e_new);
targetmap_all.add(e, e_new);
}
}
BMO_edge_flag_enable(bm, e, ELE_DEL);
}
}
/* Faces get "modified" by creating new faces here, then at the
* end the old faces are deleted. */
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
bool vert_delete = false;
int edge_collapse = 0;
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (BMO_vert_flag_test(bm, l->v, ELE_DEL)) {
vert_delete = true;
}
if (BMO_edge_flag_test(bm, l->e, EDGE_COL)) {
edge_collapse++;
}
}
if (vert_delete) {
bool use_in_place = false;
BMFace *f_new = nullptr;
BMO_face_flag_enable(bm, f, ELE_DEL);
if (f->len - edge_collapse >= 3) {
bool created;
f_new = remdoubles_createface(bm, f, slot_targetmap, &created);
/* Do this so we don't need to return a list of created faces. */
if (f_new) {
if (created) {
bmesh_face_swap_data(f_new, f);
if (bm->use_toolflags) {
std::swap((reinterpret_cast<BMFace_OFlag *>(f))->oflags,
(reinterpret_cast<BMFace_OFlag *>(f_new))->oflags);
}
BMO_face_flag_disable(bm, f, ELE_DEL);
BM_face_kill(bm, f_new);
use_in_place = true;
}
else {
BM_elem_flag_merge_ex(f_new, f, BM_ELEM_HIDDEN);
}
}
}
if ((use_in_place == false) && (f_new != nullptr)) {
BLI_assert(f != f_new);
if (use_targetmap_all) {
targetmap_all.add(f, f_new);
}
if (bm->act_face && (f == bm->act_face)) {
bm->act_face = f_new;
}
}
}
}
if (has_selected) {
BM_select_history_merge_from_targetmap(
bm, &targetmap_all, &targetmap_all, &targetmap_all, true);
}
BMO_mesh_delete_oflag_context(bm, ELE_DEL, DEL_ONLYTAGGED, nullptr);
}
#define VERT_KEEP 8
#define EDGE_MARK 1
void bmo_pointmerge_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v, *vert_target;
BMLoop *l, *l_first = nullptr;
float fac;
int i, tot;
vert_target = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_target")));
tot = BM_vert_face_count(vert_target);
if (!tot) {
return;
}
fac = 1.0f / tot;
BM_ITER_ELEM (l, &iter, vert_target, BM_LOOPS_OF_VERT) {
if (l_first == nullptr) {
l_first = l;
}
for (i = 0; i < bm->ldata.totlayer; i++) {
if (CustomData_layer_has_math(&bm->ldata, i)) {
const int type = bm->ldata.layers[i].type;
const int offset = bm->ldata.layers[i].offset;
void *e1, *e2;
e1 = BM_ELEM_CD_GET_VOID_P(l_first, offset);
e2 = BM_ELEM_CD_GET_VOID_P(l, offset);
CustomData_data_multiply(eCustomDataType(type), e2, fac);
if (l != l_first) {
CustomData_data_add(eCustomDataType(type), e1, e2);
}
}
}
}
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
if (l == l_first) {
continue;
}
CustomData_bmesh_copy_block(bm->ldata, l_first->head.data, &l->head.data);
}
}
}
void bmo_average_vert_facedata_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMIter iter;
BMVert *v;
BMLoop *l;
CDBlockBytes min, max;
int i;
for (i = 0; i < bm->ldata.totlayer; i++) {
const int type = bm->ldata.layers[i].type;
const int offset = bm->ldata.layers[i].offset;
if (!CustomData_layer_has_math(&bm->ldata, i)) {
continue;
}
CustomData_data_initminmax(eCustomDataType(type), &min, &max);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
void *block = BM_ELEM_CD_GET_VOID_P(l, offset);
CustomData_data_dominmax(eCustomDataType(type), block, &min, &max);
}
}
CustomData_data_multiply(eCustomDataType(type), &min, 0.5f);
CustomData_data_multiply(eCustomDataType(type), &max, 0.5f);
CustomData_data_add(eCustomDataType(type), &min, &max);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
BM_ITER_ELEM (l, &iter, v, BM_LOOPS_OF_VERT) {
void *block = BM_ELEM_CD_GET_VOID_P(l, offset);
CustomData_data_copy_value(eCustomDataType(type), &min, block);
}
}
}
}
void bmo_pointmerge_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOIter siter;
BMVert *v, *vert_target = nullptr;
float vec[3];
BMOpSlot *slot_targetmap;
BMO_slot_vec_get(op->slots_in, "merge_co", vec);
// BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
vert_target = static_cast<BMVert *>(
BMO_slot_buffer_get_single(BMO_slot_get(op->slots_in, "vert_target")));
const bool is_explicit_snap = vert_target != nullptr;
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (!vert_target) {
vert_target = v;
copy_v3_v3(vert_target->co, vec);
}
else if (v != vert_target) {
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v, vert_target);
}
}
if (!is_explicit_snap) {
BMO_slot_bool_set(weldop.slots_in, "average_vert_data", true);
}
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}
void bmo_collapse_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMWalker walker;
BMIter iter;
BMEdge *e;
BLI_Stack *edge_stack;
BMOpSlot *slot_targetmap;
if (BMO_slot_bool_get(op->slots_in, "uvs")) {
BMO_op_callf(bm, op->flag, "collapse_uvs edges=%s", op, "edges");
}
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, EDGE_MARK);
BMW_init(&walker,
bm,
BMW_VERT_SHELL,
BMW_MASK_NOP,
EDGE_MARK,
BMW_MASK_NOP,
BMW_FLAG_NOP, /* No need to use #BMW_FLAG_TEST_HIDDEN, already marked data. */
BMW_NIL_LAY,
BMW_DELIMIT_NONE);
edge_stack = BLI_stack_new(sizeof(BMEdge *), __func__);
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
float center[3];
int count = 0;
BMVert *v_tar;
zero_v3(center);
if (!BMO_edge_flag_test(bm, e, EDGE_MARK)) {
continue;
}
BLI_assert(BLI_stack_is_empty(edge_stack));
for (e = static_cast<BMEdge *>(BMW_begin(&walker, e->v1)); e;
e = static_cast<BMEdge *>(BMW_step(&walker)))
{
BLI_stack_push(edge_stack, &e);
add_v3_v3(center, e->v1->co);
add_v3_v3(center, e->v2->co);
count += 2;
/* Prevent adding to `slot_targetmap` multiple times. */
BM_elem_flag_disable(e->v1, BM_ELEM_TAG);
BM_elem_flag_disable(e->v2, BM_ELEM_TAG);
}
if (!BLI_stack_is_empty(edge_stack)) {
mul_v3_fl(center, 1.0f / count);
/* Snap edges to a point. for initial testing purposes anyway. */
e = *static_cast<BMEdge **>(BLI_stack_peek(edge_stack));
v_tar = e->v1;
while (!BLI_stack_is_empty(edge_stack)) {
uint j;
BLI_stack_pop(edge_stack, &e);
for (j = 0; j < 2; j++) {
BMVert *v_src = *((&e->v1) + j);
copy_v3_v3(v_src->co, center);
if ((v_src != v_tar) && !BM_elem_flag_test(v_src, BM_ELEM_TAG)) {
BM_elem_flag_enable(v_src, BM_ELEM_TAG);
BMO_slot_map_elem_insert(&weldop, slot_targetmap, v_src, v_tar);
}
}
}
}
}
BLI_stack_free(edge_stack);
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
BMW_end(&walker);
}
/** UV collapse function. */
static void bmo_collapsecon_do_layer(BMesh *bm, const int layer, const short oflag)
{
const int type = bm->ldata.layers[layer].type;
const int offset = bm->ldata.layers[layer].offset;
BMIter iter, liter;
BMFace *f;
BMLoop *l, *l2;
BMWalker walker;
BLI_Stack *block_stack;
CDBlockBytes min, max;
BMW_init(&walker,
bm,
BMW_LOOPDATA_ISLAND,
BMW_MASK_NOP,
oflag,
BMW_MASK_NOP,
BMW_FLAG_NOP, /* No need to use #BMW_FLAG_TEST_HIDDEN, already marked data. */
layer,
BMW_DELIMIT_NONE);
block_stack = BLI_stack_new(sizeof(void *), __func__);
BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) {
BM_ITER_ELEM (l, &liter, f, BM_LOOPS_OF_FACE) {
if (BMO_edge_flag_test(bm, l->e, oflag)) {
/* Walk. */
BLI_assert(BLI_stack_is_empty(block_stack));
CustomData_data_initminmax(eCustomDataType(type), &min, &max);
for (l2 = static_cast<BMLoop *>(BMW_begin(&walker, l)); l2;
l2 = static_cast<BMLoop *>(BMW_step(&walker)))
{
void *block = BM_ELEM_CD_GET_VOID_P(l2, offset);
CustomData_data_dominmax(eCustomDataType(type), block, &min, &max);
BLI_stack_push(block_stack, &block);
}
if (!BLI_stack_is_empty(block_stack)) {
CustomData_data_multiply(eCustomDataType(type), &min, 0.5f);
CustomData_data_multiply(eCustomDataType(type), &max, 0.5f);
CustomData_data_add(eCustomDataType(type), &min, &max);
/* Snap custom-data (UV, vertex-colors) points to their centroid. */
while (!BLI_stack_is_empty(block_stack)) {
void *block;
BLI_stack_pop(block_stack, &block);
CustomData_data_copy_value(eCustomDataType(type), &min, block);
}
}
}
}
}
BLI_stack_free(block_stack);
BMW_end(&walker);
}
void bmo_collapse_uvs_exec(BMesh *bm, BMOperator *op)
{
const short oflag = EDGE_MARK;
int i;
/* Check flags don't change once set. */
#ifndef NDEBUG
int tot_test;
#endif
if (!CustomData_has_math(&bm->ldata)) {
return;
}
BMO_slot_buffer_flag_enable(bm, op->slots_in, "edges", BM_EDGE, oflag);
#ifndef NDEBUG
tot_test = BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, oflag, true);
#endif
for (i = 0; i < bm->ldata.totlayer; i++) {
if (CustomData_layer_has_math(&bm->ldata, i)) {
bmo_collapsecon_do_layer(bm, i, oflag);
}
}
#ifndef NDEBUG
BLI_assert(tot_test == BM_iter_mesh_count_flag(BM_EDGES_OF_MESH, bm, EDGE_MARK, true));
#endif
}
/**
* \return a `verts_len` aligned array of indices.
* Index values:
* - `-1`: Not a duplicate, others may use as a target.
* - `<itself>`: Not a duplicate (marked to be kept), others may use as a target.
* - `0..verts_len`: The target double.
*/
static int *bmesh_find_doubles_by_distance_impl(BMesh *bm,
BMVert *const *verts,
const int verts_len,
const float dist,
const bool has_keep_vert)
{
int *duplicates = MEM_new_array_uninitialized<int>(verts_len, __func__);
bool found_duplicates = false;
bool has_self_index = false;
KDTree<float3> *tree = kdtree_new<float3>(verts_len);
for (int i = 0; i < verts_len; i++) {
kdtree_insert<float3>(tree, i, verts[i]->co);
if (has_keep_vert && BMO_vert_flag_test(bm, verts[i], VERT_KEEP)) {
duplicates[i] = i;
has_self_index = true;
}
else {
duplicates[i] = -1;
}
}
kdtree_balance<float3>(tree);
/* Given a cluster of duplicates, pick the index to keep. */
auto deduplicate_target_calc_fn = [&verts](const int *cluster, const int cluster_num) -> int {
if (cluster_num == 2) {
/* Special case, no use in calculating centroid.
* Use the lowest index for stability. */
return (cluster[0] < cluster[1]) ? 0 : 1;
}
BLI_assert(cluster_num > 2);
float3 centroid{0.0f};
for (int i = 0; i < cluster_num; i++) {
centroid += float3(verts[cluster[i]]->co);
}
centroid /= float(cluster_num);
/* Now pick the most "central" index (with lowest index as a tie breaker). */
const int cluster_end = cluster_num - 1;
/* Assign `i_best` from the last index as this is the index where the search originated
* so it's most likely to be the best. */
int i_best = cluster_end;
float dist_sq_best = len_squared_v3v3(centroid, verts[cluster[i_best]]->co);
for (int i = 0; i < cluster_end; i++) {
const float dist_sq_test = len_squared_v3v3(centroid, verts[cluster[i]]->co);
if (dist_sq_test > dist_sq_best) {
continue;
}
if (dist_sq_test == dist_sq_best) {
if (cluster[i] > cluster[i_best]) {
continue;
}
}
i_best = i;
dist_sq_best = dist_sq_test;
}
return i_best;
};
found_duplicates = kdtree_calc_duplicates_cb<float3>(
tree, dist, duplicates, has_self_index, deduplicate_target_calc_fn) != 0;
kdtree_free<float3>(tree);
if (!found_duplicates) {
MEM_delete(duplicates);
duplicates = nullptr;
}
return duplicates;
}
/** \copydoc #bmesh_find_doubles_by_distance_connected_impl. */
static int *bmesh_find_doubles_by_distance_connected_impl(BMesh *bm,
BMVert *const *verts,
const int verts_len,
const float dist,
const bool has_keep_vert)
{
int *duplicates = MEM_new_array_uninitialized<int>(verts_len, __func__);
bool found_duplicates = false;
Stack<int> vert_stack;
Map<BMVert *, int> vert_to_index_map;
for (int i = 0; i < verts_len; i++) {
if (has_keep_vert && BMO_vert_flag_test(bm, verts[i], VERT_KEEP)) {
duplicates[i] = i;
}
else {
duplicates[i] = -1;
}
vert_to_index_map.add(verts[i], i);
}
const float dist_sq = math::square(dist);
for (int i = 0; i < verts_len; i++) {
if (!ELEM(duplicates[i], -1, i)) {
continue;
}
const float *co_check = verts[i]->co;
BLI_assert(vert_stack.is_empty());
int i_check = i;
do {
BMVert *v_check = verts[i_check];
if (v_check->e) {
BMEdge *e_iter, *e_first;
e_first = e_iter = v_check->e;
do {
/* Edge stepping. */
BMVert *v_other = BM_edge_other_vert(e_iter, v_check);
if (len_squared_v3v3(v_other->co, co_check) < dist_sq) {
const int i_other = vert_to_index_map.lookup_default(v_other, -1);
if ((i_other != -1) && (duplicates[i_other] == -1)) {
duplicates[i_other] = i;
vert_stack.push(i_other);
found_duplicates = true;
}
}
/* Face stepping. */
if (e_iter->l) {
BMLoop *l_radial_iter;
l_radial_iter = e_iter->l;
do {
if (l_radial_iter->v != v_check) {
/* This face will be met from another edge. */
continue;
}
if (l_radial_iter->f->len <= 3) {
/* Edge iteration handles triangles. */
continue;
}
/* Loop over all vertices not connected to edges attached to `v_check`.
* For a 4 sided face, this will only check 1 vertex. */
BMLoop *l_iter = l_radial_iter->next->next;
BMLoop *l_end = l_radial_iter->prev;
do {
BMVert *v_other = l_iter->v;
if (len_squared_v3v3(v_other->co, co_check) < dist_sq) {
const int i_other = vert_to_index_map.lookup_default(v_other, -1);
if ((i_other != -1) && (duplicates[i_other] == -1)) {
duplicates[i_other] = i;
vert_stack.push(i_other);
found_duplicates = true;
}
}
} while ((l_iter = l_iter->next) != l_end);
} while ((l_radial_iter = l_radial_iter->radial_next) != e_iter->l);
}
} while ((e_iter = BM_DISK_EDGE_NEXT(e_iter, v_check)) != e_first);
}
} while ((i_check = vert_stack.is_empty() ? -1 : vert_stack.pop()) != -1);
}
if (!found_duplicates) {
MEM_delete(duplicates);
duplicates = nullptr;
}
return duplicates;
}
static void bmesh_find_doubles_common(BMesh *bm,
BMOperator *op,
BMOperator *optarget,
BMOpSlot *optarget_slot)
{
const bool use_connected = BMO_slot_bool_get(op->slots_in, "use_connected");
const BMOpSlot *slot_verts = BMO_slot_get(op->slots_in, "verts");
BMVert *const *verts = reinterpret_cast<BMVert **>(slot_verts->data.buf);
const int verts_len = slot_verts->len;
bool has_keep_vert = false;
const float dist = BMO_slot_float_get(op->slots_in, "dist");
/* Test whether keep_verts arg exists and is non-empty. */
if (BMO_slot_exists(op->slots_in, "keep_verts")) {
BMOIter oiter;
has_keep_vert = BMO_iter_new(&oiter, op->slots_in, "keep_verts", BM_VERT) != nullptr;
}
/* Flag keep_verts. */
if (has_keep_vert) {
BMO_slot_buffer_flag_enable(bm, op->slots_in, "keep_verts", BM_VERT, VERT_KEEP);
}
int *duplicates = nullptr; /* `verts_len` aligned index array. */
if (use_connected) {
duplicates = bmesh_find_doubles_by_distance_connected_impl(
bm, verts, verts_len, dist, has_keep_vert);
}
else {
duplicates = bmesh_find_doubles_by_distance_impl(bm, verts, verts_len, dist, has_keep_vert);
}
/* Null when no duplicates were found. */
if (duplicates) {
for (int i = 0; i < verts_len; i++) {
BMVert *v_check = verts[i];
if (duplicates[i] == -1) {
/* NOP (others can use as target). */
}
else if (duplicates[i] == i) {
/* Keep (others can use as target). */
}
else {
BMVert *v_other = verts[duplicates[i]];
BLI_assert(ELEM(duplicates[duplicates[i]], -1, duplicates[i]));
BMO_slot_map_elem_insert(optarget, optarget_slot, v_check, v_other);
}
}
MEM_delete(duplicates);
}
}
void bmo_remove_doubles_exec(BMesh *bm, BMOperator *op)
{
BMOperator weldop;
BMOpSlot *slot_targetmap;
BMO_op_init(bm, &weldop, op->flag, "weld_verts");
slot_targetmap = BMO_slot_get(weldop.slots_in, "targetmap");
bmesh_find_doubles_common(bm, op, &weldop, slot_targetmap);
BMO_op_exec(bm, &weldop);
BMO_op_finish(bm, &weldop);
}
void bmo_find_doubles_exec(BMesh *bm, BMOperator *op)
{
BMOpSlot *slot_targetmap_out;
slot_targetmap_out = BMO_slot_get(op->slots_out, "targetmap.out");
bmesh_find_doubles_common(bm, op, op, slot_targetmap_out);
}
} // namespace blender

View File

@@ -0,0 +1,271 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Rotate edges topology that share two faces.
*/
#include <cfloat>
#include "MEM_guardedalloc.h"
#include "BLI_heap.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
#define EDGE_OUT 1
#define FACE_MARK 1
/**
* Rotate edges where every edge has its own faces (we can rotate in any order).
*/
static void bm_rotate_edges_simple(BMesh *bm,
BMOperator *op,
const short check_flag,
const bool use_ccw)
{
BMOIter siter;
BMEdge *e;
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
/* This ends up being called twice, could add option to not to call check in
* #BM_edge_rotate to get some extra speed. */
if (BM_edge_rotate_check(e)) {
BMEdge *e_rotate = BM_edge_rotate(bm, e, use_ccw, check_flag);
if (e_rotate != nullptr) {
BMO_edge_flag_enable(bm, e_rotate, EDGE_OUT);
}
}
}
}
/**
* Edge length is just a way of ordering that's independent of order in the edges argument,
* we could use some other method since ideally all edges will be rotated,
* this just happens to be simple to calculate.
*/
static float bm_edge_calc_rotate_cost(const BMEdge *e)
{
return -BM_edge_calc_length_squared(e);
}
/**
* Check if this edge is a boundary: Are more than one of the connected faces edges rotating too?
*/
static bool bm_edge_rotate_is_boundary(const BMEdge *e)
{
/* Number of adjacent shared faces. */
int count = 0;
BMLoop *l_radial_iter = e->l;
do {
/* Skip this edge. */
BMLoop *l_iter = l_radial_iter->next;
do {
BMEdge *e_iter = l_iter->e;
const int e_iter_index = BM_elem_index_get(e_iter);
if (e_iter_index != -1) {
if (count == 1) {
return false;
}
count += 1;
break;
}
} while ((l_iter = l_iter->next) != l_radial_iter);
} while ((l_radial_iter = l_radial_iter->radial_next) != e->l);
return true;
}
/**
* Rotate edges where edges share faces,
* edges which could not rotate need to be re-considered after neighbors are rotated.
*/
static void bm_rotate_edges_shared(
BMesh *bm, BMOperator *op, short check_flag, const bool use_ccw, const int edges_len)
{
Heap *heap = BLI_heap_new_ex(edges_len);
HeapNode **eheap_table = MEM_new_array_uninitialized<HeapNode *>(edges_len, __func__);
BMEdge **edges = reinterpret_cast<BMEdge **>(
BMO_SLOT_AS_BUFFER(BMO_slot_get(op->slots_in, "edges")));
int edges_len_rotate = 0;
/* Never read edges with this value in the `eheap_table` since they have been freed. */
HeapNode *edge_free_id = reinterpret_cast<HeapNode *>(uintptr_t(-1));
{
BMIter iter;
BMEdge *e;
BM_ITER_MESH (e, &iter, bm, BM_EDGES_OF_MESH) {
BM_elem_index_set(e, -1); /* set_dirty! */
}
bm->elem_index_dirty |= BM_EDGE;
}
for (int i = 0; i < edges_len; i++) {
BMEdge *e = edges[i];
BM_elem_index_set(e, BM_edge_is_manifold(e) ? i : -1); /* set_dirty! */
eheap_table[i] = nullptr;
}
/* First operate on boundary edges, this is often all that's needed,
* regions that have no boundaries are handles after. */
enum {
PASS_TYPE_BOUNDARY = 0,
PASS_TYPE_ALL = 1,
PASS_TYPE_DONE = 2,
};
uint pass_type = PASS_TYPE_BOUNDARY;
while ((pass_type != PASS_TYPE_DONE) && (edges_len_rotate != edges_len)) {
BLI_assert(BLI_heap_is_empty(heap));
{
for (int i = 0; i < edges_len; i++) {
if (eheap_table[i] == edge_free_id) {
/* `e` is freed. */
continue;
}
BMEdge *e = edges[i];
BLI_assert(eheap_table[i] == nullptr);
bool ok = (BM_elem_index_get(e) != -1) && BM_edge_rotate_check(e);
if (ok) {
if (pass_type == PASS_TYPE_BOUNDARY) {
ok = bm_edge_rotate_is_boundary(e);
}
}
if (ok) {
float cost = bm_edge_calc_rotate_cost(e);
if (pass_type == PASS_TYPE_BOUNDARY) {
/* Trick to ensure once started,
* non boundaries are handled before other boundary edges.
* This means the first longest boundary defines the starting point which is rotated
* until all its connected edges are exhausted
* and the next boundary is popped off the heap.
*
* Without this we may rotate from different starting points and meet in the middle
* with obviously uneven topology.
*
* Move from negative to positive value,
* inverting so large values are still handled first.
*/
cost = cost != 0.0f ? -1.0f / cost : FLT_MAX;
}
eheap_table[i] = BLI_heap_insert(heap, cost, e);
}
}
}
if (BLI_heap_is_empty(heap)) {
pass_type += 1;
continue;
}
const int edges_len_rotate_prev = edges_len_rotate;
while (!BLI_heap_is_empty(heap)) {
BMEdge *e_best = static_cast<BMEdge *>(BLI_heap_pop_min(heap));
const int e_best_index = BM_elem_index_get(e_best);
eheap_table[e_best_index] = nullptr;
/* No problem if this fails, re-evaluate if faces connected to this edge are touched. */
if (BM_edge_rotate_check(e_best)) {
BMEdge *e_rotate = BM_edge_rotate(bm, e_best, use_ccw, check_flag);
if (e_rotate != nullptr) {
BMO_edge_flag_enable(bm, e_rotate, EDGE_OUT);
/* invalidate so we don't try touch this again. */
BM_elem_index_set(e_rotate, -1); /* set_dirty! */
/* If rotate succeeds, the edge has been freed. */
eheap_table[e_best_index] = edge_free_id;
edges_len_rotate += 1;
/* NOTE: we could validate all edges which have not been rotated
* (not just previously degenerate edges).
* However there is no real need -
* they can be left until they're popped off the queue. */
/* We don't know the exact topology after rotating the edge,
* so loop over all faces attached to the new edge,
* typically this will only be two faces. */
BMLoop *l_radial_iter = e_rotate->l;
do {
/* Skip this edge. */
BMLoop *l_iter = l_radial_iter->next;
do {
BMEdge *e_iter = l_iter->e;
const int e_iter_index = BM_elem_index_get(e_iter);
if ((e_iter_index != -1) && (eheap_table[e_iter_index] == nullptr)) {
/* Once freed, they cannot be accessed via connected geometry. */
BLI_assert(eheap_table[e_iter_index] != edge_free_id);
if (BM_edge_rotate_check(e_iter)) {
/* Previously degenerate, now valid. */
float cost = bm_edge_calc_rotate_cost(e_iter);
eheap_table[e_iter_index] = BLI_heap_insert(heap, cost, e_iter);
}
}
} while ((l_iter = l_iter->next) != l_radial_iter);
} while ((l_radial_iter = l_radial_iter->radial_next) != e_rotate->l);
}
}
}
/* If no actions were taken, move onto the next pass. */
if (edges_len_rotate == edges_len_rotate_prev) {
pass_type += 1;
continue;
}
}
BLI_heap_free(heap, nullptr);
MEM_delete(eheap_table);
}
void bmo_rotate_edges_exec(BMesh *bm, BMOperator *op)
{
BMOIter siter;
BMEdge *e;
const int edges_len = BMO_slot_buffer_len(op->slots_in, "edges");
const bool use_ccw = BMO_slot_bool_get(op->slots_in, "use_ccw");
const bool is_single = (edges_len == 1);
short check_flag = is_single ? BM_EDGEROT_CHECK_EXISTS :
BM_EDGEROT_CHECK_EXISTS | BM_EDGEROT_CHECK_DEGENERATE;
bool is_simple = true;
if (is_single == false) {
BMO_ITER (e, &siter, op->slots_in, "edges", BM_EDGE) {
BMFace *f_pair[2];
if (BM_edge_face_pair(e, &f_pair[0], &f_pair[1])) {
for (uint i = 0; i < ARRAY_SIZE(f_pair); i += 1) {
if (BMO_face_flag_test(bm, f_pair[i], FACE_MARK)) {
is_simple = false;
break;
}
BMO_face_flag_enable(bm, f_pair[i], FACE_MARK);
}
if (is_simple == false) {
break;
}
}
}
}
if (is_simple) {
bm_rotate_edges_simple(bm, op, check_flag, use_ccw);
}
else {
bm_rotate_edges_shared(bm, op, check_flag, use_ccw, edges_len);
}
BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "edges.out", BM_EDGE, EDGE_OUT);
}
} // namespace blender

View File

@@ -0,0 +1,500 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bmesh
*
* Advanced smoothing.
*/
#include "MEM_guardedalloc.h"
#include "BLI_math_geom.h"
#include "BLI_math_vector.h"
#include "eigen_capi.h"
#include "bmesh.hh"
#include "intern/bmesh_operators_private.hh" /* own include */
namespace blender {
// #define SMOOTH_LAPLACIAN_AREA_FACTOR 4.0f /* UNUSED */
// #define SMOOTH_LAPLACIAN_EDGE_FACTOR 2.0f /* UNUSED */
#define SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE 1.8f
#define SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE 0.15f
struct BLaplacianSystem {
float *eweights; /* Length weights per Edge. */
float (*fweights)[3]; /* Cotangent weights per loop. */
float *ring_areas; /* Total area per ring. */
float *vlengths; /* Total sum of lengths(edges) per vertex. */
float *vweights; /* Total sum of weights per vertex. */
int numEdges; /* Number of edges. */
int numLoops; /* Number of loops. */
int numVerts; /* Number of verts. */
bool *zerola; /* Is zero area or length. */
/* Pointers to data. */
BMesh *bm;
BMOperator *op;
LinearSolver *context;
/* Data. */
float min_area;
};
using LaplacianSystem = BLaplacianSystem;
static bool vert_is_boundary(BMVert *v);
static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts);
static void init_laplacian_matrix(LaplacianSystem *sys);
static void delete_laplacian_system(LaplacianSystem *sys);
static void delete_void_pointer(void *data);
static void fill_laplacian_matrix(LaplacianSystem *sys);
static void memset_laplacian_system(LaplacianSystem *sys, int val);
static void validate_solution(
LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume);
static void volume_preservation(
BMOperator *op, float vini, float vend, int usex, int usey, int usez);
static void delete_void_pointer(void *data)
{
if (data) {
MEM_delete_void(data);
}
}
static void delete_laplacian_system(LaplacianSystem *sys)
{
delete_void_pointer(sys->eweights);
delete_void_pointer(sys->fweights);
delete_void_pointer(sys->ring_areas);
delete_void_pointer(sys->vlengths);
delete_void_pointer(sys->vweights);
delete_void_pointer(sys->zerola);
if (sys->context) {
EIG_linear_solver_delete(sys->context);
}
sys->bm = nullptr;
sys->op = nullptr;
MEM_delete(sys);
}
static void memset_laplacian_system(LaplacianSystem *sys, int val)
{
memset(sys->eweights, val, sizeof(float) * sys->numEdges);
memset(sys->fweights, val, sizeof(float[3]) * sys->numLoops);
memset(sys->ring_areas, val, sizeof(float) * sys->numVerts);
memset(sys->vlengths, val, sizeof(float) * sys->numVerts);
memset(sys->vweights, val, sizeof(float) * sys->numVerts);
memset(sys->zerola, val, sizeof(bool) * sys->numVerts);
}
static LaplacianSystem *init_laplacian_system(int a_numEdges, int a_numLoops, int a_numVerts)
{
LaplacianSystem *sys;
sys = MEM_new_zeroed<LaplacianSystem>("ModLaplSmoothSystem");
sys->numEdges = a_numEdges;
sys->numLoops = a_numLoops;
sys->numVerts = a_numVerts;
sys->eweights = MEM_new_array_zeroed<float>(sys->numEdges, "ModLaplSmoothEWeight");
if (!sys->eweights) {
delete_laplacian_system(sys);
return nullptr;
}
sys->fweights = MEM_new_array_zeroed<float[3]>(sys->numLoops, "ModLaplSmoothFWeight");
if (!sys->fweights) {
delete_laplacian_system(sys);
return nullptr;
}
sys->ring_areas = MEM_new_array_zeroed<float>(sys->numVerts, "ModLaplSmoothRingAreas");
if (!sys->ring_areas) {
delete_laplacian_system(sys);
return nullptr;
}
sys->vlengths = MEM_new_array_zeroed<float>(sys->numVerts, "ModLaplSmoothVlengths");
if (!sys->vlengths) {
delete_laplacian_system(sys);
return nullptr;
}
sys->vweights = MEM_new_array_zeroed<float>(sys->numVerts, "ModLaplSmoothVweights");
if (!sys->vweights) {
delete_laplacian_system(sys);
return nullptr;
}
sys->zerola = MEM_new_array_zeroed<bool>(sys->numVerts, "ModLaplSmoothZeloa");
if (!sys->zerola) {
delete_laplacian_system(sys);
return nullptr;
}
return sys;
}
/**
* Compute weight between vertex v_i and all your neighbors
* weight between v_i and v_neighbor
* <pre>
* Wij = cot(alpha) + cot(beta) / (4.0 * total area of all faces * sum all weight)
*
* v_i *
* / | \
* / | \
* v_beta* | * v_alpha
* \ | /
* \ | /
* * v_neighbor
* </pre>
*/
static void init_laplacian_matrix(LaplacianSystem *sys)
{
BMEdge *e;
BMFace *f;
BMIter eiter;
BMIter fiter;
uint i;
BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT) || !BM_edge_is_boundary(e)) {
continue;
}
const float *v1 = e->v1->co;
const float *v2 = e->v2->co;
const int idv1 = BM_elem_index_get(e->v1);
const int idv2 = BM_elem_index_get(e->v2);
float w1 = len_v3v3(v1, v2);
if (w1 > sys->min_area) {
w1 = 1.0f / w1;
sys->eweights[i] = w1;
sys->vlengths[idv1] += w1;
sys->vlengths[idv2] += w1;
}
else {
sys->zerola[idv1] = true;
sys->zerola[idv2] = true;
}
}
uint l_curr_index = 0;
BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
l_curr_index += f->len;
continue;
}
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter;
l_iter = l_first;
do {
const int vi_prev = BM_elem_index_get(l_iter->prev->v);
const int vi_curr = BM_elem_index_get(l_iter->v);
const int vi_next = BM_elem_index_get(l_iter->next->v);
const float *co_prev = l_iter->prev->v->co;
const float *co_curr = l_iter->v->co;
const float *co_next = l_iter->next->v->co;
const float areaf = area_tri_v3(co_prev, co_curr, co_next);
if (areaf < sys->min_area) {
sys->zerola[vi_curr] = true;
}
sys->ring_areas[vi_prev] += areaf;
sys->ring_areas[vi_curr] += areaf;
sys->ring_areas[vi_next] += areaf;
const float w1 = cotangent_tri_weight_v3(co_curr, co_next, co_prev) / 2.0f;
const float w2 = cotangent_tri_weight_v3(co_next, co_prev, co_curr) / 2.0f;
const float w3 = cotangent_tri_weight_v3(co_prev, co_curr, co_next) / 2.0f;
sys->fweights[l_curr_index][0] += w1;
sys->fweights[l_curr_index][1] += w2;
sys->fweights[l_curr_index][2] += w3;
sys->vweights[vi_prev] += w1 + w2;
sys->vweights[vi_curr] += w2 + w3;
sys->vweights[vi_next] += w1 + w3;
} while ((void)(l_curr_index += 1), (l_iter = l_iter->next) != l_first);
}
}
static void fill_laplacian_matrix(LaplacianSystem *sys)
{
BMEdge *e;
BMFace *f;
BMIter eiter;
BMIter fiter;
int i;
uint l_curr_index = 0;
BM_ITER_MESH (f, &fiter, sys->bm, BM_FACES_OF_MESH) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
l_curr_index += f->len;
continue;
}
BMLoop *l_first = BM_FACE_FIRST_LOOP(f);
BMLoop *l_iter = l_first;
int vi_prev = BM_elem_index_get(l_iter->prev->v);
int vi_curr = BM_elem_index_get(l_iter->v);
bool ok_prev = (sys->zerola[vi_prev] == false) && !vert_is_boundary(l_iter->prev->v);
bool ok_curr = (sys->zerola[vi_curr] == false) && !vert_is_boundary(l_iter->v);
do {
const int vi_next = BM_elem_index_get(l_iter->next->v);
const bool ok_next = (sys->zerola[vi_next] == false) && !vert_is_boundary(l_iter->next->v);
if (ok_prev) {
EIG_linear_solver_matrix_add(sys->context,
vi_prev,
vi_curr,
sys->fweights[l_curr_index][1] * sys->vweights[vi_prev]);
EIG_linear_solver_matrix_add(sys->context,
vi_prev,
vi_next,
sys->fweights[l_curr_index][0] * sys->vweights[vi_prev]);
}
if (ok_curr) {
EIG_linear_solver_matrix_add(sys->context,
vi_curr,
vi_next,
sys->fweights[l_curr_index][2] * sys->vweights[vi_curr]);
EIG_linear_solver_matrix_add(sys->context,
vi_curr,
vi_prev,
sys->fweights[l_curr_index][1] * sys->vweights[vi_curr]);
}
if (ok_next) {
EIG_linear_solver_matrix_add(sys->context,
vi_next,
vi_curr,
sys->fweights[l_curr_index][2] * sys->vweights[vi_next]);
EIG_linear_solver_matrix_add(sys->context,
vi_next,
vi_prev,
sys->fweights[l_curr_index][0] * sys->vweights[vi_next]);
}
vi_prev = vi_curr;
vi_curr = vi_next;
ok_prev = ok_curr;
ok_curr = ok_next;
} while ((void)(l_curr_index += 1), (l_iter = l_iter->next) != l_first);
}
BM_ITER_MESH_INDEX (e, &eiter, sys->bm, BM_EDGES_OF_MESH, i) {
if (BM_elem_flag_test(e, BM_ELEM_SELECT) || !BM_edge_is_boundary(e)) {
continue;
}
const uint idv1 = BM_elem_index_get(e->v1);
const uint idv2 = BM_elem_index_get(e->v2);
if (sys->zerola[idv1] == false && sys->zerola[idv2] == false) {
EIG_linear_solver_matrix_add(
sys->context, idv1, idv2, sys->eweights[i] * sys->vlengths[idv1]);
EIG_linear_solver_matrix_add(
sys->context, idv2, idv1, sys->eweights[i] * sys->vlengths[idv2]);
}
}
}
static bool vert_is_boundary(BMVert *v)
{
BMEdge *ed;
BMFace *f;
BMIter ei;
BMIter fi;
BM_ITER_ELEM (ed, &ei, v, BM_EDGES_OF_VERT) {
if (BM_edge_is_boundary(ed)) {
return true;
}
}
BM_ITER_ELEM (f, &fi, v, BM_FACES_OF_VERT) {
if (!BM_elem_flag_test(f, BM_ELEM_SELECT)) {
return true;
}
}
return false;
}
static void volume_preservation(
BMOperator *op, float vini, float vend, int usex, int usey, int usez)
{
float beta;
BMOIter siter;
BMVert *v;
if (vend != 0.0f) {
beta = pow(vini / vend, 1.0f / 3.0f);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
if (usex) {
v->co[0] *= beta;
}
if (usey) {
v->co[1] *= beta;
}
if (usez) {
v->co[2] *= beta;
}
}
}
}
static void validate_solution(
LaplacianSystem *sys, int usex, int usey, int usez, int preserve_volume)
{
int m_vertex_id;
float leni, lene;
float vini, vend;
float *vi1, *vi2, ve1[3], ve2[3];
uint idv1, idv2;
BMOIter siter;
BMVert *v;
BMEdge *e;
BMIter eiter;
BM_ITER_MESH (e, &eiter, sys->bm, BM_EDGES_OF_MESH) {
idv1 = BM_elem_index_get(e->v1);
idv2 = BM_elem_index_get(e->v2);
vi1 = e->v1->co;
vi2 = e->v2->co;
ve1[0] = EIG_linear_solver_variable_get(sys->context, 0, idv1);
ve1[1] = EIG_linear_solver_variable_get(sys->context, 1, idv1);
ve1[2] = EIG_linear_solver_variable_get(sys->context, 2, idv1);
ve2[0] = EIG_linear_solver_variable_get(sys->context, 0, idv2);
ve2[1] = EIG_linear_solver_variable_get(sys->context, 1, idv2);
ve2[2] = EIG_linear_solver_variable_get(sys->context, 2, idv2);
leni = len_v3v3(vi1, vi2);
lene = len_v3v3(ve1, ve2);
if (lene > leni * SMOOTH_LAPLACIAN_MAX_EDGE_PERCENTAGE ||
lene < leni * SMOOTH_LAPLACIAN_MIN_EDGE_PERCENTAGE)
{
sys->zerola[idv1] = true;
sys->zerola[idv2] = true;
}
}
if (preserve_volume) {
vini = BM_mesh_calc_volume(sys->bm, false);
}
BMO_ITER (v, &siter, sys->op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
if (sys->zerola[m_vertex_id] == false) {
if (usex) {
v->co[0] = EIG_linear_solver_variable_get(sys->context, 0, m_vertex_id);
}
if (usey) {
v->co[1] = EIG_linear_solver_variable_get(sys->context, 1, m_vertex_id);
}
if (usez) {
v->co[2] = EIG_linear_solver_variable_get(sys->context, 2, m_vertex_id);
}
}
}
if (preserve_volume) {
vend = BM_mesh_calc_volume(sys->bm, false);
volume_preservation(sys->op, vini, vend, usex, usey, usez);
}
}
void bmo_smooth_laplacian_vert_exec(BMesh *bm, BMOperator *op)
{
int i;
int m_vertex_id;
bool usex, usey, usez, preserve_volume;
float lambda_factor, lambda_border;
float w;
BMOIter siter;
BMVert *v;
LaplacianSystem *sys;
if (bm->totface == 0) {
return;
}
sys = init_laplacian_system(bm->totedge, bm->totloop, bm->totvert);
if (!sys) {
return;
}
sys->bm = bm;
sys->op = op;
memset_laplacian_system(sys, 0);
BM_mesh_elem_index_ensure(bm, BM_VERT);
lambda_factor = BMO_slot_float_get(op->slots_in, "lambda_factor");
lambda_border = BMO_slot_float_get(op->slots_in, "lambda_border");
sys->min_area = 0.00001f;
usex = BMO_slot_bool_get(op->slots_in, "use_x");
usey = BMO_slot_bool_get(op->slots_in, "use_y");
usez = BMO_slot_bool_get(op->slots_in, "use_z");
preserve_volume = BMO_slot_bool_get(op->slots_in, "preserve_volume");
sys->context = EIG_linear_least_squares_solver_new(bm->totvert, bm->totvert, 3);
for (i = 0; i < bm->totvert; i++) {
EIG_linear_solver_variable_lock(sys->context, i);
}
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
EIG_linear_solver_variable_unlock(sys->context, m_vertex_id);
EIG_linear_solver_variable_set(sys->context, 0, m_vertex_id, v->co[0]);
EIG_linear_solver_variable_set(sys->context, 1, m_vertex_id, v->co[1]);
EIG_linear_solver_variable_set(sys->context, 2, m_vertex_id, v->co[2]);
}
init_laplacian_matrix(sys);
BMO_ITER (v, &siter, op->slots_in, "verts", BM_VERT) {
m_vertex_id = BM_elem_index_get(v);
EIG_linear_solver_right_hand_side_add(sys->context, 0, m_vertex_id, v->co[0]);
EIG_linear_solver_right_hand_side_add(sys->context, 1, m_vertex_id, v->co[1]);
EIG_linear_solver_right_hand_side_add(sys->context, 2, m_vertex_id, v->co[2]);
i = m_vertex_id;
if ((sys->zerola[i] == false) &&
/* Non zero check is to account for vertices that aren't connected to a selected face.
* Without this wire edges become `nan`, see #89214. */
(sys->ring_areas[i] != 0.0f))
{
w = sys->vweights[i] * sys->ring_areas[i];
sys->vweights[i] = (w == 0.0f) ? 0.0f : -lambda_factor / (4.0f * w);
w = sys->vlengths[i];
sys->vlengths[i] = (w == 0.0f) ? 0.0f : -lambda_border * 2.0f / w;
if (!vert_is_boundary(v)) {
EIG_linear_solver_matrix_add(
sys->context, i, i, 1.0f + lambda_factor / (4.0f * sys->ring_areas[i]));
}
else {
EIG_linear_solver_matrix_add(sys->context, i, i, 1.0f + lambda_border * 2.0f);
}
}
else {
EIG_linear_solver_matrix_add(sys->context, i, i, 1.0f);
}
}
fill_laplacian_matrix(sys);
if (EIG_linear_solver_solve(sys->context)) {
validate_solution(sys, usex, usey, usez, preserve_volume);
}
delete_laplacian_system(sys);
}
} // namespace blender

Some files were not shown because too many files have changed in this diff Show More