Add Chromium-only Blender WebEngine parity work
This commit is contained in:
229
blender-5.2.0/source/blender/bmesh/CMakeLists.txt
Normal file
229
blender-5.2.0/source/blender/bmesh/CMakeLists.txt
Normal 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()
|
||||
212
blender-5.2.0/source/blender/bmesh/bmesh.hh
Normal file
212
blender-5.2.0/source/blender/bmesh/bmesh.hh
Normal 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
|
||||
735
blender-5.2.0/source/blender/bmesh/bmesh_class.hh
Normal file
735
blender-5.2.0/source/blender/bmesh/bmesh_class.hh
Normal 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
|
||||
27
blender-5.2.0/source/blender/bmesh/bmesh_tools.hh
Normal file
27
blender-5.2.0/source/blender/bmesh/bmesh_tools.hh
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
714
blender-5.2.0/source/blender/bmesh/intern/bmesh_construct.cc
Normal file
714
blender-5.2.0/source/blender/bmesh/intern/bmesh_construct.cc
Normal 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, ¶ms);
|
||||
|
||||
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
|
||||
190
blender-5.2.0/source/blender/bmesh/intern/bmesh_construct.hh
Normal file
190
blender-5.2.0/source/blender/bmesh/intern/bmesh_construct.hh
Normal 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
|
||||
3022
blender-5.2.0/source/blender/bmesh/intern/bmesh_core.cc
Normal file
3022
blender-5.2.0/source/blender/bmesh/intern/bmesh_core.cc
Normal file
File diff suppressed because it is too large
Load Diff
488
blender-5.2.0/source/blender/bmesh/intern/bmesh_core.hh
Normal file
488
blender-5.2.0/source/blender/bmesh/intern/bmesh_core.hh
Normal 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
|
||||
365
blender-5.2.0/source/blender/bmesh/intern/bmesh_delete.cc
Normal file
365
blender-5.2.0/source/blender/bmesh/intern/bmesh_delete.cc
Normal 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
|
||||
39
blender-5.2.0/source/blender/bmesh/intern/bmesh_delete.hh
Normal file
39
blender-5.2.0/source/blender/bmesh/intern/bmesh_delete.hh
Normal 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
|
||||
811
blender-5.2.0/source/blender/bmesh/intern/bmesh_edgeloop.cc
Normal file
811
blender-5.2.0/source/blender/bmesh/intern/bmesh_edgeloop.cc
Normal 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
|
||||
91
blender-5.2.0/source/blender/bmesh/intern/bmesh_edgeloop.hh
Normal file
91
blender-5.2.0/source/blender/bmesh/intern/bmesh_edgeloop.hh
Normal 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
|
||||
97
blender-5.2.0/source/blender/bmesh/intern/bmesh_error.hh
Normal file
97
blender-5.2.0/source/blender/bmesh/intern/bmesh_error.hh
Normal 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
|
||||
133
blender-5.2.0/source/blender/bmesh/intern/bmesh_inline.hh
Normal file
133
blender-5.2.0/source/blender/bmesh/intern/bmesh_inline.hh
Normal 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
|
||||
1333
blender-5.2.0/source/blender/bmesh/intern/bmesh_interp.cc
Normal file
1333
blender-5.2.0/source/blender/bmesh/intern/bmesh_interp.cc
Normal file
File diff suppressed because it is too large
Load Diff
160
blender-5.2.0/source/blender/bmesh/intern/bmesh_interp.hh
Normal file
160
blender-5.2.0/source/blender/bmesh/intern/bmesh_interp.hh
Normal 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
|
||||
661
blender-5.2.0/source/blender/bmesh/intern/bmesh_iterators.cc
Normal file
661
blender-5.2.0/source/blender/bmesh/intern/bmesh_iterators.cc
Normal 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
|
||||
295
blender-5.2.0/source/blender/bmesh/intern/bmesh_iterators.hh
Normal file
295
blender-5.2.0/source/blender/bmesh/intern/bmesh_iterators.hh
Normal 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))
|
||||
@@ -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
|
||||
890
blender-5.2.0/source/blender/bmesh/intern/bmesh_log.cc
Normal file
890
blender-5.2.0/source/blender/bmesh/intern/bmesh_log.cc
Normal 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
|
||||
205
blender-5.2.0/source/blender/bmesh/intern/bmesh_log.hh
Normal file
205
blender-5.2.0/source/blender/bmesh/intern/bmesh_log.hh
Normal 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
|
||||
1639
blender-5.2.0/source/blender/bmesh/intern/bmesh_marking.cc
Normal file
1639
blender-5.2.0/source/blender/bmesh/intern/bmesh_marking.cc
Normal file
File diff suppressed because it is too large
Load Diff
232
blender-5.2.0/source/blender/bmesh/intern/bmesh_marking.hh
Normal file
232
blender-5.2.0/source/blender/bmesh/intern/bmesh_marking.hh
Normal 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
|
||||
1368
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh.cc
Normal file
1368
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh.cc
Normal file
File diff suppressed because it is too large
Load Diff
219
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh.hh
Normal file
219
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh.hh
Normal 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
|
||||
2220
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_convert.cc
Normal file
2220
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_convert.cc
Normal file
File diff suppressed because it is too large
Load Diff
109
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_convert.hh
Normal file
109
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_convert.hh
Normal 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
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
2400
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_normals.cc
Normal file
2400
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_normals.cc
Normal file
File diff suppressed because it is too large
Load Diff
119
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_normals.hh
Normal file
119
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_normals.hh
Normal 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
|
||||
@@ -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 ¶ms,
|
||||
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 ¶ms,
|
||||
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 ¶ms,
|
||||
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
|
||||
@@ -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 ¶ms, 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 ¶ms, 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 ¶ms, Span<int> verts_group, int verts_group_count);
|
||||
|
||||
void BM_mesh_partial_destroy(BMPartialUpdate *bmpinfo) ATTR_NONNULL(1);
|
||||
|
||||
} // namespace blender
|
||||
@@ -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, ¶ms);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \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
|
||||
@@ -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
|
||||
251
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_validate.cc
Normal file
251
blender-5.2.0/source/blender/bmesh/intern/bmesh_mesh_validate.cc
Normal 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
|
||||
@@ -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
|
||||
903
blender-5.2.0/source/blender/bmesh/intern/bmesh_mods.cc
Normal file
903
blender-5.2.0/source/blender/bmesh/intern/bmesh_mods.cc
Normal 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
|
||||
285
blender-5.2.0/source/blender/bmesh/intern/bmesh_mods.hh
Normal file
285
blender-5.2.0/source/blender/bmesh/intern/bmesh_mods.hh
Normal 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
|
||||
3001
blender-5.2.0/source/blender/bmesh/intern/bmesh_opdefines.cc
Normal file
3001
blender-5.2.0/source/blender/bmesh/intern/bmesh_opdefines.cc
Normal file
File diff suppressed because it is too large
Load Diff
847
blender-5.2.0/source/blender/bmesh/intern/bmesh_operator_api.hh
Normal file
847
blender-5.2.0/source/blender/bmesh/intern/bmesh_operator_api.hh
Normal 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
|
||||
@@ -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
|
||||
1920
blender-5.2.0/source/blender/bmesh/intern/bmesh_operators.cc
Normal file
1920
blender-5.2.0/source/blender/bmesh/intern/bmesh_operators.cc
Normal file
File diff suppressed because it is too large
Load Diff
241
blender-5.2.0/source/blender/bmesh/intern/bmesh_operators.hh
Normal file
241
blender-5.2.0/source/blender/bmesh/intern/bmesh_operators.hh
Normal 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 */
|
||||
@@ -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
|
||||
1530
blender-5.2.0/source/blender/bmesh/intern/bmesh_polygon.cc
Normal file
1530
blender-5.2.0/source/blender/bmesh/intern/bmesh_polygon.cc
Normal file
File diff suppressed because it is too large
Load Diff
306
blender-5.2.0/source/blender/bmesh/intern/bmesh_polygon.hh
Normal file
306
blender-5.2.0/source/blender/bmesh/intern/bmesh_polygon.hh
Normal 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
|
||||
1758
blender-5.2.0/source/blender/bmesh/intern/bmesh_polygon_edgenet.cc
Normal file
1758
blender-5.2.0/source/blender/bmesh/intern/bmesh_polygon_edgenet.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
95
blender-5.2.0/source/blender/bmesh/intern/bmesh_private.hh
Normal file
95
blender-5.2.0/source/blender/bmesh/intern/bmesh_private.hh
Normal 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 */
|
||||
2585
blender-5.2.0/source/blender/bmesh/intern/bmesh_query.cc
Normal file
2585
blender-5.2.0/source/blender/bmesh/intern/bmesh_query.cc
Normal file
File diff suppressed because it is too large
Load Diff
799
blender-5.2.0/source/blender/bmesh/intern/bmesh_query.hh
Normal file
799
blender-5.2.0/source/blender/bmesh/intern/bmesh_query.hh
Normal 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 */
|
||||
151
blender-5.2.0/source/blender/bmesh/intern/bmesh_query_inline.hh
Normal file
151
blender-5.2.0/source/blender/bmesh/intern/bmesh_query_inline.hh
Normal 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
|
||||
210
blender-5.2.0/source/blender/bmesh/intern/bmesh_query_uv.cc
Normal file
210
blender-5.2.0/source/blender/bmesh/intern/bmesh_query_uv.cc
Normal 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
|
||||
94
blender-5.2.0/source/blender/bmesh/intern/bmesh_query_uv.hh
Normal file
94
blender-5.2.0/source/blender/bmesh/intern/bmesh_query_uv.hh
Normal 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
|
||||
573
blender-5.2.0/source/blender/bmesh/intern/bmesh_structure.cc
Normal file
573
blender-5.2.0/source/blender/bmesh/intern/bmesh_structure.cc
Normal 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
|
||||
169
blender-5.2.0/source/blender/bmesh/intern/bmesh_structure.hh
Normal file
169
blender-5.2.0/source/blender/bmesh/intern/bmesh_structure.hh
Normal 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 */
|
||||
@@ -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
|
||||
2524
blender-5.2.0/source/blender/bmesh/intern/bmesh_uvselect.cc
Normal file
2524
blender-5.2.0/source/blender/bmesh/intern/bmesh_uvselect.cc
Normal file
File diff suppressed because it is too large
Load Diff
581
blender-5.2.0/source/blender/bmesh/intern/bmesh_uvselect.hh
Normal file
581
blender-5.2.0/source/blender/bmesh/intern/bmesh_uvselect.hh
Normal 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 ¶ms);
|
||||
void BM_edge_uvselect_set_pick(BMesh *bm,
|
||||
BMEdge *e,
|
||||
bool select,
|
||||
const BMUVSelectPickParams ¶ms);
|
||||
void BM_face_uvselect_set_pick(BMesh *bm,
|
||||
BMFace *f,
|
||||
bool select,
|
||||
const BMUVSelectPickParams ¶ms);
|
||||
|
||||
/**
|
||||
* 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 ¶ms,
|
||||
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 ¶ms,
|
||||
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
|
||||
203
blender-5.2.0/source/blender/bmesh/intern/bmesh_walkers.cc
Normal file
203
blender-5.2.0/source/blender/bmesh/intern/bmesh_walkers.cc
Normal 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
|
||||
197
blender-5.2.0/source/blender/bmesh/intern/bmesh_walkers.hh
Normal file
197
blender-5.2.0/source/blender/bmesh/intern/bmesh_walkers.hh
Normal 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
|
||||
2079
blender-5.2.0/source/blender/bmesh/intern/bmesh_walkers_impl.cc
Normal file
2079
blender-5.2.0/source/blender/bmesh/intern/bmesh_walkers_impl.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
73
blender-5.2.0/source/blender/bmesh/operators/bmo_beautify.cc
Normal file
73
blender-5.2.0/source/blender/bmesh/operators/bmo_beautify.cc
Normal 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
|
||||
96
blender-5.2.0/source/blender/bmesh/operators/bmo_bevel.cc
Normal file
96
blender-5.2.0/source/blender/bmesh/operators/bmo_bevel.cc
Normal 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
|
||||
@@ -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
|
||||
666
blender-5.2.0/source/blender/bmesh/operators/bmo_bridge.cc
Normal file
666
blender-5.2.0/source/blender/bmesh/operators/bmo_bridge.cc
Normal 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
|
||||
747
blender-5.2.0/source/blender/bmesh/operators/bmo_circularize.cc
Normal file
747
blender-5.2.0/source/blender/bmesh/operators/bmo_circularize.cc
Normal 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 ¢er,
|
||||
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 ¢er,
|
||||
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> <ri = 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 ¢er_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> <ri = 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
|
||||
220
blender-5.2.0/source/blender/bmesh/operators/bmo_connect.cc
Normal file
220
blender-5.2.0/source/blender/bmesh/operators/bmo_connect.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
745
blender-5.2.0/source/blender/bmesh/operators/bmo_connect_pair.cc
Normal file
745
blender-5.2.0/source/blender/bmesh/operators/bmo_connect_pair.cc
Normal 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
|
||||
299
blender-5.2.0/source/blender/bmesh/operators/bmo_create.cc
Normal file
299
blender-5.2.0/source/blender/bmesh/operators/bmo_create.cc
Normal 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
|
||||
969
blender-5.2.0/source/blender/bmesh/operators/bmo_dissolve.cc
Normal file
969
blender-5.2.0/source/blender/bmesh/operators/bmo_dissolve.cc
Normal 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(®walker,
|
||||
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(®walker, f)); f2;
|
||||
f2 = static_cast<BMFace *>(BMW_step(®walker)))
|
||||
{
|
||||
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(®walker);
|
||||
|
||||
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(®walker,
|
||||
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(®walker, f))) &&
|
||||
(faces_init[1] = static_cast<BMFace *>(BMW_step(®walker))))
|
||||
{
|
||||
Vector<BMFace *> faces;
|
||||
faces.append(faces_init[0]);
|
||||
faces.append(faces_init[1]);
|
||||
|
||||
BMFace *f_iter;
|
||||
while ((f_iter = static_cast<BMFace *>(BMW_step(®walker)))) {
|
||||
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(®walker);
|
||||
}
|
||||
|
||||
/* 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
|
||||
747
blender-5.2.0/source/blender/bmesh/operators/bmo_dupe.cc
Normal file
747
blender-5.2.0/source/blender/bmesh/operators/bmo_dupe.cc
Normal 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
|
||||
239
blender-5.2.0/source/blender/bmesh/operators/bmo_edgenet.cc
Normal file
239
blender-5.2.0/source/blender/bmesh/operators/bmo_edgenet.cc
Normal 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
|
||||
866
blender-5.2.0/source/blender/bmesh/operators/bmo_extrude.cc
Normal file
866
blender-5.2.0/source/blender/bmesh/operators/bmo_extrude.cc
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
734
blender-5.2.0/source/blender/bmesh/operators/bmo_fill_grid.cc
Normal file
734
blender-5.2.0/source/blender/bmesh/operators/bmo_fill_grid.cc
Normal 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
|
||||
@@ -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
|
||||
128
blender-5.2.0/source/blender/bmesh/operators/bmo_flatten.cc
Normal file
128
blender-5.2.0/source/blender/bmesh/operators/bmo_flatten.cc
Normal 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
|
||||
606
blender-5.2.0/source/blender/bmesh/operators/bmo_hull.cc
Normal file
606
blender-5.2.0/source/blender/bmesh/operators/bmo_hull.cc
Normal 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 */
|
||||
1377
blender-5.2.0/source/blender/bmesh/operators/bmo_inset.cc
Normal file
1377
blender-5.2.0/source/blender/bmesh/operators/bmo_inset.cc
Normal file
File diff suppressed because it is too large
Load Diff
1149
blender-5.2.0/source/blender/bmesh/operators/bmo_join_triangles.cc
Normal file
1149
blender-5.2.0/source/blender/bmesh/operators/bmo_join_triangles.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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, ¶ms);
|
||||
|
||||
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, ¶ms);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
110
blender-5.2.0/source/blender/bmesh/operators/bmo_mirror.cc
Normal file
110
blender-5.2.0/source/blender/bmesh/operators/bmo_mirror.cc
Normal 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
|
||||
303
blender-5.2.0/source/blender/bmesh/operators/bmo_normals.cc
Normal file
303
blender-5.2.0/source/blender/bmesh/operators/bmo_normals.cc
Normal 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
|
||||
@@ -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
|
||||
134
blender-5.2.0/source/blender/bmesh/operators/bmo_planar_faces.cc
Normal file
134
blender-5.2.0/source/blender/bmesh/operators/bmo_planar_faces.cc
Normal 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
|
||||
135
blender-5.2.0/source/blender/bmesh/operators/bmo_poke.cc
Normal file
135
blender-5.2.0/source/blender/bmesh/operators/bmo_poke.cc
Normal 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
|
||||
1736
blender-5.2.0/source/blender/bmesh/operators/bmo_primitive.cc
Normal file
1736
blender-5.2.0/source/blender/bmesh/operators/bmo_primitive.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
271
blender-5.2.0/source/blender/bmesh/operators/bmo_rotate_edges.cc
Normal file
271
blender-5.2.0/source/blender/bmesh/operators/bmo_rotate_edges.cc
Normal 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
|
||||
@@ -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
Reference in New Issue
Block a user