Sculpt: Better boundary preservation with dyntopo
The user-level goal is to make it so boundaries are preserved in a much better manner. At this point of the project boundaries are considered edges which are marked as seam or sharp. It is possible to expand this logic to more cases like boundary between face sets. For now the main focus in the algorithm itself. The rough idea is the following: - Splitting an edge is "lossless" for boundary, so there is no need in any special handling of edges there. - When collapsing an edge prefer to collapse edges which are not boundary but adjacent to it. The vertex which is adjacent to boundary does is preserved and is not moved during the edge collapse algorithm. - Then collapse boundary edges. There are a bit of tricky parts, especially with the flaps. They are explained in the code and ASCII diagrams are provided for better clarity. Co-authored-by: Hans Goudey <hans@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/113836
This commit is contained in:
committed by
Sergey Sharybin
parent
a49dd41290
commit
396ad5db83
@@ -774,6 +774,83 @@ static void edge_queue_insert(EdgeQueueContext *eq_ctx, BMEdge *e, float priorit
|
||||
}
|
||||
}
|
||||
|
||||
/* Return true if the edge is a boundary edge: both its vertices are on a boundary. */
|
||||
static bool is_boundary_edge(const BMEdge &edge)
|
||||
{
|
||||
if (edge.head.hflag & BM_ELEM_SEAM) {
|
||||
return true;
|
||||
}
|
||||
if ((edge.head.hflag & BM_ELEM_SMOOTH) == 0) {
|
||||
return true;
|
||||
}
|
||||
if (!BM_edge_is_manifold(&edge)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TODO(@sergey): Other boundaries? For example, edges between two different face sets. */
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return true if the vertex is adjacent to a boundary edge. */
|
||||
static bool is_boundary_vert(const BMVert &vertex)
|
||||
{
|
||||
BMEdge *edge = vertex.e;
|
||||
BMEdge *first_edge = edge;
|
||||
do {
|
||||
if (is_boundary_edge(*edge)) {
|
||||
return true;
|
||||
}
|
||||
} while ((edge = BM_DISK_EDGE_NEXT(edge, &vertex)) != first_edge);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Return true if at least one of the edge vertices is adjacent to a boundary. */
|
||||
static bool is_edge_adjacent_to_boundary(const BMEdge &edge)
|
||||
{
|
||||
return is_boundary_vert(*edge.v1) || is_boundary_vert(*edge.v2);
|
||||
}
|
||||
|
||||
/* Notes on edge priority.
|
||||
*
|
||||
* The priority is used to control the order in which edges are handled for both splitting of long
|
||||
* edges and collapsing of short edges. For long edges we start by splitting the longest edge and
|
||||
* for collapsing we start with the shortest.
|
||||
*
|
||||
* A heap-like data structure is used to accelerate such ordering. A bit confusingly, this data
|
||||
* structure gives the higher priorities to elements with lower numbers.
|
||||
*
|
||||
* When edges do not belong to and are not adjacent to boundaries, their length is used as the
|
||||
* priority directly. Prefer to handle those edges first. Modifying those edges leads to no
|
||||
* distortion to the boundary.
|
||||
*
|
||||
* Edges adjacent to a boundary with one vertex are handled next, and the vertex which is
|
||||
* on the boundary does not change position as part of the edge collapse algorithm.
|
||||
*
|
||||
* And last, the boundary edges are handled. While subdivision of boundary edges does not change
|
||||
* the shape of the boundary, collapsing boundary edges distorts the boundary. Hence they are
|
||||
* handled last. */
|
||||
|
||||
static float long_edge_queue_priority(const BMEdge &edge)
|
||||
{
|
||||
return -BM_edge_calc_length_squared(&edge);
|
||||
}
|
||||
|
||||
static float short_edge_queue_priority(const BMEdge &edge)
|
||||
{
|
||||
float priority = BM_edge_calc_length_squared(&edge);
|
||||
|
||||
if (is_boundary_edge(edge)) {
|
||||
priority *= 1.5f;
|
||||
}
|
||||
else if (is_edge_adjacent_to_boundary(edge)) {
|
||||
priority *= 1.25f;
|
||||
}
|
||||
|
||||
return priority;
|
||||
}
|
||||
|
||||
static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
|
||||
{
|
||||
#ifdef USE_EDGEQUEUE_TAG
|
||||
@@ -782,7 +859,7 @@ static void long_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
|
||||
{
|
||||
const float len_sq = BM_edge_calc_length_squared(e);
|
||||
if (len_sq > eq_ctx->q->limit_len_squared) {
|
||||
edge_queue_insert(eq_ctx, e, -len_sq);
|
||||
edge_queue_insert(eq_ctx, e, long_edge_queue_priority(*e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -805,7 +882,7 @@ static void long_edge_queue_edge_add_recursive(
|
||||
if (EDGE_QUEUE_TEST(l_edge->e) == false)
|
||||
# endif
|
||||
{
|
||||
edge_queue_insert(eq_ctx, l_edge->e, -len_sq);
|
||||
edge_queue_insert(eq_ctx, l_edge->e, long_edge_queue_priority(*l_edge->e));
|
||||
}
|
||||
|
||||
/* temp support previous behavior! */
|
||||
@@ -853,7 +930,7 @@ static void short_edge_queue_edge_add(EdgeQueueContext *eq_ctx, BMEdge *e)
|
||||
{
|
||||
const float len_sq = BM_edge_calc_length_squared(e);
|
||||
if (len_sq < eq_ctx->q->limit_len_squared) {
|
||||
edge_queue_insert(eq_ctx, e, len_sq);
|
||||
edge_queue_insert(eq_ctx, e, short_edge_queue_priority(*e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1033,6 +1110,19 @@ static void copy_edge_data(BMesh &bm, BMEdge &dst, /*const*/ BMEdge &src)
|
||||
CustomData_bmesh_copy_data(&bm.edata, &bm.edata, src.head.data, &dst.head.data);
|
||||
}
|
||||
|
||||
/* Merge edge custom data from src to dst. */
|
||||
static void merge_edge_data(BMesh &bm, BMEdge &dst, const BMEdge &src)
|
||||
{
|
||||
dst.head.hflag |= (src.head.hflag & ~(BM_ELEM_TAG | BM_ELEM_SMOOTH));
|
||||
|
||||
/* If either of the src or dst is sharp the result is sharp. */
|
||||
if ((src.head.hflag & BM_ELEM_SMOOTH) == 0) {
|
||||
dst.head.hflag &= ~BM_ELEM_SMOOTH;
|
||||
}
|
||||
|
||||
BM_data_interp_from_edges(&bm, &src, &dst, &dst, 0.5f);
|
||||
}
|
||||
|
||||
static void pbvh_bmesh_split_edge(EdgeQueueContext *eq_ctx, PBVH *pbvh, BMEdge *e)
|
||||
{
|
||||
BMesh *bm = pbvh->header.bm;
|
||||
@@ -1188,16 +1278,278 @@ static bool pbvh_bmesh_subdivide_long_edges(EdgeQueueContext *eq_ctx, PBVH *pbvh
|
||||
return any_subdivided;
|
||||
}
|
||||
|
||||
/** Check whether the \a vert is adjacent to any face which are adjacent to the #edge. */
|
||||
static bool vert_in_face_adjacent_to_edge(BMVert &vert, BMEdge &edge)
|
||||
{
|
||||
BMIter bm_iter;
|
||||
BMFace *face;
|
||||
BM_ITER_ELEM (face, &bm_iter, &edge, BM_FACES_OF_EDGE) {
|
||||
if (BM_vert_in_face(&vert, face)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge attributes of a flap face into an edge which will remain after the edge collapse in
|
||||
* #pbvh_bmesh_collapse_edge.
|
||||
*
|
||||
* This function is to be called before faces adjacent to \a e are deleted.
|
||||
* This function only handles edge attributes and does not handle face deletion.
|
||||
|
||||
* \param del_face: Face which is adjacent to \a v_del and will form a flap when merging \a v_del
|
||||
* to \a v_conn.
|
||||
* \param flap_face: Face which is adjacent to \a v_conn and will form a flap when merging \a v_del
|
||||
* to \a v_conn.
|
||||
* \param e: An edge which is being collapsed. It connects \a v_del and \a v_conn.
|
||||
* \param v_del: A vertex which will be removed after the edge collapse.
|
||||
* \param l_del: A loop of del_face which is adjacent to v_del.
|
||||
* \param v_conn: A vertex which into which geometry is reconnected to after the edge collapse.
|
||||
*/
|
||||
static void merge_flap_edge_data(BMesh &bm,
|
||||
BMFace *del_face,
|
||||
BMFace *flap_face,
|
||||
BMEdge *e,
|
||||
BMVert *v_del,
|
||||
BMLoop *l_del,
|
||||
BMVert *v_conn)
|
||||
{
|
||||
/*
|
||||
*
|
||||
* v_del
|
||||
* +
|
||||
* del_face . / |
|
||||
* . / |
|
||||
* . / |
|
||||
* v1 +---------------------+ v2 |
|
||||
* . \ |
|
||||
* . \ |
|
||||
* . \ |
|
||||
* flap_face +
|
||||
* v_conn
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
UNUSED_VARS_NDEBUG(del_face, flap_face);
|
||||
|
||||
/* Faces around `e` (which connects `v_del` to `v_conn`) are to the handled separately from this
|
||||
* function. Help troubleshooting cases where these faces are mistakingly considered flaps. */
|
||||
BLI_assert(!BM_edge_in_face(e, del_face));
|
||||
BLI_assert(!BM_edge_in_face(e, flap_face));
|
||||
|
||||
/* The l_del->next->v and l_del->prev->v are v1 and v2, but in an unknown order. */
|
||||
BMEdge *edge_v1_v2 = BM_edge_exists(l_del->next->v, l_del->prev->v);
|
||||
if (!edge_v1_v2) {
|
||||
CLOG_WARN(&LOG, "Unable to find edge shared between deleting and flap faces");
|
||||
return;
|
||||
}
|
||||
|
||||
BLI_assert(BM_edge_in_face(edge_v1_v2, del_face));
|
||||
BLI_assert(BM_edge_in_face(edge_v1_v2, flap_face));
|
||||
|
||||
/* Disambiguate v1 from v2: the v2 is adjacent to a face around #e. */
|
||||
BMVert *v2 = vert_in_face_adjacent_to_edge(*edge_v1_v2->v1, *e) ? edge_v1_v2->v1 :
|
||||
edge_v1_v2->v2;
|
||||
BMVert *v1 = BM_edge_other_vert(edge_v1_v2, v2);
|
||||
|
||||
/* Merge attributes into an edge (v1, v_conn). */
|
||||
BMEdge *dst_edge = BM_edge_exists(v1, v_conn);
|
||||
|
||||
const std::array<const BMEdge *, 4> source_edges{
|
||||
/* Edges of the `flap_face`.
|
||||
* The face will be deleted, effectively being "collapsed" into an edge. */
|
||||
edge_v1_v2,
|
||||
BM_edge_exists(v2, v_conn),
|
||||
|
||||
/* Edges of the `del_face`.
|
||||
* These edges are implicitly merged with the ones from the `flap_face` upon collapsing edge
|
||||
* `e`. */
|
||||
BM_edge_exists(v1, v_del),
|
||||
BM_edge_exists(v2, v_del),
|
||||
};
|
||||
|
||||
for (const BMEdge *src_edge : source_edges) {
|
||||
if (!src_edge) {
|
||||
CLOG_WARN(&LOG, "Unable to find source edge for flap attributes merge");
|
||||
continue;
|
||||
}
|
||||
|
||||
merge_edge_data(bm, *dst_edge, *src_edge);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find vertex which can be an outer for the flap face: the vertex will become loose when the face
|
||||
* and its edges are removed.
|
||||
* If there are multiple of such vertices, return null.
|
||||
*/
|
||||
static BMVert *find_outer_flap_vert(BMFace &face)
|
||||
{
|
||||
BMVert *flap_vert = nullptr;
|
||||
|
||||
BMIter bm_iter;
|
||||
BMVert *vert;
|
||||
BM_ITER_ELEM (vert, &bm_iter, &face, BM_VERTS_OF_FACE) {
|
||||
if (BM_vert_face_count_at_most(vert, 2) == 1) {
|
||||
if (flap_vert) {
|
||||
/* There are multiple vertices which become loose on removing the face and its edges.*/
|
||||
return nullptr;
|
||||
}
|
||||
flap_vert = vert;
|
||||
}
|
||||
}
|
||||
|
||||
return flap_vert;
|
||||
}
|
||||
|
||||
/* If the `del_face` is a flap, merge edge data from edges adjacent to "corner" vertex into the
|
||||
* other edge. The "corner" as it is an "outer", or a vertex which will become loose when the
|
||||
* `del_face` and its edges are removed.
|
||||
*
|
||||
* If the face is not a flap then this function does nothing. */
|
||||
static void try_merge_flap_edge_data_before_dissolve(BMesh &bm, BMFace &face)
|
||||
{
|
||||
/*
|
||||
* v1 v2
|
||||
* ... ------ + ----------------- + ------ ...
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* \ /
|
||||
* + v_flap
|
||||
*/
|
||||
|
||||
BMVert *v_flap = find_outer_flap_vert(face);
|
||||
if (!v_flap) {
|
||||
return;
|
||||
}
|
||||
|
||||
BMLoop *l_flap = BM_vert_find_first_loop(v_flap);
|
||||
BLI_assert(l_flap->v == v_flap);
|
||||
|
||||
/* Edges which are adjacent ot the v_flap. */
|
||||
BMEdge *edge_1 = l_flap->prev->e;
|
||||
BMEdge *edge_2 = l_flap->e;
|
||||
|
||||
BLI_assert(BM_edge_face_count(edge_1) == 1);
|
||||
BLI_assert(BM_edge_face_count(edge_2) == 1);
|
||||
|
||||
BMEdge *edge_v1_v2 = l_flap->next->e;
|
||||
|
||||
merge_edge_data(bm, *edge_v1_v2, *edge_1);
|
||||
merge_edge_data(bm, *edge_v1_v2, *edge_2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge attributes of edges from \a v_del to \a f
|
||||
*
|
||||
* This function is to be called before faces adjacent to \a e are deleted.
|
||||
* This function only handles edge attributes. and does not handle face deletion.
|
||||
|
||||
* \param del_face: Face which is adjacent to \a v_del and will be deleted as part of merging
|
||||
* \a v_del to \a v_conn.
|
||||
* \param new_face: A new face which is created from \a del_face by replacing \a v_del with
|
||||
* \a v_conn.
|
||||
* \param v_del: A vertex which will be removed after the edge collapse.
|
||||
* \param l_del: A loop of del_face which is adjacent to v_del.
|
||||
* \param v_conn: A vertex which into which geometry is reconnected to after the edge collapse.
|
||||
*/
|
||||
static void merge_face_edge_data(BMesh &bm,
|
||||
BMFace * /*del_face*/,
|
||||
BMFace *new_face,
|
||||
BMVert *v_del,
|
||||
BMLoop *l_del,
|
||||
BMVert *v_conn)
|
||||
{
|
||||
/* When collapsing an edge (v_conn, v_del) a face (v_conn, v2, v_del) is to be deleted and the
|
||||
* v_del reference in the face (v_del, v2, v1) is to be replaced with v_conn. Doing vertex
|
||||
* reference replacement in BMesh is not trivial. so for the simplicity the
|
||||
* #pbvh_bmesh_collapse_edge deletes both original faces and creates new one (c_conn, v2, v1).
|
||||
*
|
||||
* When doing such re-creating attributes from old edges are to be merged into the new ones:
|
||||
* - Attributes of (v_del, v1) needs to be merged into (v_conn, v1),
|
||||
* - Attributes of (v_del, v2) needs to be merged into (v_conn, v2),
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* v2
|
||||
* +
|
||||
* /|\
|
||||
* / | \
|
||||
* / | \
|
||||
* / | \
|
||||
* / | \
|
||||
* / | \
|
||||
* / | \
|
||||
* +-------+-------+
|
||||
* v_conn v_del v1
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
|
||||
/* The l_del->next->v and l_del->prev->v are v1 and v2, but in an unknown order. */
|
||||
BMEdge *edge_v1_v2 = BM_edge_exists(l_del->next->v, l_del->prev->v);
|
||||
if (!edge_v1_v2) {
|
||||
CLOG_WARN(&LOG, "Unable to find edge shared between old and new faces");
|
||||
return;
|
||||
}
|
||||
|
||||
BMIter bm_iter;
|
||||
BMEdge *dst_edge;
|
||||
BM_ITER_ELEM (dst_edge, &bm_iter, new_face, BM_EDGES_OF_FACE) {
|
||||
if (dst_edge == edge_v1_v2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BLI_assert(BM_vert_in_edge(dst_edge, v_conn));
|
||||
|
||||
/* Depending on an edge v_other will be v1 or v2. */
|
||||
BMVert *v_other = BM_edge_other_vert(dst_edge, v_conn);
|
||||
|
||||
BMEdge *src_edge = BM_edge_exists(v_del, v_other);
|
||||
BLI_assert(src_edge);
|
||||
|
||||
if (src_edge) {
|
||||
merge_edge_data(bm, *dst_edge, *src_edge);
|
||||
}
|
||||
else {
|
||||
CLOG_WARN(&LOG, "Unable to find edge to merge attributes from");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void pbvh_bmesh_collapse_edge(
|
||||
PBVH *pbvh, BMEdge *e, BMVert *v1, BMVert *v2, GHash *deleted_verts, EdgeQueueContext *eq_ctx)
|
||||
{
|
||||
BMesh &bm = *pbvh->header.bm;
|
||||
/* Prefer deleting the vertex that is less masked. */
|
||||
|
||||
const bool v1_on_boundary = is_boundary_vert(*v1);
|
||||
const bool v2_on_boundary = is_boundary_vert(*v2);
|
||||
|
||||
BMVert *v_del;
|
||||
BMVert *v_conn;
|
||||
if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) <
|
||||
BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset))
|
||||
if (v1_on_boundary || v2_on_boundary) {
|
||||
/* Boundary edges can be collapsed with minimal distortion. For those it does not
|
||||
* matter too much which vertex to keep and which one to remove.
|
||||
*
|
||||
* For edges which are adjacent to boundaries, keep the vertex which is on boundary and
|
||||
* dissolve the other one. */
|
||||
if (v1_on_boundary) {
|
||||
v_del = v2;
|
||||
v_conn = v1;
|
||||
}
|
||||
else {
|
||||
v_del = v1;
|
||||
v_conn = v2;
|
||||
}
|
||||
}
|
||||
else if (BM_ELEM_CD_GET_FLOAT(v1, eq_ctx->cd_vert_mask_offset) <
|
||||
BM_ELEM_CD_GET_FLOAT(v2, eq_ctx->cd_vert_mask_offset))
|
||||
{
|
||||
/* Prefer deleting the vertex that is less masked. */
|
||||
v_del = v1;
|
||||
v_conn = v2;
|
||||
}
|
||||
@@ -1209,6 +1561,38 @@ static void pbvh_bmesh_collapse_edge(
|
||||
/* Remove the merge vertex from the PBVH. */
|
||||
pbvh_bmesh_vert_remove(pbvh, v_del);
|
||||
|
||||
/* For all remaining faces of v_del, create a new face that is the
|
||||
* same except it uses v_conn instead of v_del */
|
||||
/* NOTE: this could be done with BM_vert_splice(), but that requires handling other issues like
|
||||
* duplicate edges, so it wouldn't really buy anything. */
|
||||
Vector<BMFace *, 16> deleted_faces;
|
||||
|
||||
BMLoop *l;
|
||||
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) {
|
||||
BMFace *f_del = l->f;
|
||||
|
||||
/* Ignore faces around `e`: they will be deleted explicitly later on.
|
||||
* Without ignoring these faces the #bm_face_exists_tri_from_loop_vert() triggers an assert. */
|
||||
if (BM_edge_in_face(e, f_del)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Schedule the faces adjacent to the v_del for deletion first.
|
||||
* This way we know that it will be #existing_face which is deleted last when deleting faces
|
||||
* which forms a flap. */
|
||||
deleted_faces.append(f_del);
|
||||
|
||||
/* Check if a face using these vertices already exists. If so, skip adding this face and mark
|
||||
* the existing one for deletion as well. Prevents extraneous "flaps" from being created.
|
||||
* Check is similar to #BM_face_exists. */
|
||||
if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
|
||||
merge_flap_edge_data(bm, f_del, existing_face, e, v_del, l, v_conn);
|
||||
|
||||
deleted_faces.append(existing_face);
|
||||
}
|
||||
}
|
||||
BM_LOOPS_OF_VERT_ITER_END;
|
||||
|
||||
/* Remove all faces adjacent to the edge. */
|
||||
BMLoop *l_adj;
|
||||
while ((l_adj = e->l)) {
|
||||
@@ -1222,23 +1606,12 @@ static void pbvh_bmesh_collapse_edge(
|
||||
BLI_assert(BM_edge_is_wire(e));
|
||||
BM_edge_kill(&bm, e);
|
||||
|
||||
/* For all remaining faces of v_del, create a new face that is the
|
||||
* same except it uses v_conn instead of v_del */
|
||||
/* NOTE: this could be done with BM_vert_splice(), but that
|
||||
* requires handling other issues like duplicate edges, so doesn't
|
||||
* really buy anything. */
|
||||
Vector<BMFace *, 16> deleted_faces;
|
||||
|
||||
BMLoop *l;
|
||||
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_del) {
|
||||
/* Get vertices, replace use of v_del with v_conn */
|
||||
BMFace *f = l->f;
|
||||
|
||||
/* Check if a face using these vertices already exists. If so, skip adding this face and mark
|
||||
* the existing one for deletion as well. Prevents extraneous "flaps" from being created.
|
||||
* Check is similar to #BM_face_exists. */
|
||||
if (BMFace *existing_face = bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
|
||||
deleted_faces.append(existing_face);
|
||||
if (bm_face_exists_tri_from_loop_vert(l->next, v_conn)) {
|
||||
/* This case is handled above. */
|
||||
}
|
||||
else {
|
||||
const std::array<BMVert *, 3> v_tri{v_conn, l->next->v, l->prev->v};
|
||||
@@ -1247,15 +1620,15 @@ static void pbvh_bmesh_collapse_edge(
|
||||
PBVHNode *n = pbvh_bmesh_node_from_face(pbvh, f);
|
||||
int ni = n - pbvh->nodes.data();
|
||||
const std::array<BMEdge *, 3> e_tri = bm_edges_from_tri(&bm, v_tri);
|
||||
pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f);
|
||||
BMFace *new_face = pbvh_bmesh_face_create(pbvh, ni, v_tri, e_tri, f);
|
||||
|
||||
merge_face_edge_data(bm, f, new_face, v_del, l, v_conn);
|
||||
|
||||
/* Ensure that v_conn is in the new face's node */
|
||||
if (!n->bm_unique_verts.contains(v_conn)) {
|
||||
n->bm_other_verts.add(v_conn);
|
||||
}
|
||||
}
|
||||
|
||||
deleted_faces.append(f);
|
||||
}
|
||||
BM_LOOPS_OF_VERT_ITER_END;
|
||||
|
||||
@@ -1267,6 +1640,11 @@ static void pbvh_bmesh_collapse_edge(
|
||||
const std::array<BMVert *, 3> v_tri{l_iter->v, l_iter->next->v, l_iter->next->next->v};
|
||||
const std::array<BMEdge *, 3> e_tri{l_iter->e, l_iter->next->e, l_iter->next->next->e};
|
||||
|
||||
/* if its sa flap face merge its "outer" edge data into "base", so that boundary is propagated
|
||||
* from edges which are about to be deleted to the base of the triangle and will stay attached
|
||||
* to the mesh. */
|
||||
try_merge_flap_edge_data_before_dissolve(bm, *f_del);
|
||||
|
||||
/* Remove the face */
|
||||
pbvh_bmesh_face_remove(pbvh, f_del);
|
||||
BM_face_kill(&bm, f_del);
|
||||
@@ -1296,14 +1674,19 @@ static void pbvh_bmesh_collapse_edge(
|
||||
}
|
||||
}
|
||||
|
||||
/* Move v_conn to the midpoint of v_conn and v_del (if v_conn still exists, it
|
||||
* may have been deleted above). */
|
||||
if (v_conn != nullptr) {
|
||||
/* If the v_conn was not removed above move it to the midpoint of v_conn and v_del. Doing so
|
||||
* helps avoiding long stretched and degenerated triangles.
|
||||
*
|
||||
* However, if the vertex is on a boundary, do not move it to preserve the shape of the
|
||||
* boundary. */
|
||||
if (v_conn != nullptr && !is_boundary_vert(*v_conn)) {
|
||||
BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset);
|
||||
mid_v3_v3v3(v_conn->co, v_conn->co, v_del->co);
|
||||
add_v3_v3(v_conn->no, v_del->no);
|
||||
normalize_v3(v_conn->no);
|
||||
}
|
||||
|
||||
if (v_conn != nullptr) {
|
||||
/* Update bounding boxes attached to the connected vertex.
|
||||
* Note that we can often get-away without this but causes #48779. */
|
||||
BM_LOOPS_OF_VERT_ITER_BEGIN (l, v_conn) {
|
||||
|
||||
Reference in New Issue
Block a user