diff --git a/source/blender/blenkernel/BKE_paint.hh b/source/blender/blenkernel/BKE_paint.hh index 4790e3e2d07..36ccc7e8b57 100644 --- a/source/blender/blenkernel/BKE_paint.hh +++ b/source/blender/blenkernel/BKE_paint.hh @@ -785,6 +785,9 @@ void BKE_sculpt_attribute_destroy_temporary_all(Object *ob); /* Destroy attributes that were marked as stroke only in SculptAttributeParams. */ void BKE_sculpt_attributes_destroy_temporary_stroke(Object *ob); +/* Ensures the internal sculpt attribute references are valid. */ +void BKE_sculpt_update_attribute_refs(Object *ob); + BLI_INLINE void *BKE_sculpt_vertex_attr_get(const PBVHVertRef vertex, const SculptAttribute *attr) { if (attr->data) { diff --git a/source/blender/blenkernel/intern/paint.cc b/source/blender/blenkernel/intern/paint.cc index 7868288b442..5d485ee7f4c 100644 --- a/source/blender/blenkernel/intern/paint.cc +++ b/source/blender/blenkernel/intern/paint.cc @@ -71,6 +71,7 @@ #include "BLO_read_write.hh" #include "bmesh.h" +#include "bmesh_log.h" using blender::float3; using blender::MutableSpan; @@ -1493,15 +1494,15 @@ void BKE_sculptsession_free(Object *ob) if (ss->bm) { BKE_sculptsession_bm_to_me(ob, true); + + if (ss->bm_log) { + BM_log_free(ss->bm, ss->bm_log); + } BM_mesh_free(ss->bm); } sculptsession_free_pbvh(ob); - if (ss->bm_log) { - BM_log_free(ss->bm_log); - } - if (ss->tex_pool) { BKE_image_pool_free(ss->tex_pool); } @@ -2833,6 +2834,11 @@ static void sculpt_attribute_update_refs(Object *ob) } } +void BKE_sculpt_update_attribute_refs(Object *ob) +{ + sculpt_attribute_update_refs(ob); +} + void BKE_sculpt_attribute_destroy_temporary_all(Object *ob) { SculptSession *ss = ob->sculpt; diff --git a/source/blender/blenkernel/intern/pbvh_bmesh.cc b/source/blender/blenkernel/intern/pbvh_bmesh.cc index f8cfac6d11f..5aa80010754 100644 --- a/source/blender/blenkernel/intern/pbvh_bmesh.cc +++ b/source/blender/blenkernel/intern/pbvh_bmesh.cc @@ -23,6 +23,7 @@ #include "DRW_pbvh.hh" #include "bmesh.h" +#include "bmesh_log.h" #include "pbvh_intern.hh" using blender::Array; @@ -448,8 +449,8 @@ static BMVert *pbvh_bmesh_vert_create(PBVH *pbvh, node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateBB | PBVH_TopologyUpdated; - /* Log the new vertex. */ - BM_log_vert_added(pbvh->bm_log, v, cd_vert_mask_offset); + /* Log the new vertex */ + BM_log_vert_added(pbvh->header.bm, pbvh->bm_log, v); return v; } @@ -478,8 +479,8 @@ static BMFace *pbvh_bmesh_face_create(PBVH *pbvh, node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated; node->flag &= ~PBVH_FullyHidden; - /* Log the new face. */ - BM_log_face_added(pbvh->bm_log, f); + /* Log the new face */ + BM_log_face_added(pbvh->header.bm, pbvh->bm_log, f); return f; } @@ -609,8 +610,8 @@ static void pbvh_bmesh_face_remove(PBVH *pbvh, BMFace *f) f_node->bm_faces.remove(f); BM_ELEM_CD_SET_INT(f, pbvh->cd_face_node_offset, DYNTOPO_NODE_NONE); - /* Log removed face. */ - BM_log_face_removed(pbvh->bm_log, f); + /* Log removed face */ + BM_log_face_removed(pbvh->header.bm, pbvh->bm_log, f); /* Mark node for update. */ f_node->flag |= PBVH_UpdateDrawBuffers | PBVH_UpdateNormals | PBVH_TopologyUpdated; @@ -1274,7 +1275,7 @@ static void pbvh_bmesh_collapse_edge( if ((v_tri[j] != v_del) && (v_tri[j]->e == nullptr)) { pbvh_bmesh_vert_remove(pbvh, v_tri[j]); - BM_log_vert_removed(pbvh->bm_log, v_tri[j], eq_ctx->cd_vert_mask_offset); + BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v_tri[j]); if (v_tri[j] == v_conn) { v_conn = nullptr; @@ -1288,7 +1289,7 @@ 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) { - BM_log_vert_before_modified(pbvh->bm_log, v_conn, eq_ctx->cd_vert_mask_offset); + BM_log_vert_before_modified(pbvh->header.bm, pbvh->bm_log, v_conn); 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); @@ -1304,7 +1305,7 @@ static void pbvh_bmesh_collapse_edge( /* Delete v_del */ BLI_assert(!BM_vert_face_check(v_del)); - BM_log_vert_removed(pbvh->bm_log, v_del, eq_ctx->cd_vert_mask_offset); + BM_log_vert_removed(pbvh->header.bm, pbvh->bm_log, v_del); /* v_conn == nullptr is OK */ BLI_ghash_insert(deleted_verts, v_del, v_conn); BM_vert_kill(&bm, v_del); diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt index cebf8209bbb..568b4558dd2 100644 --- a/source/blender/bmesh/CMakeLists.txt +++ b/source/blender/bmesh/CMakeLists.txt @@ -67,6 +67,8 @@ set(SRC intern/bmesh_delete.h intern/bmesh_edgeloop.cc intern/bmesh_edgeloop.h + intern/bmesh_idmap.cc + intern/bmesh_idmap.hh intern/bmesh_inline.h intern/bmesh_interp.cc intern/bmesh_interp.h @@ -162,6 +164,7 @@ set(SRC # public includes bmesh.h + bmesh_log.h bmesh_tools.h ) diff --git a/source/blender/bmesh/bmesh_log.h b/source/blender/bmesh/bmesh_log.h new file mode 100644 index 00000000000..ae6db230d7f --- /dev/null +++ b/source/blender/bmesh/bmesh_log.h @@ -0,0 +1,2 @@ +#include "intern/bmesh_log.h" + diff --git a/source/blender/bmesh/intern/bmesh_idmap.cc b/source/blender/bmesh/intern/bmesh_idmap.cc new file mode 100644 index 00000000000..605bccec209 --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_idmap.cc @@ -0,0 +1,417 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup bmesh + * + */ + +#include "MEM_guardedalloc.h" + +#include "BLI_assert.h" +#include "BLI_compiler_attrs.h" +#include "BLI_compiler_compat.h" +#include "BLI_index_range.hh" +#include "BLI_map.hh" +#include "BLI_set.hh" +#include "BLI_vector.hh" + +#include "BKE_customdata.h" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "bmesh_idmap.hh" +#include +#include + +using namespace blender; + +/* Controls when `BMIdMap.free_idx_map` is used. + * When the size of `BMIdMap.freelist` goes above `FREELIST_HASHMAP_THRESHOLD_HIGH` + * then `BMIdMap.free_idx_map` will be created. When it goes below + * `FREELIST_HASHMAP_THRESHOLD_LOW` it will be freed. + */ +#define FREELIST_HASHMAP_THRESHOLD_HIGH 1024 +#define FREELIST_HASHMAP_THRESHOLD_LOW 700 + +const char *BM_idmap_attr_name_get(int htype) +{ + switch (htype) { + case BM_VERT: + return ".sculpt.vertex_id"; + case BM_EDGE: + return ".sculpt.edge_id"; + case BM_LOOP: + return ".sculpt.corner_id"; + case BM_FACE: + return ".sculpt.face_id"; + default: + BLI_assert_unreachable(); + return "error"; + } +} + +static void idmap_log_message(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); +} + +BMIdMap *BM_idmap_new(BMesh *bm, int elem_mask) +{ + BMIdMap *idmap = MEM_new("BMIdMap"); + + for (int i = 0; i < ARRAY_SIZE(idmap->cd_id_off); i++) { + idmap->cd_id_off[i] = -1; + } + + idmap->flag = elem_mask; + idmap->bm = bm; + idmap->maxid = BM_ID_NONE + 1; + + BM_idmap_check_attributes(idmap); + + return idmap; +} + +template static constexpr char get_elem_type() +{ + if constexpr (std::is_same_v) { + return BM_VERT; + } + else if constexpr (std::is_same_v) { + return BM_EDGE; + } + else if constexpr (std::is_same_v) { + return BM_LOOP; + } + else if constexpr (std::is_same_v) { + return BM_FACE; + } +} + +static void idmap_grow_map(BMIdMap *idmap, int newid) +{ + if (idmap->map.size() <= newid) { + idmap->map.resize(newid + 1); + } +} + +void BM_idmap_delete_attributes_mesh(Mesh *me) +{ + CustomData_free_layer_named(&me->vert_data, BM_idmap_attr_name_get(BM_VERT), me->totvert); + CustomData_free_layer_named(&me->edge_data, BM_idmap_attr_name_get(BM_EDGE), me->totedge); + CustomData_free_layer_named(&me->loop_data, BM_idmap_attr_name_get(BM_LOOP), me->totloop); + CustomData_free_layer_named(&me->face_data, BM_idmap_attr_name_get(BM_FACE), me->faces_num); +} + +void BM_idmap_delete_attributes(BMesh *bm) +{ + BM_data_layer_free_named(bm, &bm->vdata, BM_idmap_attr_name_get(BM_VERT)); + BM_data_layer_free_named(bm, &bm->edata, BM_idmap_attr_name_get(BM_EDGE)); + BM_data_layer_free_named(bm, &bm->ldata, BM_idmap_attr_name_get(BM_LOOP)); + BM_data_layer_free_named(bm, &bm->pdata, BM_idmap_attr_name_get(BM_FACE)); +} + +void BM_idmap_check_ids(BMIdMap *idmap) +{ + BMIter iter; + BMVert *v; + BMEdge *e; + BMFace *f; + + BM_idmap_check_attributes(idmap); + + idmap->freelist.clear(); + if (idmap->free_idx_map) { + MEM_delete(idmap->free_idx_map); + idmap->free_idx_map = nullptr; + } + + int max_id = 1; + + if (idmap->flag & BM_VERT) { + BM_ITER_MESH (v, &iter, idmap->bm, BM_VERTS_OF_MESH) { + int id = BM_ELEM_CD_GET_INT(v, idmap->cd_id_off[BM_VERT]); + + max_id = max_ii(max_id, id); + } + } + if (idmap->flag & BM_EDGE) { + BM_ITER_MESH (e, &iter, idmap->bm, BM_EDGES_OF_MESH) { + int id = BM_ELEM_CD_GET_INT(e, idmap->cd_id_off[BM_EDGE]); + + max_id = max_ii(max_id, id); + } + } + if (idmap->flag & (BM_FACE | BM_LOOP)) { + BM_ITER_MESH (f, &iter, idmap->bm, BM_FACES_OF_MESH) { + if (idmap->flag & BM_FACE) { + int id = BM_ELEM_CD_GET_INT(f, idmap->cd_id_off[BM_FACE]); + max_id = max_ii(max_id, id); + } + + if (idmap->flag & BM_LOOP) { + BMLoop *l = f->l_first; + do { + int id = BM_ELEM_CD_GET_INT(l, idmap->cd_id_off[BM_LOOP]); + max_id = max_ii(max_id, id); + } while ((l = l->next) != f->l_first); + } + } + } + + max_id++; + + if (idmap->map.size() <= max_id) { + idmap->map.resize(max_id); + } + + /* Zero map. */ + memset(static_cast(idmap->map.data()), 0, sizeof(void *) * idmap->map.size()); + + auto check_elem = [&](auto *elem) { + int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]); + + if (id == BM_ID_NONE || id < 0 || id >= idmap->map.size() || (idmap->map[id])) { + id = max_id++; + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id); + } + + idmap_grow_map(idmap, id); + idmap->map[id] = reinterpret_cast(elem); + }; + + if (idmap->flag & BM_VERT) { + BM_ITER_MESH (v, &iter, idmap->bm, BM_VERTS_OF_MESH) { + check_elem(v); + } + } + if (idmap->flag & BM_EDGE) { + BM_ITER_MESH (e, &iter, idmap->bm, BM_EDGES_OF_MESH) { + check_elem(e); + } + } + if (idmap->flag & (BM_FACE | BM_LOOP)) { + BM_ITER_MESH (f, &iter, idmap->bm, BM_FACES_OF_MESH) { + check_elem(f); + if (idmap->flag & BM_LOOP) { + BMLoop *l = f->l_first; + + do { + check_elem(l); + } while ((l = l->next) != f->l_first); + } + } + } + + idmap->maxid = max_id + 1; +} + +static bool bm_idmap_check_attr(BMIdMap *idmap, int type) +{ + if (!(idmap->flag & type)) { + return false; + } + + CustomData *cdata; + const char *name = BM_idmap_attr_name_get(type); + + switch (type) { + case BM_VERT: + cdata = &idmap->bm->vdata; + break; + case BM_EDGE: + cdata = &idmap->bm->edata; + break; + case BM_LOOP: + cdata = &idmap->bm->ldata; + break; + case BM_FACE: + cdata = &idmap->bm->pdata; + break; + default: + BLI_assert_unreachable(); + return false; + } + + int idx = CustomData_get_named_layer_index(cdata, CD_PROP_INT32, name); + bool exists = idx != -1; + + if (!exists) { + BM_data_layer_add_named(idmap->bm, cdata, CD_PROP_INT32, name); + idx = CustomData_get_named_layer_index(cdata, CD_PROP_INT32, name); + } + + idmap->cd_id_off[type] = cdata->layers[idx].offset; + + return !exists; +} + +bool BM_idmap_check_attributes(BMIdMap *idmap) +{ + bool ret = false; + + ret |= bm_idmap_check_attr(idmap, BM_VERT); + ret |= bm_idmap_check_attr(idmap, BM_EDGE); + ret |= bm_idmap_check_attr(idmap, BM_LOOP); + ret |= bm_idmap_check_attr(idmap, BM_FACE); + + return ret; +} + +void BM_idmap_destroy(BMIdMap *idmap) +{ + if (idmap->free_idx_map) { + MEM_delete(idmap->free_idx_map); + } + + MEM_delete(idmap); +} + +static void check_idx_map(BMIdMap *idmap) +{ + if (idmap->free_idx_map && idmap->freelist.size() < FREELIST_HASHMAP_THRESHOLD_LOW) { + MEM_delete(idmap->free_idx_map); + idmap->free_idx_map = nullptr; + } + else if (!idmap->free_idx_map && idmap->freelist.size() < FREELIST_HASHMAP_THRESHOLD_HIGH) { + idmap->free_idx_map = MEM_new("BMIdMap::FreeIdxMap"); + + for (int i : IndexRange(idmap->freelist.size())) { + idmap->free_idx_map->add(idmap->freelist[i], i); + } + } +} + +template int BM_idmap_alloc(BMIdMap *idmap, T *elem) +{ + int id = BM_ID_NONE; + + if (idmap->freelist.size()) { + id = idmap->freelist.pop_last(); + if (idmap->free_idx_map) { + idmap->free_idx_map->remove(id); + } + } + + if (id == BM_ID_NONE) { + id = idmap->maxid++; + } + + idmap_grow_map(idmap, id); + idmap->map[id] = reinterpret_cast(elem); + + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id); + + return id; +} + +template void BM_idmap_assign(BMIdMap *idmap, T *elem, int id) +{ + /* Remove id from freelist. */ + if (idmap->free_idx_map) { + const int *val; + + if ((val = idmap->free_idx_map->lookup_ptr(id))) { + idmap->freelist[*val] = BM_ID_NONE; + idmap->free_idx_map->remove(id); + } + } + else { + for (int i : IndexRange(idmap->freelist.size())) { + if (idmap->freelist[i] == id) { + idmap->freelist[i] = BM_ID_NONE; + } + } + } + + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], id); + + idmap_grow_map(idmap, id); + idmap->map[id] = reinterpret_cast(elem); + + check_idx_map(idmap); +} + +void BM_idmap_release_id(BMIdMap *idmap, int id, bool clear_id) +{ + BMElem *elem = idmap->map[id]; + + if (elem) { + BM_idmap_release(idmap, elem, clear_id); + } +} + +template void BM_idmap_release(BMIdMap *idmap, T *elem, bool clear_id) +{ + int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]); + + if (id == BM_ID_NONE) { + idmap_log_message("%s: unassigned id!\n", __func__); + return; + } + + if (id < 0 || id >= idmap->map.size() || + (idmap->map[id] && idmap->map[id] != reinterpret_cast(elem))) + { + idmap_log_message("%s: invalid id %d\n", __func__, id); + return; + } + else { + idmap->map[id] = nullptr; + } + + idmap->freelist.append(id); + + if (idmap->free_idx_map) { + idmap->free_idx_map->add(id, idmap->freelist.size() - 1); + } + + check_idx_map(idmap); + + if (clear_id) { + BM_ELEM_CD_SET_INT(elem, idmap->cd_id_off[int(elem->head.htype)], BM_ID_NONE); + } +} + +template int BM_idmap_check_assign(BMIdMap *idmap, T *elem) +{ + int id = BM_ELEM_CD_GET_INT(elem, idmap->cd_id_off[int(elem->head.htype)]); + + if (id == BM_ID_NONE) { + id = BM_idmap_alloc(idmap, (BMElem *)elem); + } + + return id; +} + +/* Instantiate templates. */ +template void BM_idmap_assign(BMIdMap *idmap, BMElem *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMVert *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMEdge *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMLoop *elem, int id); +template void BM_idmap_assign(BMIdMap *idmap, BMFace *elem, int id); + +template int BM_idmap_check_assign(BMIdMap *idmap, BMElem *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMVert *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMEdge *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMLoop *elem); +template int BM_idmap_check_assign(BMIdMap *idmap, BMFace *elem); + +template int BM_idmap_alloc(BMIdMap *idmap, BMElem *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMVert *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMEdge *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMLoop *elem); +template int BM_idmap_alloc(BMIdMap *idmap, BMFace *elem); + +template void BM_idmap_release(BMIdMap *idmap, BMElem *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMVert *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMEdge *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMLoop *elem, bool clear_id); +template void BM_idmap_release(BMIdMap *idmap, BMFace *elem, bool clear_id); diff --git a/source/blender/bmesh/intern/bmesh_idmap.hh b/source/blender/bmesh/intern/bmesh_idmap.hh new file mode 100644 index 00000000000..dc590d76d53 --- /dev/null +++ b/source/blender/bmesh/intern/bmesh_idmap.hh @@ -0,0 +1,85 @@ +/* SPDX-FileCopyrightText: 2023 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_sys_types.h" + +/** \file + * \ingroup bmesh + * + * A simple self-contained element ID library. + * Stores IDs in integer attributes. + */ + +#include "BLI_compiler_compat.h" +#include "BLI_map.hh" +#include "BLI_sys_types.h" +#include "BLI_vector.hh" + +#include "bmesh.h" + +#define BM_ID_NONE 0 + +struct BMIdMap { + int flag; + + uint maxid; + int cd_id_off[15]; + BMesh *bm; + + blender::Vector map; /* ID -> Element map. */ + blender::Vector freelist; + + using FreeIdxMap = blender::Map; + + /* maps ids to their position within the freelist + only used if freelist is bigger then a certain size, + see FREELIST_HASHMAP_THRESHOLD_HIGH in bmesh_construct.c.*/ + FreeIdxMap *free_idx_map; +}; + +BMIdMap *BM_idmap_new(BMesh *bm, int elem_mask); +void BM_idmap_destroy(BMIdMap *idmap); + +/* Ensures idmap attributes exist. */ +bool BM_idmap_check_attributes(BMIdMap *idmap); + +/* Ensures every element has a unique ID. */ +void BM_idmap_check_ids(BMIdMap *idmap); + +/* Explicitly assign an ID. id cannot be BM_ID_NONE (zero). */ +template void BM_idmap_assign(BMIdMap *idmap, T *elem, int id); + +/* Automatically allocate an ID. */ +template int BM_idmap_alloc(BMIdMap *idmap, T *elem); + +/* Checks if an element needs an ID (it's id is BM_ID_NONE), + * and if so allocates one. + */ +template int BM_idmap_check_assign(BMIdMap *idmap, T *elem); + +/* Release an ID; if clear_id is true the id attribute for + * that element will be set to BM_ID_NONE. + */ +template void BM_idmap_release(BMIdMap *idmap, T *elem, bool clear_id = true); +void BM_idmap_release_id(BMIdMap *idmap, int id, bool clear_id = true); + +const char *BM_idmap_attr_name_get(int htype); + +/* Deletes all id attributes. */ +void BM_idmap_delete_attributes(BMesh *bm); +void BM_idmap_delete_attributes_mesh(Mesh *me); + +/* Elem -> ID. */ +template inline int BM_idmap_get_id(BMIdMap *map, T *elem) +{ + return BM_ELEM_CD_GET_INT(elem, map->cd_id_off[(int)elem->head.htype]); +} + +/* ID -> elem. */ +template inline T *BM_idmap_lookup(BMIdMap *map, int elem) +{ + return elem >= 0 ? reinterpret_cast(map->map[elem]) : NULL; +} diff --git a/source/blender/bmesh/intern/bmesh_log.cc b/source/blender/bmesh/intern/bmesh_log.cc index 82c7a4cf2db..10db9cf8f6e 100644 --- a/source/blender/bmesh/intern/bmesh_log.cc +++ b/source/blender/bmesh/intern/bmesh_log.cc @@ -5,415 +5,1702 @@ /** \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_ghash.h" -#include "BLI_listbase.h" -#include "BLI_math_vector.h" -#include "BLI_mempool.h" -#include "BLI_utildefines.h" - #include "BKE_customdata.h" +#include "BKE_lib_id.h" +#include "BKE_mesh.h" + +#include "DNA_customdata_types.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BLI_alloca.h" +#include "BLI_array.hh" +#include "BLI_asan.h" +#include "BLI_hash.hh" +#include "BLI_listbase_wrapper.hh" +#include "BLI_map.hh" +#include "BLI_math_vector.h" +#include "BLI_math_vector_types.hh" +#include "BLI_memory_utils.hh" +#include "BLI_mempool.h" +#include "BLI_set.hh" +#include "BLI_vector.hh" #include "bmesh.h" +#include "bmesh_idmap.hh" #include "bmesh_log.h" -#include "range_tree.h" -#include "BLI_strict_flags.h" +#include +#include +#include +#include +#include +#include +#include +#include -struct BMLogEntry { - BMLogEntry *next, *prev; +using blender::Array; +using blender::DynamicStackBuffer; +using blender::float3; +using blender::IndexRange; +using blender::Map; +using blender::Set; +using blender::Vector; - /* The following #GHash members map from an element ID to one of the log types above. */ +/* Avoid C++ runtime type ids. */ +enum class BMLogSetType { LOG_SET_DIFF, LOG_SET_FULL }; - /** Elements that were in the previous entry, but have been deleted. */ - GHash *deleted_verts; - GHash *deleted_faces; - /** Elements that were not in the previous entry, but are in the result of this entry. */ - GHash *added_verts; - GHash *added_faces; +/* `customdata_layout_is_same` and `customdata_copy_all_layout` are + * used internally by BMLog and probably don't have much use elsewhere. + * They ignore all copy-on-write semantics which makes sense since this + * is BMesh. + */ - /** Vertices whose coordinates, mask value, or hflag have changed. */ - GHash *modified_verts; - GHash *modified_faces; - - BLI_mempool *pool_verts; - BLI_mempool *pool_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. - */ - GHash *id_to_elem; - GHash *elem_to_id; - - /** All #BMLogEntrys, ordered from earliest to most recent. */ - ListBase 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 { - float co[3]; - float no[3]; - char hflag; - float mask; -}; - -struct BMLogFace { - uint v_ids[3]; - char hflag; -}; - -/************************* Get/set element IDs ************************/ - -/* bypass actual hashing, the keys don't overlap */ -#define logkey_hash BLI_ghashutil_inthash_p_simple -#define logkey_cmp BLI_ghashutil_intcmp - -/* Get the vertex's unique ID from the log */ -static uint bm_log_vert_id_get(BMLog *log, BMVert *v) +/* Returns true if the exact layout of a is the same as b. */ +static bool customdata_layout_is_same(const CustomData *data_a, const CustomData *data_b) { - BLI_assert(BLI_ghash_haskey(log->elem_to_id, v)); - return POINTER_AS_UINT(BLI_ghash_lookup(log->elem_to_id, v)); -} - -/* Set the vertex's unique ID in the log */ -static void bm_log_vert_id_set(BMLog *log, BMVert *v, uint id) -{ - void *vid = POINTER_FROM_UINT(id); - - BLI_ghash_reinsert(log->id_to_elem, vid, v, nullptr, nullptr); - BLI_ghash_reinsert(log->elem_to_id, v, vid, nullptr, nullptr); -} - -/* Get a vertex from its unique ID */ -static BMVert *bm_log_vert_from_id(BMLog *log, uint id) -{ - void *key = POINTER_FROM_UINT(id); - BLI_assert(BLI_ghash_haskey(log->id_to_elem, key)); - return static_cast(BLI_ghash_lookup(log->id_to_elem, key)); -} - -/* Get the face's unique ID from the log */ -static uint bm_log_face_id_get(BMLog *log, BMFace *f) -{ - BLI_assert(BLI_ghash_haskey(log->elem_to_id, f)); - return POINTER_AS_UINT(BLI_ghash_lookup(log->elem_to_id, f)); -} - -/* Set the face's unique ID in the log */ -static void bm_log_face_id_set(BMLog *log, BMFace *f, uint id) -{ - void *fid = POINTER_FROM_UINT(id); - - BLI_ghash_reinsert(log->id_to_elem, fid, f, nullptr, nullptr); - BLI_ghash_reinsert(log->elem_to_id, f, fid, nullptr, nullptr); -} - -/* Get a face from its unique ID */ -static BMFace *bm_log_face_from_id(BMLog *log, uint id) -{ - void *key = POINTER_FROM_UINT(id); - BLI_assert(BLI_ghash_haskey(log->id_to_elem, key)); - return static_cast(BLI_ghash_lookup(log->id_to_elem, key)); -} - -/************************ 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); + if (data_a->totlayer != data_b->totlayer || data_a->totsize != data_b->totsize) { + return false; } - 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); - } -} + for (int i : IndexRange(data_a->totlayer)) { + CustomDataLayer &layer_a = data_a->layers[i]; + CustomDataLayer &layer_b = data_b->layers[i]; -/* 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->co, v->co); - copy_v3_v3(lv->no, 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 = static_cast(BLI_mempool_alloc(entry->pool_verts)); - - 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 = static_cast(BLI_mempool_alloc(entry->pool_faces)); - 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; -} - -/************************ Helpers for undo/redo ***********************/ - -static void bm_log_verts_unmake(BMesh *bm, BMLog *log, GHash *verts) -{ - const int cd_vert_mask_offset = CustomData_get_offset(&bm->vdata, CD_PAINT_MASK); - - GHashIterator gh_iter; - GHASH_ITER (gh_iter, verts) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogVert *lv = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - uint id = POINTER_AS_UINT(key); - BMVert *v = bm_log_vert_from_id(log, id); - - /* Ensure the log has the final values of the vertex before - * deleting it */ - bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset); - - BM_vert_kill(bm, v); - } -} - -static void bm_log_faces_unmake(BMesh *bm, BMLog *log, GHash *faces) -{ - GHashIterator gh_iter; - GHASH_ITER (gh_iter, faces) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - uint id = POINTER_AS_UINT(key); - BMFace *f = bm_log_face_from_id(log, id); - BMEdge *e_tri[3]; - BMLoop *l_iter; - int i; - - l_iter = BM_FACE_FIRST_LOOP(f); - for (i = 0; i < 3; i++, l_iter = l_iter->next) { - e_tri[i] = l_iter->e; + if (!STREQ(layer_a.name, layer_b.name) || layer_a.type != layer_b.type || + layer_a.offset != layer_b.offset) + { + return false; } + } - /* Remove any unused edges */ - BM_face_kill(bm, f); - for (i = 0; i < 3; i++) { - if (BM_edge_is_wire(e_tri[i])) { - BM_edge_kill(bm, e_tri[i]); + return true; +} + +/* Copies all customdata layers without allocating data + * and without respect to type masks or NO_COPY/etc flags. + */ +static void customdata_copy_all_layout(const CustomData *source, CustomData *dest) +{ + *dest = *source; + dest->external = nullptr; + dest->pool = nullptr; + + if (source->layers) { + dest->layers = static_cast( + MEM_mallocN(sizeof(*dest->layers) * source->maxlayer, __func__)); + + for (int i : IndexRange(source->totlayer)) { + CustomDataLayer *layer = &dest->layers[i]; + + *layer = source->layers[i]; + + layer->data = nullptr; + layer->anonymous_id = nullptr; + layer->sharing_info = nullptr; + } + } + + CustomData_update_typemap(dest); +} + +void swap_customdata_block(CustomData *data_a, CustomData *data_b, void **block_a, void **block_b) +{ + if (!data_a->totsize || !data_b->totsize) { + return; + } + if (!*block_a) { + CustomData_bmesh_set_default(data_a, block_a); + } + if (!*block_b) { + CustomData_bmesh_set_default(data_a, block_b); + } + + DynamicStackBuffer<128> buffer(data_b->totsize, 4); + void *scratch = buffer.buffer(); + memset(scratch, 0, data_b->totsize); + + CustomData_bmesh_copy_data(data_a, data_b, *block_a, &scratch); + CustomData_bmesh_copy_data(data_b, data_a, *block_b, block_a); + CustomData_bmesh_copy_data(data_a, data_b, scratch, block_b); +} + +template struct BMLogElem { + int id; + void *customdata; + char flag; + +#ifdef WITH_ASAN + bool dead = false; + ~BMLogElem() + { + dead = true; + } +#endif + + void free(CustomData *domain) + { + if (customdata) { + CustomData_bmesh_free_block_data(domain, customdata); + } + } +}; + +struct BMLogVert : public BMLogElem { + float3 co; + float3 no; +}; + +struct BMLogEdge : public BMLogElem { + int v1 = BM_ID_NONE; + int v2 = BM_ID_NONE; +}; + +struct BMLogFace : public BMLogElem { + std::array verts; + std::array loop_customdata; + + void free(CustomData *domain, CustomData *loop_domain) + { + BMLogElem::free(domain); + + if (loop_customdata[0]) { + for (void *data : loop_customdata) { + CustomData_bmesh_free_block_data(loop_domain, data); } } } -} +}; -static void bm_log_verts_restore(BMesh *bm, BMLog *log, GHash *verts) -{ - const int cd_vert_mask_offset = CustomData_get_offset(&bm->vdata, CD_PAINT_MASK); +struct BMLogEntry; - GHashIterator gh_iter; - GHASH_ITER (gh_iter, verts) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogVert *lv = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - BMVert *v = BM_vert_create(bm, lv->co, 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->no); - bm_log_vert_id_set(log, v, POINTER_AS_UINT(key)); +static BMIdMap *entry_get_idmap(BMLogEntry *entry); + +struct BMLogSetBase { + BMLogSetType type; + BMLogEntry *entry = nullptr; /* Parent entry */ + + BMLogSetBase(BMLogEntry *_entry, BMLogSetType _type) : type(_type), entry(_entry) {} + + virtual ~BMLogSetBase() {} + + virtual const char *debug_name() + { + return ""; } -} + virtual void undo(BMesh * /*bm*/) = 0; + virtual void redo(BMesh * /*bm*/) = 0; +}; -static void bm_log_faces_restore(BMesh *bm, BMLog *log, GHash *faces) -{ - GHashIterator gh_iter; - const int cd_face_sets = CustomData_get_offset_named( - &bm->pdata, CD_PROP_INT32, ".sculpt_face_set"); +struct BMLogSetDiff : public BMLogSetBase { + BMLogSetDiff(BMLogEntry *entry) : BMLogSetBase(entry, BMLogSetType::LOG_SET_DIFF) {} - GHASH_ITER (gh_iter, faces) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogFace *lf = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - 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; + Map modified_verts; + Map modified_edges; + Map modified_faces; - 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, POINTER_AS_UINT(key)); + Map removed_verts; + Map removed_edges; + Map removed_faces; - /* Ensure face sets have valid values. Fixes #80174. */ - if (cd_face_sets != -1) { - BM_ELEM_CD_SET_INT(f, cd_face_sets, 1); + Map added_verts; + Map added_edges; + Map added_faces; + + const char *debug_name() override + { + return "Diff"; + } + + void add_vert(BMesh *bm, BMVert *v); + void remove_vert(BMesh *bm, BMVert *v); + void modify_vert(BMesh *bm, BMVert *v); + void add_edge(BMesh *bm, BMEdge *e); + void remove_edge(BMesh *bm, BMEdge *e); + void modify_edge(BMesh *bm, BMEdge *e); + void add_face(BMesh *bm, BMFace *f); + void remove_face(BMesh *bm, BMFace *f); + void modify_face(BMesh *bm, BMFace *f); + + void undo(BMesh *bm) override; + void redo(BMesh *bm) override; + + void restore_verts(BMesh *bm, blender::Map verts); + void remove_verts(BMesh *bm, blender::Map verts); + void swap_verts(BMesh *bm, blender::Map verts); + void restore_edges(BMesh *bm, blender::Map edges); + void remove_edges(BMesh *bm, blender::Map edges); + void swap_edges(BMesh *bm, blender::Map edges); + + void restore_faces(BMesh *bm, blender::Map faces); + void remove_faces(BMesh *bm, blender::Map faces); + void swap_faces(BMesh *bm, blender::Map faces); +}; + +struct BMLogSetFullMesh : public BMLogSetBase { + Mesh *mesh = nullptr; + + BMLogSetFullMesh(BMesh *bm, BMLogEntry *entry, BMIdMap *idmap) + : BMLogSetBase(entry, BMLogSetType::LOG_SET_FULL) + { + /* Validate id map. */ + BM_idmap_check_ids(idmap); + + BMeshToMeshParams params{}; + mesh = static_cast(BKE_id_new_nomain(ID_ME, nullptr)); + BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); + } + + ~BMLogSetFullMesh() + { + if (mesh) { + BKE_mesh_free_data_for_undo(mesh); + MEM_SAFE_FREE(mesh); } } + + const char *debug_name() override + { + return "Full"; + } + + void swap(BMesh *bm) + { + CustomData_MeshMasks cd_mask_extra{}; + + BMeshToMeshParams params{}; + + /* Don't swap in a new mesh if bmesh is empty, happens during undo. */ + Mesh *current_mesh = nullptr; + if (bm->totvert > 0) { + current_mesh = static_cast(BKE_id_new_nomain(ID_ME, nullptr)); + BM_mesh_bm_to_me(nullptr, bm, current_mesh, ¶ms); + } + + int shapenr = bm->shapenr; + + BMeshFromMeshParams params2 = {}; + params2.cd_mask_extra = cd_mask_extra; + params2.calc_face_normal = params2.add_key_index = params2.use_shapekey = false; + + BM_mesh_clear(bm); + BM_mesh_bm_from_me(bm, + mesh, /* Note: we stored shapekeys as customdata layers, + * that's why the shapekey params are false. + */ + ¶ms2); + + /* Regenerate ID map. */ + BMIdMap *idmap = entry_get_idmap(entry); + BM_idmap_check_ids(idmap); + + bm->shapenr = shapenr; + +#if 0 + bm->elem_index_dirty |= BM_VERT | BM_EDGE | BM_FACE; + bm->elem_table_dirty |= BM_VERT | BM_EDGE | BM_FACE; + + BM_mesh_elem_table_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); + BM_mesh_elem_index_ensure(bm, BM_VERT | BM_EDGE | BM_FACE); +#endif + + if (current_mesh) { + BKE_mesh_free_data_for_undo(mesh); + MEM_SAFE_FREE(mesh); + mesh = current_mesh; + } + } + + void undo(BMesh *bm) override + { + swap(bm); + } + + void redo(BMesh *bm) override + { + swap(bm); + } +}; + +static const char *get_elem_htype_str(int htype) +{ + switch (htype) { + case BM_VERT: + return "vertex"; + case BM_EDGE: + return "edge"; + case BM_LOOP: + return "loop"; + case BM_FACE: + return "face"; + } + + BLI_assert_unreachable(); + return "unknown type"; } -static void bm_log_vert_values_swap(BMesh *bm, BMLog *log, GHash *verts) +template constexpr char get_elem_type() { - const int cd_vert_mask_offset = CustomData_get_offset(&bm->vdata, CD_PAINT_MASK); + if constexpr (std::is_same_v) { + return BM_VERT; + } + else if constexpr (std::is_same_v) { + return BM_EDGE; + } + else if constexpr (std::is_same_v) { + return BM_LOOP; + } + else if constexpr (std::is_same_v) { + return BM_FACE; + } +} - GHashIterator gh_iter; - GHASH_ITER (gh_iter, verts) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogVert *lv = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - uint id = POINTER_AS_UINT(key); - BMVert *v = bm_log_vert_from_id(log, id); - float mask; +struct BMLogEntry { + BMLogEntry *next = nullptr, *prev = nullptr; + Vector> sets; + BLI_mempool *vpool = nullptr; + BLI_mempool *epool = nullptr; + BLI_mempool *fpool = nullptr; + + CustomData vdata; + CustomData edata; + CustomData ldata; + CustomData pdata; + + BMIdMap *idmap = nullptr; + + int cd_mask_offset = -1; + + BMLog *log = nullptr; + bool dead = false; + + bool cd_layout_changed = false; + + BMLogEntry(BMIdMap *_idmap, + const CustomData *src_vdata, + const CustomData *src_edata, + const CustomData *src_ldata, + const CustomData *src_pdata) + : idmap(_idmap) + { + vpool = BLI_mempool_create(sizeof(BMLogVert), 0, 512, BLI_MEMPOOL_ALLOW_ITER); + epool = BLI_mempool_create(sizeof(BMLogEdge), 0, 512, BLI_MEMPOOL_ALLOW_ITER); + fpool = BLI_mempool_create(sizeof(BMLogFace), 0, 512, BLI_MEMPOOL_ALLOW_ITER); + + customdata_copy_all_layout(src_vdata, &vdata); + customdata_copy_all_layout(src_edata, &edata); + customdata_copy_all_layout(src_ldata, &ldata); + customdata_copy_all_layout(src_pdata, &pdata); + + CustomData_bmesh_init_pool(&vdata, 0, BM_VERT); + CustomData_bmesh_init_pool(&edata, 0, BM_EDGE); + CustomData_bmesh_init_pool(&ldata, 0, BM_LOOP); + CustomData_bmesh_init_pool(&pdata, 0, BM_FACE); + + cd_mask_offset = CustomData_get_offset(&vdata, CD_PAINT_MASK); + } + + BMLogVert *find_logvert(BMVert *v) + { + int id = get_elem_id(v); + BMLogVert **lv = nullptr; + + for (int i = sets.size() - 1; i >= 0; i--) { + BMLogSetBase *set = sets[i].get(); + + if (set->type != BMLogSetType::LOG_SET_DIFF) { + continue; + } + + BMLogSetDiff *diff = static_cast(set); + + lv = diff->modified_verts.lookup_ptr(id); + if (lv) { + return *lv; + } + + lv = diff->added_verts.lookup_ptr(id); + if (lv) { + return *lv; + } + } + + return nullptr; + } + + ~BMLogEntry() + { + dead = true; + + BLI_mempool_iter iter; + + BLI_mempool_iternew(vpool, &iter); + BMLogVert *vert = static_cast(BLI_mempool_iterstep(&iter)); + for (; vert; vert = static_cast(BLI_mempool_iterstep(&iter))) { + vert->free(&vdata); + } + + BLI_mempool_iternew(epool, &iter); + BMLogEdge *edge = static_cast(BLI_mempool_iterstep(&iter)); + for (; edge; edge = static_cast(BLI_mempool_iterstep(&iter))) { + edge->free(&edata); + } + + BLI_mempool_iternew(fpool, &iter); + BMLogFace *face = static_cast(BLI_mempool_iterstep(&iter)); + for (; face; face = static_cast(BLI_mempool_iterstep(&iter))) { + face->free(&pdata, &ldata); + } + + BLI_mempool_destroy(vpool); + BLI_mempool_destroy(epool); + BLI_mempool_destroy(fpool); + + if (vdata.pool) { + BLI_mempool_destroy(vdata.pool); + } + if (edata.pool) { + BLI_mempool_destroy(edata.pool); + } + if (ldata.pool) { + BLI_mempool_destroy(ldata.pool); + } + if (pdata.pool) { + BLI_mempool_destroy(pdata.pool); + } + + CustomData_free(&vdata, 0); + CustomData_free(&edata, 0); + CustomData_free(&ldata, 0); + CustomData_free(&pdata, 0); + } + + template T *get_elem_from_id(int id) + { + if (id < 0 || id >= idmap->map.size()) { + return nullptr; + } + + T *elem = BM_idmap_lookup(idmap, id); + char htype = 0; + + if (!elem) { + return nullptr; + } + + if constexpr (std::is_same_v) { + htype = BM_VERT; + } + if constexpr (std::is_same_v) { + htype = BM_EDGE; + } + if constexpr (std::is_same_v) { + htype = BM_FACE; + } + + if (elem->head.htype != htype) { + printf("%s: error: expected %s, got %s; id: %d\n", + __func__, + get_elem_htype_str(htype), + get_elem_htype_str(elem->head.htype), + id); + return nullptr; + } + + return elem; + } + + template void assign_elem_id(BMesh * /*bm*/, T *elem, int id, bool check_unique) + { + if (check_unique && id >= 0 && id < idmap->map.size()) { + T *old = BM_idmap_lookup(idmap, id); + + if (old && old != elem) { + printf( + "id conflict in BMLogEntry::assign_elem_id; elem %p (a %s) is being reassinged to id " + "%d.\n", + elem, + get_elem_htype_str((int)elem->head.htype), + (int)id); + printf( + " elem %p (a %s) will get a new id\n", old, get_elem_htype_str((int)old->head.htype)); + + BM_idmap_assign(idmap, elem, id); + return; + } + } + + BM_idmap_assign(idmap, elem, id); + } + + template int get_elem_id(T *elem) + { + BM_idmap_check_assign(idmap, elem); + return BM_idmap_get_id(idmap, elem); + } + + void push_set(BMesh *bm, BMLogSetType type) + { + switch (type) { + case BMLogSetType::LOG_SET_DIFF: { + sets.append( + std::unique_ptr(static_cast(new BMLogSetDiff(this)))); + break; + } + case BMLogSetType::LOG_SET_FULL: + sets.append(std::unique_ptr( + static_cast(new BMLogSetFullMesh(bm, this, idmap)))); + break; + } + } + + BMLogSetDiff *current_diff_set(BMesh *bm) + { + if (sets.size() == 0 || sets[sets.size() - 1]->type != BMLogSetType::LOG_SET_DIFF) { + push_set(bm, BMLogSetType::LOG_SET_DIFF); + } + + return static_cast(sets[sets.size() - 1].get()); + } + + BMLogSetDiff *first_diff_set(BMesh *bm) + { + for (std::unique_ptr &set : sets) { + if (set->type == BMLogSetType::LOG_SET_DIFF) { + return static_cast(set.get()); + } + } + + return current_diff_set(bm); + } + + void update_logvert(BMesh *bm, BMVert *v, BMLogVert *lv) + { + CustomData_bmesh_copy_data(&bm->vdata, &vdata, v->head.data, &lv->customdata); + + lv->co = v->co; + lv->no = v->no; + lv->flag = v->head.hflag; + } + + void swap_logvert(BMesh *bm, int /*id*/, BMVert *v, BMLogVert *lv) + { + if (v->head.data && lv->customdata) { + swap_customdata_block(&vdata, &bm->vdata, &lv->customdata, &v->head.data); + } + + std::swap(v->head.hflag, lv->flag); swap_v3_v3(v->co, lv->co); swap_v3_v3(v->no, lv->no); - SWAP(char, v->head.hflag, lv->hflag); - mask = lv->mask; - lv->mask = vert_mask_get(v, cd_vert_mask_offset); - vert_mask_set(v, mask, cd_vert_mask_offset); + } + + void swap_logedge(BMesh *bm, int /*id*/, BMEdge *e, BMLogEdge *le) + { + if (e->head.data && le->customdata) { + swap_customdata_block(&edata, &bm->edata, &le->customdata, &e->head.data); + } + + std::swap(e->head.hflag, le->flag); + } + + void swap_logface(BMesh *bm, int /*id*/, BMFace *f, BMLogFace *lf) + { + if (f->head.data && lf->customdata) { + swap_customdata_block(&pdata, &bm->pdata, &lf->customdata, &f->head.data); + } + + if (f->len != lf->verts.size()) { + printf("%s: error: wrong length for face, was %d, should be %d\n", + __func__, + f->len, + (int)lf->verts.size()); + return; + } + + if (lf->loop_customdata[0]) { + BMLoop *l = f->l_first; + + int i = 0; + do { + swap_customdata_block(&ldata, &bm->ldata, &lf->loop_customdata[i], &l->head.data); + i++; + } while ((l = l->next) != f->l_first); + } + std::swap(f->head.hflag, lf->flag); + } + + template T *alloc_logelem(BLI_mempool *pool) + { + return static_cast(BLI_mempool_calloc(pool)); + } + + template void free_logelem(BLI_mempool *pool, T *elem) + { + BLI_mempool_free(pool, static_cast(elem)); + } + + BMLogVert *alloc_logvert(BMesh *bm, BMVert *v) + { + int id = get_elem_id(v); + BMLogVert *lv = alloc_logelem(vpool); + + lv->id = id; + + update_logvert(bm, v, lv); + + return lv; + } + + void free_logvert(BMLogVert *lv) + { + if (lv->customdata) { + BLI_mempool_free(vdata.pool, lv->customdata); + } + + free_logelem(vpool, lv); + } + + void load_vert(BMesh *bm, BMVert *v, BMLogVert *lv) + { + if (v->head.data && lv->customdata) { + CustomData_bmesh_copy_data(&vdata, &bm->vdata, lv->customdata, &v->head.data); + } + + v->head.hflag = lv->flag; + copy_v3_v3(v->co, lv->co); + copy_v3_v3(v->no, lv->no); + } + + BMLogEdge *alloc_logedge(BMesh *bm, BMEdge *e) + { + BMLogEdge *le = alloc_logelem(epool); + + le->id = get_elem_id(e); + le->v1 = get_elem_id(e->v1); + le->v2 = get_elem_id(e->v2); + + update_logedge(bm, e, le); + + return le; + } + + void update_logedge(BMesh *bm, BMEdge *e, BMLogEdge *le) + { + le->flag = e->head.hflag; + CustomData_bmesh_copy_data(&bm->edata, &edata, e->head.data, &le->customdata); + } + + void free_logedge(BMesh * /*bm*/, BMLogEdge *le) + { + if (le->customdata) { + BLI_mempool_free(edata.pool, le->customdata); + } + + free_logelem(epool, le); + } + + BMLogFace *alloc_logface(BMesh *bm, BMFace *f) + { + BMLogFace *lf = alloc_logelem(fpool); + + lf->id = get_elem_id(f); + lf->flag = f->head.hflag; + + CustomData_bmesh_copy_data(&bm->pdata, &pdata, f->head.data, &lf->customdata); + + BMLoop *l = f->l_first; + int i = 0; + do { + lf->verts[i] = get_elem_id(l->v); + void *loop_customdata = nullptr; + + if (l->head.data) { + CustomData_bmesh_copy_data(&bm->ldata, &ldata, l->head.data, &loop_customdata); + } + + lf->loop_customdata[i] = loop_customdata; + i++; + } while (i < 3 && (l = l->next) != f->l_first); + + return lf; + } + + void update_logface(BMesh *bm, BMLogFace *lf, BMFace *f) + { + lf->flag = f->head.hflag; + + CustomData_bmesh_copy_data(&bm->pdata, &pdata, f->head.data, &lf->customdata); + + if (f->len != lf->verts.size()) { + printf("%s: error: face length changed.\n", __func__); + return; + } + + BMLoop *l = f->l_first; + int i = 0; + do { + if (l->head.data) { + CustomData_bmesh_copy_data(&bm->ldata, &ldata, l->head.data, &lf->loop_customdata[i]); + } + + i++; + } while ((l = l->next) != f->l_first); + } + + void free_logface(BMesh * /*bm*/, BMLogFace *lf) + { + if (lf->loop_customdata[0]) { + for (int i = 0; i < lf->verts.size(); i++) { + BLI_mempool_free(ldata.pool, lf->loop_customdata[i]); + } + } + + if (lf->customdata) { + BLI_mempool_free(pdata.pool, lf->customdata); + } + + free_logelem(fpool, lf); + } + + void add_vert(BMesh *bm, BMVert *v) + { + current_diff_set(bm)->add_vert(bm, v); + } + + void remove_vert(BMesh *bm, BMVert *v) + { + current_diff_set(bm)->remove_vert(bm, v); + } + + void modify_vert(BMesh *bm, BMVert *v) + { + current_diff_set(bm)->modify_vert(bm, v); + } + + void add_edge(BMesh *bm, BMEdge *e) + { + current_diff_set(bm)->add_edge(bm, e); + } + + void remove_edge(BMesh *bm, BMEdge *e) + { + current_diff_set(bm)->remove_edge(bm, e); + } + + void modify_edge(BMesh *bm, BMEdge *e) + { + current_diff_set(bm)->modify_edge(bm, e); + } + + void add_face(BMesh *bm, BMFace *f) + { + current_diff_set(bm)->add_face(bm, f); + } + void remove_face(BMesh *bm, BMFace *f) + { + current_diff_set(bm)->remove_face(bm, f); + } + void modify_face(BMesh *bm, BMFace *f) + { + current_diff_set(bm)->modify_face(bm, f); + } + + void undo(BMesh *bm) + { + for (int i = sets.size() - 1; i >= 0; i--) { + sets[i]->undo(bm); + } + } + + void redo(BMesh *bm) + { + for (int i = 0; i < sets.size(); i++) { + sets[i]->redo(bm); + } + } +}; + +struct BMLog { + BMIdMap *idmap = nullptr; + BMLogEntry *current_entry = nullptr; + BMLogEntry *first_entry = nullptr; + int refcount = 1; + bool dead = false; + + BMLog(BMesh *bm) + { + idmap = BM_idmap_new(bm, BM_VERT | BM_EDGE | BM_FACE); + BM_idmap_check_ids(idmap); + } + + ~BMLog() {} + + void set_entries_idmap(BMIdMap *new_idmap) + { + idmap = new_idmap; + + BMLogEntry *entry = first_entry; + while (entry) { + entry->idmap = new_idmap; + entry = entry->next; + } + } + + bool free_all_entries() + { + BMLogEntry *entry = first_entry; + + if (!entry) { + return false; + } + + while (entry) { + BMLogEntry *next = entry->next; + + MEM_delete(entry); + entry = next; + } + + return true; + } + + BMLogEntry *push_entry(BMesh *bm) + { + BMLogEntry *entry = MEM_new( + "BMLogEntry", idmap, &bm->vdata, &bm->edata, &bm->ldata, &bm->pdata); + + /* Truncate undo list. */ + BMLogEntry *entry2 = current_entry ? current_entry->next : nullptr; + while (entry2) { + BMLogEntry *next = entry2->next; + MEM_delete(entry2); + + entry2 = next; + } + + entry->prev = current_entry; + entry->log = this; + entry->idmap = idmap; + + if (!first_entry) { + first_entry = entry; + } + else if (current_entry) { + current_entry->next = entry; + } + + current_entry = entry; + return entry; + } + + void load_entries(BMLogEntry *entry) + { + first_entry = current_entry = entry; + while (first_entry->prev) { + first_entry = first_entry->prev; + } + + entry = first_entry; + while (entry) { + entry->log = this; + entry->idmap = idmap; + entry = entry->next; + } + } + + void ensure_entry(BMesh *bm) + { + if (!current_entry) { + push_entry(bm); + } + } + + void add_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->add_vert(bm, v); + } + + void remove_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->remove_vert(bm, v); + } + + void modify_vert(BMesh *bm, BMVert *v) + { + ensure_entry(bm); + current_entry->modify_vert(bm, v); + } + + void add_edge(BMesh *bm, BMEdge *e) + { + ensure_entry(bm); + current_entry->add_edge(bm, e); + } + + void remove_edge(BMesh *bm, BMEdge *e) + { + ensure_entry(bm); + current_entry->remove_edge(bm, e); + } + + void modify_edge(BMesh *bm, BMEdge *e) + { + ensure_entry(bm); + current_entry->modify_edge(bm, e); + } + + void add_face(BMesh *bm, BMFace *f) + { + ensure_entry(bm); + current_entry->add_face(bm, f); + } + + void remove_face(BMesh *bm, BMFace *f) + { + ensure_entry(bm); + current_entry->remove_face(bm, f); + } + + void modify_face(BMesh *bm, BMFace *f) + { + ensure_entry(bm); + current_entry->modify_face(bm, f); + } + + void full_mesh(BMesh *bm) + { + ensure_entry(bm); + current_entry->push_set(bm, BMLogSetType::LOG_SET_FULL); + } + + void skip(int dir) + { + if (current_entry) { + current_entry = dir > 0 ? current_entry->next : current_entry->prev; + } + } + + void undo(BMesh *bm) + { + if (!current_entry) { + current_entry = first_entry; + while (current_entry->next) { + current_entry = current_entry->next; + } + } + + current_entry->undo(bm); + current_entry = current_entry->prev; + } + + void redo(BMesh *bm) + { + if (!current_entry) { + current_entry = first_entry; + } + else { + current_entry = current_entry->next; + } + + if (current_entry) { + current_entry->redo(bm); + } + } +}; + +void BMLogSetDiff::add_vert(BMesh *bm, BMVert *v) +{ + int id = entry->get_elem_id(v); + + BMLogVert *lv = nullptr; + if (added_verts.contains(id)) { + return; + } + + if (!lv) { + lv = entry->alloc_logvert(bm, v); + } + + added_verts.add(id, lv); +} + +void BMLogSetDiff::remove_vert(BMesh *bm, BMVert *v) +{ + int id = entry->get_elem_id(v); + + BMLogVert **added_lv = added_verts.lookup_ptr(id); + if (added_lv) { + added_verts.remove(id); + entry->free_logvert(*added_lv); + BM_idmap_release(entry->idmap, v); + return; + } + + BMLogVert *lv; + BMLogVert **modified_lv = modified_verts.lookup_ptr(id); + if (modified_lv) { + modified_verts.remove(id); + lv = *modified_lv; + } + else { + lv = entry->alloc_logvert(bm, v); + } + + removed_verts.add(id, lv); + BM_idmap_release(entry->idmap, v); +} + +void BMLogSetDiff::modify_vert(BMesh *bm, BMVert *v) +{ + int id = entry->get_elem_id(v); + if (modified_verts.contains(id)) { + return; + } + + BMLogVert **added_lv = added_verts.lookup_ptr(id); + if (added_lv) { + entry->update_logvert(bm, v, *added_lv); + } + else { + modified_verts.add(id, entry->alloc_logvert(bm, v)); } } -static void bm_log_face_values_swap(BMLog *log, GHash *faces) +void BMLogSetDiff::add_edge(BMesh *bm, BMEdge *e) { - GHashIterator gh_iter; - GHASH_ITER (gh_iter, faces) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - BMLogFace *lf = static_cast(BLI_ghashIterator_getValue(&gh_iter)); - uint id = POINTER_AS_UINT(key); - BMFace *f = bm_log_face_from_id(log, id); + int id = entry->get_elem_id(e); + BMLogEdge *le; - SWAP(char, f->head.hflag, lf->hflag); + le = entry->alloc_logedge(bm, e); + added_edges.add_or_modify( + id, [&](BMLogEdge **le_out) { *le_out = le; }, [&](BMLogEdge **le_out) { *le_out = le; }); +} + +void BMLogSetDiff::remove_edge(BMesh *bm, BMEdge *e) +{ + int id = entry->get_elem_id(e); + + if (added_edges.remove(id)) { + BM_idmap_release(entry->idmap, e); + return; + } + + BMLogEdge *le; + BMLogEdge **modified_le = modified_edges.lookup_ptr(id); + if (modified_le) { + le = *modified_le; + modified_edges.remove(id); + } + else { + le = entry->alloc_logedge(bm, e); + } + + removed_edges.add(id, le); + BM_idmap_release(entry->idmap, e); +} + +void BMLogSetDiff::modify_edge(BMesh *bm, BMEdge *e) +{ + int id = entry->get_elem_id(e); + + if (modified_edges.contains(id)) { + return; + } + + modified_edges.add(id, entry->alloc_logedge(bm, e)); +} + +void BMLogSetDiff::add_face(BMesh *bm, BMFace *f) +{ + BM_idmap_check_assign(entry->idmap, f); + + int id = entry->get_elem_id(f); + + if (added_faces.contains(id)) { + return; + } + + added_faces.add(id, entry->alloc_logface(bm, f)); +} + +void BMLogSetDiff::remove_face(BMesh *bm, BMFace *f) +{ + int id = entry->get_elem_id(f); + + if (added_faces.remove(id)) { + BM_idmap_release(entry->idmap, f); + return; + } + + BMLogFace *lf; + + if (BMLogFace **ptr = modified_faces.lookup_ptr(id)) { + lf = *ptr; + modified_faces.remove(id); + if (lf->verts.size() != f->len) { + entry->update_logface(bm, lf, f); + } + } + else { + lf = entry->alloc_logface(bm, f); + } + + removed_faces.add(id, lf); + BM_idmap_release(entry->idmap, f); +} + +void BMLogSetDiff::modify_face(BMesh *bm, BMFace *f) +{ + int id = entry->get_elem_id(f); + + BMLogFace *lf; + + if (BMLogFace **ptr = modified_faces.lookup_ptr(id)) { + lf = *ptr; + entry->update_logface(bm, lf, f); + } + else { + lf = entry->alloc_logface(bm, f); + + modified_faces.add(id, lf); } } -/**********************************************************************/ - -/* Assign unique IDs to all vertices and faces already in the BMesh */ -static void bm_log_assign_ids(BMesh *bm, BMLog *log) +void BMLogSetDiff::swap_verts(BMesh *bm, blender::Map verts) { - BMIter iter; - BMVert *v; - BMFace *f; + void *old_customdata = bm->vdata.pool ? BLI_mempool_alloc(bm->vdata.pool) : nullptr; - /* 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); + const int cd_id = entry->idmap->cd_id_off[BM_VERT]; + + for (BMLogVert *lv : verts.values()) { + BMVert *v = entry->get_elem_from_id(lv->id); + + if (!v) { + printf("modified_verts: invalid vertex %d\n", lv->id); + continue; + } + + if (old_customdata) { + memcpy(old_customdata, v->head.data, bm->vdata.totsize); + } + + entry->swap_logvert(bm, lv->id, v, lv); + + /* Ensure id wasn't mangled in customdata swap. */ + BM_ELEM_CD_SET_INT(v, cd_id, lv->id); } - /* 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); + if (old_customdata) { + BLI_mempool_free(bm->vdata.pool, old_customdata); } } -/* Allocate an empty log entry */ -static BMLogEntry *bm_log_entry_create() +void BMLogSetDiff::restore_verts(BMesh *bm, blender::Map verts) { - BMLogEntry *entry = static_cast(MEM_callocN(sizeof(BMLogEntry), __func__)); + for (BMLogVert *lv : verts.values()) { + BMVert *v = BM_vert_create(bm, lv->co, nullptr, BM_CREATE_NOP); - entry->deleted_verts = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->deleted_faces = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->added_verts = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->added_faces = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->modified_verts = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); - entry->modified_faces = BLI_ghash_new(logkey_hash, logkey_cmp, __func__); + v->head.hflag = lv->flag; + copy_v3_v3(v->no, lv->no); - entry->pool_verts = BLI_mempool_create(sizeof(BMLogVert), 0, 64, BLI_MEMPOOL_NOP); - entry->pool_faces = BLI_mempool_create(sizeof(BMLogFace), 0, 64, BLI_MEMPOOL_NOP); + CustomData_bmesh_copy_data(&entry->vdata, &bm->vdata, lv->customdata, &v->head.data); + entry->assign_elem_id(bm, v, lv->id, true); + } + + bm->elem_index_dirty |= BM_VERT | BM_EDGE; + bm->elem_table_dirty |= BM_VERT | BM_EDGE; +} + +void BMLogSetDiff::remove_verts(BMesh *bm, blender::Map verts) +{ + for (BMLogVert *lv : verts.values()) { + BMVert *v = entry->get_elem_from_id(lv->id); + + if (!v) { + printf("%s: Failed to find vertex %d\b", __func__, lv->id); + continue; + } + + BM_idmap_release(entry->idmap, v, false); + BM_vert_kill(bm, v); + } + + bm->elem_index_dirty |= BM_VERT | BM_EDGE; + bm->elem_table_dirty |= BM_VERT | BM_EDGE; +} + +void BMLogSetDiff::restore_edges(BMesh *bm, blender::Map edges) +{ + for (BMLogEdge *le : edges.values()) { + BMVert *v1 = entry->get_elem_from_id(le->v1); + BMVert *v2 = entry->get_elem_from_id(le->v2); + + if (!v1) { + printf("%s: missing vertex v1 %d\n", __func__, le->v1); + continue; + } + + if (!v2) { + printf("%s: missing vertex v2 %d\n", __func__, le->v2); + continue; + } + + BMEdge *e = BM_edge_create(bm, v1, v2, nullptr, BM_CREATE_NOP); + e->head.hflag = le->flag; + + CustomData_bmesh_copy_data(&entry->edata, &bm->edata, le->customdata, &e->head.data); + + entry->assign_elem_id(bm, e, le->id, true); + } +} + +void BMLogSetDiff::remove_edges(BMesh *bm, blender::Map edges) +{ + for (BMLogEdge *le : edges.values()) { + BMEdge *e = entry->get_elem_from_id(le->id); + + if (!e) { + printf("%s: failed to find edge %d\n", __func__, le->id); + continue; + } + + BM_idmap_release(entry->idmap, e, true); + BM_edge_kill(bm, e); + } +} + +void BMLogSetDiff::swap_edges(BMesh *bm, blender::Map edges) +{ + void *old_customdata = entry->edata.pool ? BLI_mempool_alloc(bm->edata.pool) : nullptr; + const int cd_id = entry->idmap->cd_id_off[BM_EDGE]; + + for (BMLogEdge *le : edges.values()) { + BMEdge *e = entry->get_elem_from_id(le->id); + + if (!e) { + printf("%s: failed to find edge %d\n", __func__, le->id); + continue; + } + + if (old_customdata) { + memcpy(old_customdata, e->head.data, bm->edata.totsize); + } + + entry->swap_logedge(bm, le->id, e, le); + + /* Ensure id wasn't mangled in customdata swap. */ + BM_ELEM_CD_SET_INT(e, cd_id, le->id); + } + + if (old_customdata) { + BLI_mempool_free(bm->edata.pool, old_customdata); + } +} + +void BMLogSetDiff::restore_faces(BMesh *bm, blender::Map faces) +{ + Vector verts; + + for (BMLogFace *lf : faces.values()) { + bool ok = true; + verts.clear(); + + for (int v_id : lf->verts) { + BMVert *v = entry->get_elem_from_id(v_id); + + if (!v) { + printf("%s: Error looking up vertex %d\n", __func__, v_id); + ok = false; + continue; + } + + verts.append(v); + } + + if (!ok) { + continue; + } + + BMFace *f = BM_face_create_verts(bm, verts.data(), verts.size(), nullptr, BM_CREATE_NOP, true); + f->head.hflag = lf->flag; + + CustomData_bmesh_copy_data(&entry->pdata, &bm->pdata, lf->customdata, &f->head.data); + entry->assign_elem_id(bm, f, lf->id, true); + + BMLoop *l = f->l_first; + int i = 0; + + if (lf->loop_customdata[0]) { + do { + CustomData_bmesh_copy_data( + &entry->ldata, &bm->ldata, lf->loop_customdata[i], &l->head.data); + i++; + } while ((l = l->next) != f->l_first); + } + } + + bm->elem_index_dirty |= BM_FACE; + bm->elem_table_dirty |= BM_FACE; +} + +void BMLogSetDiff::remove_faces(BMesh *bm, blender::Map faces) +{ + for (BMLogFace *lf : faces.values()) { + BMFace *f = entry->get_elem_from_id(lf->id); + + if (!f) { + printf("%s: error finding face %d\n", __func__, lf->id); + continue; + } + + BM_idmap_release(entry->idmap, f, true); + BM_face_kill(bm, f); + } + + bm->elem_index_dirty |= BM_FACE; + bm->elem_table_dirty |= BM_FACE; +} + +void BMLogSetDiff::swap_faces(BMesh *bm, blender::Map faces) +{ + void *old_customdata = entry->pdata.pool ? BLI_mempool_alloc(bm->pdata.pool) : nullptr; + + const int cd_id = entry->idmap->cd_id_off[BM_FACE]; + + for (BMLogFace *lf : faces.values()) { + BMFace *f = entry->get_elem_from_id(lf->id); + + if (!f) { + printf("modified_faces: invalid face %d\n", lf->id); + continue; + } + + if (old_customdata) { + memcpy(old_customdata, f->head.data, bm->pdata.totsize); + } + + entry->swap_logface(bm, lf->id, f, lf); + + /* Ensure id wasn't mangled in customdata swap. */ + BM_ELEM_CD_SET_INT(f, cd_id, lf->id); + } + + if (old_customdata) { + BLI_mempool_free(bm->pdata.pool, old_customdata); + } +} + +void BMLogSetDiff::undo(BMesh *bm) +{ + remove_faces(bm, added_faces); + remove_edges(bm, added_edges); + remove_verts(bm, added_verts); + + restore_verts(bm, removed_verts); + restore_edges(bm, removed_edges); + restore_faces(bm, removed_faces); + + swap_faces(bm, modified_faces); + swap_edges(bm, modified_edges); + swap_verts(bm, modified_verts); +} + +void BMLogSetDiff::redo(BMesh *bm) +{ + remove_faces(bm, removed_faces); + remove_edges(bm, removed_edges); + remove_verts(bm, removed_verts); + + restore_verts(bm, added_verts); + restore_edges(bm, added_edges); + restore_faces(bm, added_faces); + + swap_faces(bm, modified_faces); + swap_edges(bm, modified_edges); + swap_verts(bm, modified_verts); +} + +static BMIdMap *entry_get_idmap(BMLogEntry *entry) +{ + return entry->idmap; +} + +BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry, bool for_redo) +{ + BMLog *log = BM_log_create(bm); + log->load_entries(entry); + + if (for_redo) { + log->current_entry = entry->prev; + } + + return log; +} + +BMLog *BM_log_create(BMesh *bm) +{ + BMLog *log = MEM_new("BMLog", bm); + + return log; +} + +bool BM_log_is_dead(BMLog *log) +{ + return log->dead; +} + +bool BM_log_free(BMesh *bm, BMLog *log) +{ + BMLogEntry *entry = log->first_entry; + + while (entry) { + entry->log = nullptr; + entry = entry->next; + } + + /* Delete ID attribute layers. */ + BM_idmap_delete_attributes(bm); + + MEM_delete(log); + return true; +} + +BMLogEntry *BM_log_entry_add_delta_set(BMesh *bm, BMLog *log) +{ + if (!log->current_entry) { + log->push_entry(bm); + } + else { + log->current_entry->push_set(bm, BMLogSetType::LOG_SET_DIFF); + } + + return log->current_entry; +} + +BMLogEntry *BM_log_entry_add(BMesh *bm, BMLog *log) +{ + log->push_entry(bm)->push_set(bm, BMLogSetType::LOG_SET_DIFF); + return log->current_entry; +} + +void BM_log_vert_added(BMesh *bm, BMLog *log, BMVert *v) +{ + /* Forcibly allocate new ID for v, presumably + * it's existing one is garbage. + */ + BM_idmap_alloc(log->idmap, v); + log->add_vert(bm, v); +} + +void BM_log_vert_removed(BMesh *bm, BMLog *log, BMVert *v) +{ + log->remove_vert(bm, v); +} + +void BM_log_vert_before_modified(BMesh *bm, BMLog *log, BMVert *v) +{ + log->modify_vert(bm, v); +} + +BMLogEntry *BM_log_entry_check_customdata(BMesh *bm, BMLog *log) +{ + BMLogEntry *entry = log->current_entry; + + if (!entry) { + return BM_log_entry_add(bm, log); + } + + const CustomData *cd1[4] = {&bm->vdata, &bm->edata, &bm->ldata, &bm->pdata}; + const CustomData *cd2[4] = {&entry->vdata, &entry->edata, &entry->ldata, &entry->pdata}; + + for (int i = 0; i < 4; i++) { + if (!customdata_layout_is_same(cd1[i], cd2[i])) { + printf("%s: Customdata changed during stroke.\n", __func__); + + entry->cd_layout_changed = true; + return BM_log_entry_add_delta_set(bm, log); + } + } 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) +void BM_log_edge_added(BMesh *bm, BMLog *log, BMEdge *e) { - BLI_ghash_free(entry->deleted_verts, nullptr, nullptr); - BLI_ghash_free(entry->deleted_faces, nullptr, nullptr); - BLI_ghash_free(entry->added_verts, nullptr, nullptr); - BLI_ghash_free(entry->added_faces, nullptr, nullptr); - BLI_ghash_free(entry->modified_verts, nullptr, nullptr); - BLI_ghash_free(entry->modified_faces, nullptr, nullptr); - - BLI_mempool_destroy(entry->pool_verts); - BLI_mempool_destroy(entry->pool_faces); + /* Forcibly allocate new ID for e presumably + * it's existing one is garbage. + */ + BM_idmap_alloc(log->idmap, e); + log->add_edge(bm, e); +} +void BM_log_edge_modified(BMesh *bm, BMLog *log, BMEdge *e) +{ + log->modify_edge(bm, e); +} +void BM_log_edge_removed(BMesh *bm, BMLog *log, BMEdge *e) +{ + log->remove_edge(bm, e); } -static void bm_log_id_ghash_retake(RangeTreeUInt *unused_ids, GHash *id_ghash) +void BM_log_face_added(BMesh *bm, BMLog *log, BMFace *f) { - GHashIterator gh_iter; + /* Forcibly allocate new ID for f, presumably + * it's existing one is garbage. + */ + BM_idmap_alloc(log->idmap, f); + log->add_face(bm, f); +} +void BM_log_face_modified(BMesh *bm, BMLog *log, BMFace *f) +{ + log->modify_face(bm, f); +} +void BM_log_face_removed(BMesh *bm, BMLog *log, BMFace *f) +{ + log->remove_face(bm, f); +} - GHASH_ITER (gh_iter, id_ghash) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - uint id = POINTER_AS_UINT(key); +void BM_log_full_mesh(BMesh *bm, BMLog *log) +{ + log->full_mesh(bm); +} - range_tree_uint_retake(unused_ids, id); +BMVert *BM_log_id_vert_get(BMesh * /*bm*/, BMLog *log, uint id) +{ + return BM_idmap_lookup(log->idmap, id); +} + +uint BM_log_vert_id_get(BMesh * /*bm*/, BMLog *log, BMVert *v) +{ + return BM_idmap_get_id(log->idmap, v); +} + +BMFace *BM_log_id_face_get(BMesh * /*bm*/, BMLog *log, uint id) +{ + return BM_idmap_lookup(log->idmap, id); +} + +uint BM_log_face_id_get(BMesh * /*bm*/, BMLog *log, BMFace *f) +{ + return BM_idmap_get_id(log->idmap, f); +} + +void BM_log_undo(BMesh *bm, BMLog *log) +{ + log->undo(bm); +} + +void BM_log_redo(BMesh *bm, BMLog *log) +{ + log->redo(bm); +} + +void BM_log_undo_skip(BMesh * /*bm*/, BMLog *log) +{ + log->skip(-1); +} + +void BM_log_redo_skip(BMesh * /*bm*/, BMLog *log) +{ + log->skip(1); +} + +BMLogEntry *BM_log_entry_prev(BMLogEntry *entry) +{ + return entry->prev; +} + +BMLogEntry *BM_log_entry_next(BMLogEntry *entry) +{ + return entry->next; +} + +void BM_log_set_current_entry(BMLog *log, BMLogEntry *entry) +{ + log->current_entry = entry; +} + +bool BM_log_entry_drop(BMLogEntry *entry) +{ + if (entry->prev) { + entry->prev->next = entry->next; } + if (entry->next) { + entry->next->prev = entry->prev; + } + + if (entry->log) { + if (entry == entry->log->current_entry) { + entry->log->current_entry = entry->prev; + } + + if (entry == entry->log->first_entry) { + entry->log->first_entry = entry->next; + } + } + + MEM_delete(entry); + return true; +} + +void BM_log_print_entry(BMLogEntry *entry) +{ + printf("BMLogEntry: %p", entry); + printf("\n"); +} + +void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **co, const float **no) +{ + BLI_assert(log->current_entry); + + BMLogVert *lv = log->current_entry->find_logvert(v); + BLI_assert(lv); + + *co = lv->co; + *no = lv->no; +} + +const float *BM_log_original_vert_co(BMLog *log, BMVert *v) +{ + BLI_assert(log->current_entry); + + BMLogVert *lv = log->current_entry->find_logvert(v); + BLI_assert(lv); + + return lv->co; +} + +const float *BM_log_original_vert_no(BMLog *log, BMVert *v) +{ + BLI_assert(log->current_entry); + + BMLogVert *lv = log->current_entry->find_logvert(v); + BLI_assert(lv); + + return lv->no; +} + +float BM_log_original_mask(BMLog *log, BMVert *v) +{ + BLI_assert(log->current_entry); + + if (log->current_entry->cd_mask_offset == -1) { + return 0.0f; + } + + BMLogVert *lv = log->current_entry->find_logvert(v); + BLI_assert(lv); + + BMElem log_v; + log_v.head.data = lv->customdata; + + return BM_ELEM_CD_GET_FLOAT(&log_v, log->current_entry->cd_mask_offset); } static int uint_compare(const void *a_v, const void *b_v) @@ -447,135 +1734,17 @@ static GHash *bm_log_compress_ids_to_indices(uint *ids, uint totid) return map; } -/* Release all ID keys in id_ghash */ -static void bm_log_id_ghash_release(BMLog *log, GHash *id_ghash) -{ - GHashIterator gh_iter; - - GHASH_ITER (gh_iter, id_ghash) { - void *key = BLI_ghashIterator_getKey(&gh_iter); - uint id = POINTER_AS_UINT(key); - range_tree_uint_release(log->unused_ids, id); - } -} - -/***************************** Public API *****************************/ - -BMLog *BM_log_create(BMesh *bm) -{ - BMLog *log = static_cast(MEM_callocN(sizeof(*log), __func__)); - const uint reserve_num = uint(bm->totvert + bm->totface); - - log->unused_ids = range_tree_uint_alloc(0, uint(-1)); - log->id_to_elem = BLI_ghash_new_ex(logkey_hash, logkey_cmp, __func__, reserve_num); - log->elem_to_id = BLI_ghash_ptr_new_ex(__func__, 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 */ - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->added_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->added_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_faces); - - /* delete entries to avoid releasing ids in node cleanup */ - BLI_ghash_clear(entry->deleted_verts, nullptr, nullptr); - BLI_ghash_clear(entry->deleted_faces, nullptr, nullptr); - BLI_ghash_clear(entry->added_verts, nullptr, nullptr); - BLI_ghash_clear(entry->added_faces, nullptr, nullptr); - BLI_ghash_clear(entry->modified_verts, nullptr, nullptr); - } -} - -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(log->entries.last); - while (entry->next) { - entry = entry->next; - log->entries.last = entry; - } - } - - for (entry = static_cast(log->entries.first); entry; entry = entry->next) { - entry->log = log; - - /* Take all used IDs */ - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->deleted_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->added_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->added_faces); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_verts); - bm_log_id_ghash_retake(log->unused_ids, entry->modified_faces); - } - - return log; -} - -void BM_log_free(BMLog *log) -{ - if (log->unused_ids) { - range_tree_uint_free(log->unused_ids); - } - - if (log->id_to_elem) { - BLI_ghash_free(log->id_to_elem, nullptr, nullptr); - } - - if (log->elem_to_id) { - BLI_ghash_free(log->elem_to_id, nullptr, nullptr); - } - - /* Clear the BMLog references within each entry, but do not free - * the entries themselves */ - LISTBASE_FOREACH (BMLogEntry *, entry, &log->entries) { - entry->log = nullptr; - } - - MEM_freeN(log); -} - -int BM_log_length(const BMLog *log) -{ - return BLI_listbase_count(&log->entries); -} - void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log) { uint *varr; + uint *earr; uint *farr; GHash *id_to_idx; BMIter bm_iter; BMVert *v; + BMEdge *e; BMFace *f; uint i; @@ -583,451 +1752,55 @@ void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log) /* Put all vertex IDs into an array */ varr = static_cast(MEM_mallocN(sizeof(int) * size_t(bm->totvert), __func__)); BM_ITER_MESH_INDEX (v, &bm_iter, bm, BM_VERTS_OF_MESH, i) { - varr[i] = bm_log_vert_id_get(log, v); + varr[i] = BM_idmap_get_id(log->idmap, v); + } + + /* Put all edge IDs into an array */ + earr = static_cast(MEM_mallocN(sizeof(int) * size_t(bm->totedge), __func__)); + BM_ITER_MESH_INDEX (e, &bm_iter, bm, BM_EDGES_OF_MESH, i) { + earr[i] = BM_idmap_get_id(log->idmap, e); } /* Put all face IDs into an array */ farr = static_cast(MEM_mallocN(sizeof(int) * size_t(bm->totface), __func__)); BM_ITER_MESH_INDEX (f, &bm_iter, bm, BM_FACES_OF_MESH, i) { - farr[i] = bm_log_face_id_get(log, f); + farr[i] = BM_idmap_get_id(log->idmap, f); } /* Create BMVert index remap array */ id_to_idx = bm_log_compress_ids_to_indices(varr, uint(bm->totvert)); BM_ITER_MESH_INDEX (v, &bm_iter, bm, BM_VERTS_OF_MESH, i) { - const uint id = bm_log_vert_id_get(log, v); + const uint id = BM_idmap_get_id(log->idmap, v); const void *key = POINTER_FROM_UINT(id); const void *val = BLI_ghash_lookup(id_to_idx, key); varr[i] = POINTER_AS_UINT(val); } BLI_ghash_free(id_to_idx, nullptr, nullptr); + /* Create BMEdge index remap array */ + id_to_idx = bm_log_compress_ids_to_indices(earr, uint(bm->totedge)); + BM_ITER_MESH_INDEX (e, &bm_iter, bm, BM_EDGES_OF_MESH, i) { + const uint id = BM_idmap_get_id(log->idmap, e); + const void *key = POINTER_FROM_UINT(id); + const void *val = BLI_ghash_lookup(id_to_idx, key); + earr[i] = POINTER_AS_UINT(val); + } + BLI_ghash_free(id_to_idx, nullptr, nullptr); + /* Create BMFace index remap array */ id_to_idx = bm_log_compress_ids_to_indices(farr, uint(bm->totface)); BM_ITER_MESH_INDEX (f, &bm_iter, bm, BM_FACES_OF_MESH, i) { - const uint id = bm_log_face_id_get(log, f); + const uint id = BM_idmap_get_id(log->idmap, f); const void *key = POINTER_FROM_UINT(id); const void *val = BLI_ghash_lookup(id_to_idx, key); farr[i] = POINTER_AS_UINT(val); } BLI_ghash_free(id_to_idx, nullptr, nullptr); - BM_mesh_remap(bm, varr, nullptr, farr); + BM_mesh_remap(bm, varr, earr, farr); + BM_idmap_check_ids(log->idmap); - MEM_freeN(varr); - MEM_freeN(farr); -} - -BMLogEntry *BM_log_entry_add(BMLog *log) -{ - /* WARNING: this is now handled by the UndoSystem: BKE_UNDOSYS_TYPE_SCULPT - * freeing here causes unnecessary complications. */ - BMLogEntry *entry; -#if 0 - /* Delete any entries after the current one */ - entry = log->current_entry; - if (entry) { - BMLogEntry *next; - for (entry = entry->next; entry; entry = next) { - next = entry->next; - bm_log_entry_free(entry); - BLI_freelinkN(&log->entries, entry); - } - } -#endif - - /* Create and append the new entry */ - 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_freeN(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. */ - bm_log_id_ghash_release(log, entry->added_faces); - bm_log_id_ghash_release(log, entry->added_verts); - } - 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_freelinkN(&log->entries, 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(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; - BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - void **val_p; - - /* Find or create the BMLogVert entry */ - if ((lv = static_cast(BLI_ghash_lookup(entry->added_verts, key)))) { - bm_log_vert_bmvert_copy(lv, v, cd_vert_mask_offset); - } - else if (!BLI_ghash_ensure_p(entry->modified_verts, key, &val_p)) { - lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset); - *val_p = lv; - } -} - -void BM_log_vert_added(BMLog *log, BMVert *v, const int cd_vert_mask_offset) -{ - BMLogVert *lv; - uint v_id = range_tree_uint_take_any(log->unused_ids); - void *key = POINTER_FROM_UINT(v_id); - - bm_log_vert_id_set(log, v, v_id); - lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset); - BLI_ghash_insert(log->current_entry->added_verts, key, lv); -} - -void BM_log_face_modified(BMLog *log, BMFace *f) -{ - BMLogFace *lf; - uint f_id = bm_log_face_id_get(log, f); - void *key = POINTER_FROM_UINT(f_id); - - lf = bm_log_face_alloc(log, f); - BLI_ghash_insert(log->current_entry->modified_faces, key, lf); -} - -void BM_log_face_added(BMLog *log, BMFace *f) -{ - BMLogFace *lf; - uint f_id = range_tree_uint_take_any(log->unused_ids); - void *key = POINTER_FROM_UINT(f_id); - - /* Only triangles are supported for now */ - BLI_assert(f->len == 3); - - bm_log_face_id_set(log, f, f_id); - lf = bm_log_face_alloc(log, f); - BLI_ghash_insert(log->current_entry->added_faces, key, lf); -} - -void BM_log_vert_removed(BMLog *log, BMVert *v, const int cd_vert_mask_offset) -{ - BMLogEntry *entry = log->current_entry; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - /* if it has a key, it shouldn't be nullptr */ - BLI_assert(!!BLI_ghash_lookup(entry->added_verts, key) == - !!BLI_ghash_haskey(entry->added_verts, key)); - - if (BLI_ghash_remove(entry->added_verts, key, nullptr, nullptr)) { - range_tree_uint_release(log->unused_ids, v_id); - } - else { - BMLogVert *lv, *lv_mod; - - lv = bm_log_vert_alloc(log, v, cd_vert_mask_offset); - BLI_ghash_insert(entry->deleted_verts, key, lv); - - /* If the vertex was modified before deletion, ensure that the - * original vertex values are stored */ - if ((lv_mod = static_cast(BLI_ghash_lookup(entry->modified_verts, key)))) { - (*lv) = (*lv_mod); - BLI_ghash_remove(entry->modified_verts, key, nullptr, nullptr); - } - } -} - -void BM_log_face_removed(BMLog *log, BMFace *f) -{ - BMLogEntry *entry = log->current_entry; - uint f_id = bm_log_face_id_get(log, f); - void *key = POINTER_FROM_UINT(f_id); - - /* if it has a key, it shouldn't be nullptr */ - BLI_assert(!!BLI_ghash_lookup(entry->added_faces, key) == - !!BLI_ghash_haskey(entry->added_faces, key)); - - if (BLI_ghash_remove(entry->added_faces, key, nullptr, nullptr)) { - range_tree_uint_release(log->unused_ids, f_id); - } - else { - BMLogFace *lf; - - lf = bm_log_face_alloc(log, f); - BLI_ghash_insert(entry->deleted_faces, key, lf); - } -} - -void BM_log_all_added(BMesh *bm, BMLog *log) -{ - const int cd_vert_mask_offset = CustomData_get_offset(&bm->vdata, CD_PAINT_MASK); - BMIter bm_iter; - BMVert *v; - BMFace *f; - - /* avoid unnecessary resizing on initialization */ - if (BLI_ghash_len(log->current_entry->added_verts) == 0) { - BLI_ghash_reserve(log->current_entry->added_verts, uint(bm->totvert)); - } - - if (BLI_ghash_len(log->current_entry->added_faces) == 0) { - BLI_ghash_reserve(log->current_entry->added_faces, uint(bm->totface)); - } - - /* 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); - } - - /* 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(&bm->vdata, CD_PAINT_MASK); - BMIter bm_iter; - BMVert *v; - BMFace *f; - - /* Log deletion of all faces */ - BM_ITER_MESH (f, &bm_iter, bm, BM_FACES_OF_MESH) { - BM_log_face_removed(log, f); - } - - /* 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_original_vert_co(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv->co; -} - -const float *BM_log_original_vert_no(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv->no; -} - -float BM_log_original_mask(BMLog *log, BMVert *v) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - return lv->mask; -} - -void BM_log_original_vert_data(BMLog *log, BMVert *v, const float **r_co, const float **r_no) -{ - BMLogEntry *entry = log->current_entry; - const BMLogVert *lv; - uint v_id = bm_log_vert_id_get(log, v); - void *key = POINTER_FROM_UINT(v_id); - - BLI_assert(entry); - - BLI_assert(BLI_ghash_haskey(entry->modified_verts, key)); - - lv = static_cast(BLI_ghash_lookup(entry->modified_verts, key)); - *r_co = lv->co; - *r_no = lv->no; -} - -/************************ Debugging and Testing ***********************/ - -BMLogEntry *BM_log_current_entry(BMLog *log) -{ - return log->current_entry; -} - -RangeTreeUInt *BM_log_unused_ids(BMLog *log) -{ - return log->unused_ids; -} - -#if 0 -/* 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 = log->entries.first, i = 1; entry; entry = entry->next, i++) { - printf(" % 2d: [%p]%s\n", i, entry, (entry == log->current_entry) ? current : ""); - } -} -#endif - -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(BLI_ghash_len(entry->added_verts)), - int(BLI_ghash_len(entry->deleted_verts)), - int(BLI_ghash_len(entry->modified_verts))); - printf("f | added: %d, removed: %d, modified: %d\n", - int(BLI_ghash_len(entry->added_faces)), - int(BLI_ghash_len(entry->deleted_faces)), - int(BLI_ghash_len(entry->modified_faces))); - printf("}\n"); + MEM_SAFE_FREE(varr); + MEM_SAFE_FREE(earr); + MEM_SAFE_FREE(farr); } diff --git a/source/blender/bmesh/intern/bmesh_log.h b/source/blender/bmesh/intern/bmesh_log.h index f166b6d5b62..107f85b4013 100644 --- a/source/blender/bmesh/intern/bmesh_log.h +++ b/source/blender/bmesh/intern/bmesh_log.h @@ -4,21 +4,46 @@ #pragma once +#include "BLI_sys_types.h" + /** \file * \ingroup bmesh + * + * `BMLog` is the undo system used by DynTopo. Changes + * to a `BMesh` are stored incrementally. + * + * The following operations are supported for logging: + * - Adding and removing vertices + * - Adding and removing edges + * - Adding and removing faces + * - Attribute changes. + * - Element header flags. + * + * Internal details: + * + * Each sculpt undo step owns a pointer to a `BMLogEntry`. + * Every `BMLogEntry` in turn has a list of log sets. + * + * A log set is a subclass of `BMLogSetBase` and can be + * either a delta set (`BMLogSetDiff`) or a full mesh + * (BMLogSetFull). + * + * Particuarly complex mesh operations can sometimes benefit from + * having a clean `BMLogSetDiff` set, this helps avoid corrupting + * element IDs. This can be done with `BM_log_entry_add_delta_set`. + * + * To log a complete mesh, use `BM_log_full_mesh`. */ -struct BMFace; -struct BMVert; struct BMesh; -struct RangeTreeUInt; - -typedef struct BMLog BMLog; -typedef struct BMLogEntry BMLogEntry; - -#ifdef __cplusplus -extern "C" { -#endif +struct BMEdge; +struct BMElem; +struct BMFace; +struct BMIdMap; +struct BMLog; +struct BMLogEntry; +struct BMVert; +struct CustomData; /** * Allocate, initialize, and assign a new BMLog. @@ -34,17 +59,13 @@ BMLog *BM_log_create(BMesh *bm); * The unused IDs field of the log will be initialized by taking all * keys from all GHashes in the log entry. */ -BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry); +BMLog *BM_log_from_existing_entries_create(BMesh *bm, BMLogEntry *entry, bool for_redo); -/** - * Free all the data in a BMLog including the log itself. +/* Frees the BMLog. + * Note: The actual BMLogEntry instances are not freed they're + * owned by SculptUndoNode, not BMLog. */ -void BM_log_free(BMLog *log); - -/** - * Get the number of log entries. - */ -int BM_log_length(const BMLog *log); +bool BM_log_free(BMesh *bm, BMLog *log); /** Apply a consistent ordering to BMesh vertices and faces. */ void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log); @@ -60,10 +81,7 @@ void BM_log_mesh_elems_reorder(BMesh *bm, BMLog *log); * * In either case, 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); +BMLogEntry *BM_log_entry_add(BMesh *bm, BMLog *log); /** * Remove an entry from the log. @@ -74,7 +92,19 @@ void BM_log_cleanup_entry(BMLogEntry *entry); * 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); +bool BM_log_entry_drop(BMLogEntry *entry); + +/* + * Add a new delta set to the current log entry. If no entry + * exists one will be created. Returns current log entry. + */ +BMLogEntry *BM_log_entry_add_delta_set(BMesh *bm, BMLog *log); + +/* Check if customdata layout has changed. If it has a new + * subentry will be pushed so any further logging will have + * the correct customdata. + */ +BMLogEntry *BM_log_entry_check_customdata(BMesh *bm, BMLog *log); /** * Undo one #BMLogEntry. @@ -90,6 +120,11 @@ void BM_log_undo(BMesh *bm, BMLog *log); */ void BM_log_redo(BMesh *bm, BMLog *log); +/* Skip one BMLogEntry. */ +void BM_log_undo_skip(BMesh *bm, BMLog *log); +/* Skip one BMLogEntry. */ +void BM_log_redo_skip(BMesh *bm, BMLog *log); + /** * Log a vertex before it is modified. * @@ -114,7 +149,7 @@ void BM_log_redo(BMesh *bm, BMLog *log); * state so that a subsequent redo operation will restore the newer * vertex state. */ -void BM_log_vert_before_modified(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); +void BM_log_vert_before_modified(BMesh *bm, BMLog *log, BMVert *v); /** * Log a new vertex as added to the #BMesh. @@ -123,7 +158,10 @@ void BM_log_vert_before_modified(BMLog *log, struct BMVert *v, int cd_vert_mask_ * 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, struct BMVert *v, int cd_vert_mask_offset); +void BM_log_vert_added(BMesh *bm, BMLog *log, BMVert *v); + +/* Log a new edge as added to the BMesh */ +void BM_log_edge_added(BMesh *bm, BMLog *log, BMEdge *e); /** * Log a face before it is modified. @@ -131,7 +169,7 @@ void BM_log_vert_added(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); * This is intended to handle only header flags and we always * assume face has been added before. */ -void BM_log_face_modified(BMLog *log, struct BMFace *f); +void BM_log_face_modified(BMesh *bm, BMLog *log, BMFace *f); /** * Log a new face as added to the #BMesh. @@ -140,7 +178,7 @@ void BM_log_face_modified(BMLog *log, struct BMFace *f); * 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, struct BMFace *f); +void BM_log_face_added(BMesh *bm, BMLog *log, BMFace *f); /** * Log a vertex as removed from the #BMesh. @@ -159,7 +197,7 @@ void BM_log_face_added(BMLog *log, struct BMFace *f); * 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, struct BMVert *v, int cd_vert_mask_offset); +void BM_log_vert_removed(BMesh *bm, BMLog *log, BMVert *v); /** * Log a face as removed from the #BMesh. @@ -175,15 +213,12 @@ void BM_log_vert_removed(BMLog *log, struct BMVert *v, int cd_vert_mask_offset); * its ID and the value containing everything needed to reconstruct * that face. */ -void BM_log_face_removed(BMLog *log, struct BMFace *f); +void BM_log_face_removed(BMesh *bm, 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); +/* Log an edge's flags and customdata. */ +void BM_log_edge_modified(BMesh *bm, BMLog *log, BMEdge *e); +/* Log an edge as removed from the BMesh */ +void BM_log_edge_removed(BMesh *bm, BMLog *log, BMEdge *e); /** * Get the logged coordinates of a vertex. @@ -207,15 +242,13 @@ const float *BM_log_original_vert_no(BMLog *log, BMVert *v); float BM_log_original_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); +void BM_log_original_vert_data(BMLog *, BMVert *v, const float **co, const float **no); -/** 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); +/** Log the complete mesh, will be stored as + * a Mesh copy. + */ +void BM_log_full_mesh(BMesh *bm, BMLog *log); -void BM_log_print_entry(BMesh *bm, BMLogEntry *entry); - -#ifdef __cplusplus -} -#endif +/* Called from sculpt undo code. */ +void BM_log_print_entry(BMLogEntry *entry); +int BM_log_entry_size(BMLogEntry *entry); diff --git a/source/blender/editors/sculpt_paint/sculpt.cc b/source/blender/editors/sculpt_paint/sculpt.cc index 0c2d7dcb40e..510745db0e2 100644 --- a/source/blender/editors/sculpt_paint/sculpt.cc +++ b/source/blender/editors/sculpt_paint/sculpt.cc @@ -79,6 +79,7 @@ #include "RNA_define.hh" #include "bmesh.h" +#include "bmesh_log.h" using blender::float3; using blender::MutableSpan; diff --git a/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc b/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc index 53aef690c6b..61b546618b5 100644 --- a/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc +++ b/source/blender/editors/sculpt_paint/sculpt_dyntopo.cc @@ -43,6 +43,7 @@ #include "UI_resources.hh" #include "bmesh.h" +#include "bmesh_log.h" #include "bmesh_tools.h" using blender::IndexRange; @@ -77,7 +78,10 @@ void SCULPT_pbvh_clear(Object *ob) DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); } -void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob) +void SCULPT_dynamic_topology_enable_ex(Main *bmain, + Depsgraph *depsgraph, + Object *ob, + bool for_undo) { SculptSession *ss = ob->sculpt; Mesh *me = static_cast(ob->data); @@ -99,8 +103,24 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object convert_params.use_shapekey = true; convert_params.active_shapekey = ob->shapenr; BM_mesh_bm_from_me(ss->bm, me, &convert_params); + + /* Enable logging for undo/redo. */ + ss->bm_log = BM_log_create(ss->bm); + + /* Log full mesh prior to triangulation. */ + if (for_undo) { + SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN); + } + + int faces_num = ss->bm->totface; + SCULPT_dynamic_topology_triangulate(ss->bm); + /* Now log full mesh again after triangulation. */ + if (for_undo && ss->bm->totface != faces_num) { + BM_log_full_mesh(ss->bm, ss->bm_log); + } + BM_data_layer_add(ss->bm, &ss->bm->vdata, CD_PAINT_MASK); /* Make sure the data for existing faces are initialized. */ @@ -111,9 +131,6 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object /* Enable dynamic topology. */ me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; - /* Enable logging for undo/redo. */ - ss->bm_log = BM_log_create(ss->bm); - /* Update dependency graph, so modifiers that depend on dyntopo being enabled * are re-evaluated and the PBVH is re-created. */ DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); @@ -125,7 +142,7 @@ void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object * If 'unode' is given, the BMesh's data is copied out to the unode * before the BMesh is deleted so that it can be restored from. */ static void SCULPT_dynamic_topology_disable_ex( - Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, SculptUndoNode *unode) + Main *bmain, Depsgraph *depsgraph, Scene *scene, Object *ob, SculptUndoNode * /*unode*/) { SculptSession *ss = ob->sculpt; Mesh *me = static_cast(ob->data); @@ -140,35 +157,18 @@ static void SCULPT_dynamic_topology_disable_ex( SCULPT_pbvh_clear(ob); - if (unode) { - /* Free all existing custom data. */ - BKE_mesh_clear_geometry(me); + BKE_sculptsession_bm_to_me(ob, true); - /* Copy over stored custom data. */ - SculptUndoNodeGeometry *geometry = &unode->geometry_bmesh_enter; - me->totvert = geometry->totvert; - me->totloop = geometry->totloop; - me->faces_num = geometry->faces_num; - me->totedge = geometry->totedge; - me->totface_legacy = 0; - CustomData_copy(&geometry->vert_data, &me->vert_data, CD_MASK_MESH.vmask, geometry->totvert); - CustomData_copy(&geometry->edge_data, &me->edge_data, CD_MASK_MESH.emask, geometry->totedge); - CustomData_copy(&geometry->loop_data, &me->loop_data, CD_MASK_MESH.lmask, geometry->totloop); - CustomData_copy(&geometry->face_data, &me->face_data, CD_MASK_MESH.pmask, geometry->faces_num); - blender::implicit_sharing::copy_shared_pointer(geometry->face_offset_indices, - geometry->face_offsets_sharing_info, - &me->face_offset_indices, - &me->runtime->face_offsets_sharing_info); + if (ss->bm_log) { + BM_log_free(ss->bm, ss->bm_log); + ss->bm_log = nullptr; } - else { - BKE_sculptsession_bm_to_me(ob, true); - /* Sync the visibility to vertices manually as the `pmap` is still not initialized. */ - bool *hide_vert = (bool *)CustomData_get_layer_named_for_write( - &me->vert_data, CD_PROP_BOOL, ".hide_vert", me->totvert); - if (hide_vert != nullptr) { - memset(hide_vert, 0, sizeof(bool) * me->totvert); - } + /* Sync the visibility to vertices manually as the `pmap` is still not initialized. */ + bool *hide_vert = (bool *)CustomData_get_layer_named_for_write( + &me->vert_data, CD_PROP_BOOL, ".hide_vert", me->totvert); + if (hide_vert != nullptr) { + memset(hide_vert, 0, sizeof(bool) * me->totvert); } /* Clear data. */ @@ -178,19 +178,15 @@ static void SCULPT_dynamic_topology_disable_ex( if (ss->bm) { BM_mesh_free(ss->bm); ss->bm = nullptr; - } - if (ss->bm_log) { - BM_log_free(ss->bm_log); - ss->bm_log = nullptr; - } - BKE_particlesystem_reset_all(ob); - BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_OUTDATED); + BKE_particlesystem_reset_all(ob); + BKE_ptcache_object_reset(scene, ob, PTCACHE_RESET_OUTDATED); - /* Update dependency graph, so modifiers that depend on dyntopo being enabled - * are re-evaluated and the PBVH is re-created. */ - DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); - BKE_scene_graph_update_tagged(depsgraph, bmain); + /* Update dependency graph, so modifiers that depend on dyntopo being enabled + * are re-evaluated and the PBVH is re-created. */ + DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY); + BKE_scene_graph_update_tagged(depsgraph, bmain); + } } void SCULPT_dynamic_topology_disable(bContext *C, SculptUndoNode *unode) @@ -231,9 +227,8 @@ static void sculpt_dynamic_topology_enable_with_undo(Main *bmain, Depsgraph *dep if (use_undo) { SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable"); } - SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob); + SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob, use_undo); if (use_undo) { - SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN); SCULPT_undo_push_end(ob); } } diff --git a/source/blender/editors/sculpt_paint/sculpt_intern.hh b/source/blender/editors/sculpt_paint/sculpt_intern.hh index cdd1ccb6618..724fb45d871 100644 --- a/source/blender/editors/sculpt_paint/sculpt_intern.hh +++ b/source/blender/editors/sculpt_paint/sculpt_intern.hh @@ -41,6 +41,7 @@ struct AutomaskingCache; struct AutomaskingNodeData; struct BMLog; +struct BMLogEntry; struct Dial; struct DistRayAABB_Precalc; struct Image; @@ -223,9 +224,6 @@ struct SculptUndoNode { SculptUndoNodeGeometry geometry_original; SculptUndoNodeGeometry geometry_modified; - /* Geometry at the bmesh enter moment. */ - SculptUndoNodeGeometry geometry_bmesh_enter; - /* pivot */ float pivot_pos[3]; float pivot_rot[4]; @@ -1262,7 +1260,10 @@ enum eDynTopoWarnFlag { ENUM_OPERATORS(eDynTopoWarnFlag, DYNTOPO_WARN_MODIFIER); /** Enable dynamic topology; mesh will be triangulated */ -void SCULPT_dynamic_topology_enable_ex(Main *bmain, Depsgraph *depsgraph, Object *ob); +void SCULPT_dynamic_topology_enable_ex(Main *bmain, + Depsgraph *depsgraph, + Object *ob, + bool for_undo = false); void SCULPT_dynamic_topology_disable(bContext *C, SculptUndoNode *unode); void sculpt_dynamic_topology_disable_with_undo(Main *bmain, Depsgraph *depsgraph, diff --git a/source/blender/editors/sculpt_paint/sculpt_ops.cc b/source/blender/editors/sculpt_paint/sculpt_ops.cc index 90c44dd1efd..6f6fccf1c8b 100644 --- a/source/blender/editors/sculpt_paint/sculpt_ops.cc +++ b/source/blender/editors/sculpt_paint/sculpt_ops.cc @@ -66,6 +66,7 @@ #include "UI_resources.hh" #include "bmesh.h" +#include "bmesh_log.h" #include #include @@ -189,7 +190,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) * parts that symmetrize modifies). */ SCULPT_undo_push_begin(ob, op); SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_SYMMETRIZE); - BM_log_before_all_removed(ss->bm, ss->bm_log); + BM_log_full_mesh(ss->bm, ss->bm_log); BM_mesh_toolflags_set(ss->bm, true); @@ -208,7 +209,7 @@ static int sculpt_symmetrize_exec(bContext *C, wmOperator *op) BM_mesh_toolflags_set(ss->bm, false); /* Finish undo. */ - BM_log_all_added(ss->bm, ss->bm_log); + BM_log_full_mesh(ss->bm, ss->bm_log); SCULPT_undo_push_end(ob); break; @@ -405,9 +406,8 @@ void ED_object_sculptmode_enter_ex(Main *bmain, if (has_undo) { SCULPT_undo_push_begin_ex(ob, "Dynamic topology enable"); } - SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob); + SCULPT_dynamic_topology_enable_ex(bmain, depsgraph, ob, has_undo); if (has_undo) { - SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_DYNTOPO_BEGIN); SCULPT_undo_push_end(ob); } } @@ -1315,10 +1315,10 @@ static int sculpt_reveal_all_exec(bContext *C, wmOperator *op) const int cd_mask = CustomData_get_offset(&ss->bm->vdata, CD_PAINT_MASK); BM_ITER_MESH (v, &iter, ss->bm, BM_VERTS_OF_MESH) { - BM_log_vert_before_modified(ss->bm_log, v, cd_mask); + BM_log_vert_before_modified(ss->bm, ss->bm_log, v); } BM_ITER_MESH (f, &iter, ss->bm, BM_FACES_OF_MESH) { - BM_log_face_modified(ss->bm_log, f); + BM_log_face_modified(ss->bm, ss->bm_log, f); } SCULPT_face_visibility_all_set(ss, true); diff --git a/source/blender/editors/sculpt_paint/sculpt_undo.cc b/source/blender/editors/sculpt_paint/sculpt_undo.cc index e714e20a63a..bf9677f621b 100644 --- a/source/blender/editors/sculpt_paint/sculpt_undo.cc +++ b/source/blender/editors/sculpt_paint/sculpt_undo.cc @@ -78,6 +78,7 @@ #include "ED_undo.hh" #include "bmesh.h" +#include "bmesh_log.h" #include "sculpt_intern.hh" /* Uncomment to print the undo stack in the console on push/undo/redo. */ @@ -659,7 +660,7 @@ static void sculpt_undo_bmesh_restore_generic(SculptUndoNode *unode, Object *ob, } /* Create empty sculpt BMesh and enable logging. */ -static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode) +static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode, bool for_redo) { SculptSession *ss = ob->sculpt; Mesh *me = static_cast(ob->data); @@ -676,7 +677,7 @@ static void sculpt_undo_bmesh_enable(Object *ob, SculptUndoNode *unode) me->flag |= ME_SCULPT_DYNAMIC_TOPOLOGY; /* Restore the BMLog using saved entries. */ - ss->bm_log = BM_log_from_existing_entries_create(ss->bm, unode->bm_entry); + ss->bm_log = BM_log_from_existing_entries_create(ss->bm, unode->bm_entry, for_redo); } static void sculpt_undo_bmesh_restore_begin(bContext *C, @@ -685,15 +686,17 @@ static void sculpt_undo_bmesh_restore_begin(bContext *C, SculptSession *ss) { if (unode->applied) { + /* Load the full mesh that was saved at this step. */ + BM_log_undo(ss->bm, ss->bm_log); + SCULPT_dynamic_topology_disable(C, unode); unode->applied = false; } else { - sculpt_undo_bmesh_enable(ob, unode); + sculpt_undo_bmesh_enable(ob, unode, true); /* Restore the mesh from the first log entry. */ BM_log_redo(ss->bm, ss->bm_log); - unode->applied = true; } } @@ -704,7 +707,7 @@ static void sculpt_undo_bmesh_restore_end(bContext *C, SculptSession *ss) { if (unode->applied) { - sculpt_undo_bmesh_enable(ob, unode); + sculpt_undo_bmesh_enable(ob, unode, false); /* Restore the mesh from the last log entry. */ BM_log_undo(ss->bm, ss->bm_log); @@ -712,6 +715,9 @@ static void sculpt_undo_bmesh_restore_end(bContext *C, unode->applied = false; } else { + /* Load the full mesh that was saved at this step. */ + BM_log_redo(ss->bm, ss->bm_log); + /* Disable dynamic topology sculpting. */ SCULPT_dynamic_topology_disable(C, nullptr); unode->applied = true; @@ -1110,7 +1116,6 @@ static void sculpt_undo_free_list(ListBase *lb) sculpt_undo_geometry_free_data(&unode->geometry_original); sculpt_undo_geometry_free_data(&unode->geometry_modified); - sculpt_undo_geometry_free_data(&unode->geometry_bmesh_enter); MEM_delete(unode); @@ -1479,23 +1484,19 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt unode->applied = true; if (type == SCULPT_UNDO_DYNTOPO_END) { - unode->bm_entry = BM_log_entry_add(ss->bm_log); - BM_log_before_all_removed(ss->bm, ss->bm_log); + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); + BM_log_full_mesh(ss->bm, ss->bm_log); } else if (type == SCULPT_UNDO_DYNTOPO_BEGIN) { /* Store a copy of the mesh's current vertices, loops, and * faces. A full copy like this is needed because entering * dynamic-topology immediately does topological edits - * (converting faces to triangles) that the BMLog can't - * fully restore from. */ - SculptUndoNodeGeometry *geometry = &unode->geometry_bmesh_enter; - sculpt_undo_geometry_store_data(geometry, ob); - - unode->bm_entry = BM_log_entry_add(ss->bm_log); - BM_log_all_added(ss->bm, ss->bm_log); + * (converting faces to triangles). */ + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); + BM_log_full_mesh(ss->bm, ss->bm_log); } else { - unode->bm_entry = BM_log_entry_add(ss->bm_log); + unode->bm_entry = BM_log_entry_add(ss->bm, ss->bm_log); } BLI_addtail(&usculpt->nodes, unode); @@ -1508,32 +1509,35 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt /* Before any vertex values get modified, ensure their * original positions are logged. */ BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { - BM_log_vert_before_modified(ss->bm_log, vd.bm_vert, vd.cd_vert_mask_offset); + BM_log_vert_before_modified(ss->bm, ss->bm_log, vd.bm_vert); } BKE_pbvh_vertex_iter_end; break; case SCULPT_UNDO_HIDDEN: { BKE_pbvh_vertex_iter_begin (ss->pbvh, node, vd, PBVH_ITER_ALL) { - BM_log_vert_before_modified(ss->bm_log, vd.bm_vert, vd.cd_vert_mask_offset); + BM_log_vert_before_modified(ss->bm, ss->bm_log, vd.bm_vert); } BKE_pbvh_vertex_iter_end; for (BMFace *f : BKE_pbvh_bmesh_node_faces(node)) { - BM_log_face_modified(ss->bm_log, f); + BM_log_face_modified(ss->bm, ss->bm_log, f); } break; } + case SCULPT_UNDO_GEOMETRY: case SCULPT_UNDO_DYNTOPO_BEGIN: case SCULPT_UNDO_DYNTOPO_END: case SCULPT_UNDO_DYNTOPO_SYMMETRIZE: - case SCULPT_UNDO_GEOMETRY: case SCULPT_UNDO_FACE_SETS: case SCULPT_UNDO_COLOR: break; } } + else if (type == SCULPT_UNDO_GEOMETRY) { + BM_log_full_mesh(ss->bm, ss->bm_log); + } return unode; } @@ -1973,6 +1977,11 @@ void ED_sculpt_undo_geometry_begin_ex(Object *ob, const char *name) void ED_sculpt_undo_geometry_end(Object *ob) { + /* Inform the sculpt system attribute layouts may have changed, + * e.g. with voxel remesh. + */ + BKE_sculpt_update_attribute_refs(ob); + SCULPT_undo_push_node(ob, nullptr, SCULPT_UNDO_GEOMETRY); SCULPT_undo_push_end(ob); }