Mesh: Move loose edge flag to a separate cache

As part of T95966, this patch moves loose edge information out of the
flag on each edge and into a new lazily calculated cache in mesh
runtime data. The number of loose edges is also cached, so further
processing can be skipped completely when there are no loose edges.

Previously the `ME_LOOSEEDGE` flag was updated on a "best effort"
basis. In order to be sure that it was correct, you had to be sure
to call `BKE_mesh_calc_edges_loose` first. Now the loose edge tag
is always correct. It also doesn't have to be calculated eagerly
in various places like the screw modifier where the complexity
wasn't worth the theoretical performance benefit.

The patch also adds a function to eagerly set the number of loose
edges to zero to avoid building the cache. This is used by various
primitive nodes, with the goal of improving drawing performance.
This results in a few ms shaved off extracting draw data for some
large meshes in my tests.

In the Python API, `MeshEdge.is_loose` is no longer editable.
No built-in addons set the value anyway. The upside is that
addons can be sure the data is correct based on the mesh.

**Tests**
There is one test failure in the Python OBJ exporter: `export_obj_cube`
that happens because of existing incorrect versioning. Opening the
file in master, all the edges were set to "loose", which is fixed
by this patch.

Differential Revision: https://developer.blender.org/D16504
This commit is contained in:
Hans Goudey 2022-11-18 16:05:06 -06:00
parent c0f33814c1
commit 1ea169d90e
Notes: blender-bot 2024-04-17 14:24:09 +02:00
Referenced by commit e0fbeb6e7b, Fix T103225: Line Art modifier skips loose edges
Referenced by issue #95966, Struct of Arrays Refactor for Mesh Edges
Referenced by issue #120721, Collada: Import with loose edges not immediately show them
Referenced by pull request #120737, Fix #120721: Collada: Import with loose edges not immediately show them
Referenced by commit 0bda626ba3, Fix #120721: Collada: Import with loose edges not immediately show them
45 changed files with 259 additions and 236 deletions

View File

@ -70,6 +70,8 @@ void BKE_mesh_tag_coords_changed(struct Mesh *mesh);
*/
void BKE_mesh_tag_coords_changed_uniformly(struct Mesh *mesh);
void BKE_mesh_tag_topology_changed(struct Mesh *mesh);
/* *** mesh.c *** */
struct BMesh *BKE_mesh_to_bmesh_ex(const struct Mesh *me,
@ -943,7 +945,6 @@ void BKE_mesh_strip_loose_faces(struct Mesh *me);
void BKE_mesh_strip_loose_polysloops(struct Mesh *me);
void BKE_mesh_strip_loose_edges(struct Mesh *me);
void BKE_mesh_calc_edges_loose(struct Mesh *mesh);
/**
* Calculate edges from polygons.
*/

View File

@ -82,6 +82,9 @@ void BKE_mesh_legacy_convert_material_indices_to_mpoly(struct Mesh *mesh);
*/
void BKE_mesh_legacy_convert_mpoly_to_material_indices(struct Mesh *mesh);
/** Convert from runtime loose edge cache to legacy edge flag. */
void BKE_mesh_legacy_convert_loose_edges_to_flag(struct Mesh *mesh);
#endif
/**

View File

@ -11,6 +11,8 @@
# include <mutex>
# include "BLI_bit_vector.hh"
# include "BLI_shared_cache.hh"
# include "BLI_span.hh"
# include "DNA_customdata_types.h"
@ -61,6 +63,23 @@ typedef enum eMeshWrapperType {
namespace blender::bke {
/**
* Cache of a mesh's loose edges, accessed with #Mesh::loose_edges(). *
*/
struct LooseEdgeCache {
/**
* A bitmap set to true for each loose edge, false if the edge is used by any face.
* Allocated only if there is at least one loose edge.
*/
blender::BitVector<> is_loose_bits;
/**
* The number of loose edges. If zero, the #is_loose_bits shouldn't be accessed.
* If less than zero, the cache has been accessed in an invalid way
* (i.e.directly instead of through #Mesh::loose_edges()).
*/
int count = -1;
};
/**
* \warning Typical access is done via #Mesh::looptris().
*/
@ -154,6 +173,12 @@ struct MeshRuntime {
float (*vert_normals)[3] = nullptr;
float (*poly_normals)[3] = nullptr;
/**
* A cache of data about the loose edges. Can be shared with other data-blocks with unchanged
* topology. Accessed with #Mesh::loose_edges().
*/
SharedCache<LooseEdgeCache> loose_edges_cache;
/**
* A #BLI_bitmap containing tags for the center vertices of subdivided polygons, set by the
* subdivision surface modifier and used by drawing code instead of polygon center face dots.

View File

@ -1159,22 +1159,12 @@ static BitVector<> loose_verts_map_get(const Span<MEdge> edges,
return loose_verts_mask;
}
static BitVector<> loose_edges_map_get(const Span<MEdge> edges, int *r_loose_edge_len)
static BitVector<> loose_edges_map_get(const Mesh &mesh, int *r_loose_edge_len)
{
BitVector<> loose_edges_mask(edges.size());
int loose_edges_len = 0;
for (const int64_t i : edges.index_range()) {
const MEdge &edge = edges[i];
if (edge.flag & ME_LOOSEEDGE) {
loose_edges_mask[i].set();
loose_edges_len++;
}
}
*r_loose_edge_len = loose_edges_len;
return loose_edges_mask;
using namespace blender::bke;
const LooseEdgeCache &loose_edges = mesh.loose_edges();
*r_loose_edge_len = loose_edges.count;
return loose_edges.is_loose_bits;
}
static BitVector<> looptri_no_hidden_map_get(const Span<MPoly> polys,
@ -1261,7 +1251,7 @@ BVHTree *BKE_bvhtree_from_mesh_get(struct BVHTreeFromMesh *data,
break;
case BVHTREE_FROM_LOOSEEDGES:
mask = loose_edges_map_get(edges, &mask_bits_act_len);
mask = loose_edges_map_get(*mesh, &mask_bits_act_len);
ATTR_FALLTHROUGH;
case BVHTREE_FROM_EDGES:
data->tree = bvhtree_from_mesh_edges_create_tree(

View File

@ -1456,6 +1456,7 @@ static bool find_internal_spring_target_vertex(BVHTreeFromMesh *treedata,
static bool cloth_build_springs(ClothModifierData *clmd, Mesh *mesh)
{
using namespace blender::bke;
Cloth *cloth = clmd->clothObject;
ClothSpring *spring = nullptr, *tspring = nullptr, *tspring2 = nullptr;
uint struct_springs = 0, shear_springs = 0, bend_springs = 0, struct_springs_real = 0;
@ -1591,12 +1592,14 @@ static bool cloth_build_springs(ClothModifierData *clmd, Mesh *mesh)
}
/* Structural springs. */
const LooseEdgeCache &loose_edges = mesh->loose_edges();
for (int i = 0; i < numedges; i++) {
spring = (ClothSpring *)MEM_callocN(sizeof(ClothSpring), "cloth spring");
if (spring) {
spring_verts_ordered_set(spring, medge[i].v1, medge[i].v2);
if (clmd->sim_parms->flags & CLOTH_SIMSETTINGS_FLAG_SEW && medge[i].flag & ME_LOOSEEDGE) {
if (clmd->sim_parms->flags & CLOTH_SIMSETTINGS_FLAG_SEW && loose_edges.count > 0 &&
loose_edges.is_loose_bits[i]) {
/* handle sewing (loose edges will be pulled together) */
spring->restlen = 0.0f;
spring->lin_stiffness = 1.0f;

View File

@ -46,14 +46,12 @@ static void fill_mesh_topology(const int vert_offset,
MEdge &edge = edges[edge_offset + i];
edge.v1 = vert_offset + i;
edge.v2 = vert_offset + i + 1;
edge.flag = ME_LOOSEEDGE;
}
if (main_cyclic && main_segment_num > 1) {
MEdge &edge = edges[edge_offset + main_segment_num - 1];
edge.v1 = vert_offset + main_point_num - 1;
edge.v2 = vert_offset;
edge.flag = ME_LOOSEEDGE;
}
return;
}

View File

@ -338,9 +338,6 @@ void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
const Span<MPoly> polys = mesh.polys();
const Span<MLoop> loops = mesh.loops();
/* It may be possible to rely on the #ME_LOOSEEDGE flag, but that seems error-prone. */
Array<bool> loose_edges(mesh.totedge, true);
r_values.fill(true);
for (const int poly_index : polys.index_range()) {
const MPoly &poly = polys[poly_index];
@ -352,22 +349,23 @@ void adapt_mesh_domain_corner_to_edge_impl(const Mesh &mesh,
const MLoop &loop = loops[loop_i];
const int edge_index = loop.e;
loose_edges[edge_index] = false;
if (!old_values[loop_i] || !old_values[next_loop_i]) {
r_values[edge_index] = false;
}
}
}
/* Deselect loose edges without corners that are still selected from the 'true' default. */
threading::parallel_for(IndexRange(mesh.totedge), 2048, [&](const IndexRange range) {
for (const int edge_index : range) {
if (loose_edges[edge_index]) {
r_values[edge_index] = false;
const bke::LooseEdgeCache &loose_edges = mesh.loose_edges();
if (loose_edges.count > 0) {
/* Deselect loose edges without corners that are still selected from the 'true' default. */
threading::parallel_for(IndexRange(mesh.totedge), 2048, [&](const IndexRange range) {
for (const int edge_index : range) {
if (loose_edges.is_loose_bits[edge_index]) {
r_values[edge_index] = false;
}
}
}
});
});
}
}
static GVArray adapt_mesh_domain_corner_to_edge(const Mesh &mesh, const GVArray &varray)
@ -776,7 +774,9 @@ static GVArray adapt_mesh_domain_edge_to_face(const Mesh &mesh, const GVArray &v
} // namespace blender::bke
static bool can_simple_adapt_for_single(const eAttrDomain from_domain, const eAttrDomain to_domain)
static bool can_simple_adapt_for_single(const Mesh &mesh,
const eAttrDomain from_domain,
const eAttrDomain to_domain)
{
/* For some domain combinations, a single value will always map directly. For others, there may
* be loose elements on the result domain that should have the default value rather than the
@ -790,9 +790,15 @@ static bool can_simple_adapt_for_single(const eAttrDomain from_domain, const eAt
return ELEM(to_domain, ATTR_DOMAIN_FACE, ATTR_DOMAIN_CORNER);
case ATTR_DOMAIN_FACE:
/* There may be loose vertices or edges not connected to faces. */
if (to_domain == ATTR_DOMAIN_EDGE) {
return mesh.loose_edges().count == 0;
}
return to_domain == ATTR_DOMAIN_CORNER;
case ATTR_DOMAIN_CORNER:
/* Only faces are always connected to corners. */
if (to_domain == ATTR_DOMAIN_EDGE) {
return mesh.loose_edges().count == 0;
}
return to_domain == ATTR_DOMAIN_FACE;
default:
BLI_assert_unreachable();
@ -815,7 +821,7 @@ static blender::GVArray adapt_mesh_attribute_domain(const Mesh &mesh,
return varray;
}
if (varray.is_single()) {
if (can_simple_adapt_for_single(from_domain, to_domain)) {
if (can_simple_adapt_for_single(mesh, from_domain, to_domain)) {
BUFFER_FOR_CPP_TYPE_VALUE(varray.type(), value);
varray.get_internal_single(value);
return blender::GVArray::ForSingle(

View File

@ -128,6 +128,7 @@ static void mesh_copy_data(Main *bmain, ID *id_dst, const ID *id_src, const int
* when the source is persistent and edits to the destination don't change the bounds. It will be
* "un-shared" as necessary when the positions are changed. */
mesh_dst->runtime->bounds_cache = mesh_src->runtime->bounds_cache;
mesh_dst->runtime->loose_edges_cache = mesh_src->runtime->loose_edges_cache;
/* Only do tessface if we have no polys. */
const bool do_tessface = ((mesh_src->totface != 0) && (mesh_src->totpoly == 0));
@ -252,6 +253,7 @@ static void mesh_blend_write(BlendWriter *writer, ID *id, const void *id_address
BKE_mesh_legacy_bevel_weight_from_layers(mesh);
BKE_mesh_legacy_face_set_from_generic(mesh, poly_layers);
BKE_mesh_legacy_edge_crease_from_layers(mesh);
BKE_mesh_legacy_convert_loose_edges_to_flag(mesh);
/* When converting to the old mesh format, don't save redundant attributes. */
names_to_skip.add_multiple_new({".hide_vert",
".hide_edge",

View File

@ -262,6 +262,11 @@ void BKE_mesh_calc_edges(Mesh *mesh, bool keep_existing_edges, const bool select
}
}
if (!keep_existing_edges) {
/* All edges are rebuilt from the faces, so there are no loose edges. */
mesh->loose_edges_tag_none();
}
/* Explicitly clear edge maps, because that way it can be parallelized. */
clear_hash_tables(edge_maps);
}

View File

@ -223,7 +223,7 @@ static Mesh *mesh_nurbs_displist_to_mesh(const Curve *cu, const ListBase *dispba
for (b = 1; b < dl->nr; b++) {
medge->v1 = startvert + ofs + b - 1;
medge->v2 = startvert + ofs + b;
medge->flag = ME_LOOSEEDGE | ME_EDGEDRAW;
medge->flag = ME_EDGEDRAW;
medge++;
}
@ -251,7 +251,7 @@ static Mesh *mesh_nurbs_displist_to_mesh(const Curve *cu, const ListBase *dispba
else {
medge->v2 = startvert + ofs + b + 1;
}
medge->flag = ME_LOOSEEDGE | ME_EDGEDRAW;
medge->flag = ME_EDGEDRAW;
medge++;
}
}

View File

@ -154,9 +154,6 @@ static void mesh_calc_edges_mdata(const MVert * /*allvert*/,
if (use_old == false || ed->is_draw) {
med->flag = ME_EDGEDRAW;
}
if (ed->is_loose) {
med->flag |= ME_LOOSEEDGE;
}
/* order is swapped so extruding this edge as a surface won't flip face normals
* with cyclic curves */
@ -174,9 +171,6 @@ static void mesh_calc_edges_mdata(const MVert * /*allvert*/,
med->v1 = ed->v1;
med->v2 = ed->v2;
med->flag = ME_EDGEDRAW;
if (ed->is_loose) {
med->flag |= ME_LOOSEEDGE;
}
MEM_freeN(edsort);
@ -237,6 +231,7 @@ void BKE_mesh_calc_edges_legacy(Mesh *me, const bool use_old)
medge = (MEdge *)CustomData_add_layer(&me->edata, CD_MEDGE, CD_ASSIGN, medge, totedge);
me->totedge = totedge;
BKE_mesh_tag_topology_changed(me);
BKE_mesh_strip_loose_faces(me);
}
@ -1545,3 +1540,30 @@ void BKE_mesh_legacy_convert_flags_to_selection_layers(Mesh *mesh)
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Loose Edges
* \{ */
void BKE_mesh_legacy_convert_loose_edges_to_flag(Mesh *mesh)
{
using namespace blender;
using namespace blender::bke;
const LooseEdgeCache &loose_edges = mesh->loose_edges();
MutableSpan<MEdge> edges = mesh->edges_for_write();
threading::parallel_for(edges.index_range(), 4096, [&](const IndexRange range) {
if (loose_edges.count == 0) {
for (const int64_t i : range) {
edges[i].flag &= ~ME_LOOSEEDGE;
}
}
else {
for (const int64_t i : range) {
SET_FLAG_FROM_TEST(edges[i].flag, loose_edges.is_loose_bits[i], ME_LOOSEEDGE);
}
}
});
}
/** \} */

View File

@ -15,6 +15,7 @@
#include "BLI_math_geom.h"
#include "BLI_task.hh"
#include "BLI_timeit.hh"
#include "BKE_bvhutils.h"
#include "BKE_editmesh_cache.h"
@ -116,6 +117,38 @@ blender::Span<MLoopTri> Mesh::looptris() const
return {looptris, num_looptris};
}
const blender::bke::LooseEdgeCache &Mesh::loose_edges() const
{
using namespace blender::bke;
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
SCOPED_TIMER("loose_edges");
blender::BitVector<> &loose_edges = r_data.is_loose_bits;
loose_edges.resize(0);
loose_edges.resize(this->totedge, true);
int count = this->totedge;
for (const MLoop &loop : this->loops()) {
if (loose_edges[loop.e]) {
loose_edges[loop.e].reset();
count--;
}
}
r_data.count = count;
});
return this->runtime->loose_edges_cache.data();
}
void Mesh::loose_edges_tag_none() const
{
using namespace blender::bke;
this->runtime->loose_edges_cache.ensure([&](LooseEdgeCache &r_data) {
r_data.is_loose_bits.resize(0);
r_data.count = 0;
});
}
/**
* Ensure the array is large enough
*
@ -254,6 +287,7 @@ void BKE_mesh_runtime_clear_geometry(Mesh *mesh)
free_normals(*mesh->runtime);
free_subdiv_ccg(*mesh->runtime);
mesh->runtime->bounds_cache.tag_dirty();
mesh->runtime->loose_edges_cache.tag_dirty();
if (mesh->runtime->shrinkwrap_data) {
BKE_shrinkwrap_boundary_data_free(mesh->runtime->shrinkwrap_data);
}
@ -276,6 +310,11 @@ void BKE_mesh_tag_coords_changed_uniformly(Mesh *mesh)
mesh->runtime->bounds_cache.tag_dirty();
}
void BKE_mesh_tag_topology_changed(struct Mesh *mesh)
{
BKE_mesh_runtime_clear_geometry(mesh);
}
bool BKE_mesh_is_deformed_only(const Mesh *mesh)
{
return mesh->runtime->deformed_only;

View File

@ -1319,24 +1319,6 @@ void BKE_mesh_strip_loose_edges(Mesh *me)
/** \name Mesh Edge Calculation
* \{ */
void BKE_mesh_calc_edges_loose(Mesh *mesh)
{
const Span<MLoop> loops = mesh->loops();
MutableSpan<MEdge> edges = mesh->edges_for_write();
for (const int i : edges.index_range()) {
edges[i].flag |= ME_LOOSEEDGE;
}
for (const int i : loops.index_range()) {
edges[loops[i].e].flag &= ~ME_LOOSEEDGE;
}
for (const int i : edges.index_range()) {
if (edges[i].flag & ME_LOOSEEDGE) {
edges[i].flag |= ME_EDGEDRAW;
}
}
}
void BKE_mesh_calc_edges_tessface(Mesh *mesh)
{
const int numFaces = mesh->totface;

View File

@ -999,10 +999,6 @@ static void ccgDM_copyFinalEdgeArray(DerivedMesh *dm, MEdge *medge)
int x;
int edgeIdx = POINTER_AS_INT(ccgSubSurf_getEdgeEdgeHandle(e));
if (!ccgSubSurf_getEdgeNumFaces(e)) {
ed_flag |= ME_LOOSEEDGE;
}
if (edgeFlags) {
if (edgeIdx != -1) {
ed_flag |= ((edgeFlags[index] & (ME_SEAM | ME_SHARP)) | ME_EDGEDRAW);

View File

@ -2969,12 +2969,6 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 280, 28)) {
for (Mesh *mesh = bmain->meshes.first; mesh; mesh = mesh->id.next) {
BKE_mesh_calc_edges_loose(mesh);
}
}
if (!MAIN_VERSION_ATLEAST(bmain, 280, 29)) {
for (bScreen *screen = bmain->screens.first; screen; screen = screen->id.next) {
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {

View File

@ -731,8 +731,7 @@ short BM_edge_flag_to_mflag(BMEdge *e)
const char hflag = e->head.hflag;
return (((hflag & BM_ELEM_SEAM) ? ME_SEAM : 0) | ((hflag & BM_ELEM_DRAW) ? ME_EDGEDRAW : 0) |
((hflag & BM_ELEM_SMOOTH) == 0 ? ME_SHARP : 0) |
(BM_edge_is_wire(e) ? ME_LOOSEEDGE : 0));
((hflag & BM_ELEM_SMOOTH) == 0 ? ME_SHARP : 0));
}
char BM_face_flag_to_mflag(BMFace *f)
{

View File

@ -78,22 +78,28 @@ static void mesh_render_data_loose_geom_build(const MeshRenderData *mr, MeshBuff
static void mesh_render_data_loose_geom_mesh(const MeshRenderData *mr, MeshBufferCache *cache)
{
using namespace blender;
BLI_bitmap *lvert_map = BLI_BITMAP_NEW(mr->vert_len, __func__);
cache->loose_geom.edges = static_cast<int *>(
MEM_mallocN(mr->edge_len * sizeof(*cache->loose_geom.edges), __func__));
const MEdge *med = mr->medge;
for (int med_index = 0; med_index < mr->edge_len; med_index++, med++) {
if (med->flag & ME_LOOSEEDGE) {
cache->loose_geom.edges[cache->loose_geom.edge_len++] = med_index;
const bke::LooseEdgeCache &loose_edges = mr->me->loose_edges();
if (loose_edges.count > 0) {
cache->loose_geom.edges = static_cast<int *>(
MEM_malloc_arrayN(loose_edges.count, sizeof(int), __func__));
cache->loose_geom.edge_len = 0;
for (const int64_t i : loose_edges.is_loose_bits.index_range()) {
if (loose_edges.is_loose_bits[i]) {
cache->loose_geom.edges[cache->loose_geom.edge_len] = int(i);
cache->loose_geom.edge_len++;
}
}
/* Tag verts as not loose. */
BLI_BITMAP_ENABLE(lvert_map, med->v1);
BLI_BITMAP_ENABLE(lvert_map, med->v2);
}
if (cache->loose_geom.edge_len < mr->edge_len) {
cache->loose_geom.edges = static_cast<int *>(MEM_reallocN(
cache->loose_geom.edges, cache->loose_geom.edge_len * sizeof(*cache->loose_geom.edges)));
/* Tag verts as not loose. */
const Span<MEdge> edges(mr->medge, mr->edge_len);
for (const MEdge &edge : edges) {
BLI_BITMAP_ENABLE(lvert_map, edge.v1);
BLI_BITMAP_ENABLE(lvert_map, edge.v2);
}
cache->loose_geom.verts = static_cast<int *>(

View File

@ -552,6 +552,8 @@ void ED_mesh_geometry_clear(struct Mesh *mesh);
void ED_mesh_update(struct Mesh *mesh, struct bContext *C, bool calc_edges, bool calc_edges_loose);
bool ED_mesh_edge_is_loose(const struct Mesh *mesh, int index);
void ED_mesh_uv_ensure(struct Mesh *me, const char *name);
int ED_mesh_uv_add(
struct Mesh *me, const char *name, bool active_set, bool do_init, struct ReportList *reports);

View File

@ -1118,8 +1118,8 @@ void ED_mesh_update(Mesh *mesh, bContext *C, bool calc_edges, bool calc_edges_lo
BKE_mesh_calc_edges(mesh, calc_edges, true);
}
if (calc_edges_loose && mesh->totedge) {
BKE_mesh_calc_edges_loose(mesh);
if (calc_edges_loose) {
mesh->runtime->loose_edges_cache.tag_dirty();
}
/* Default state is not to have tessface's so make sure this is the case. */
@ -1132,6 +1132,13 @@ void ED_mesh_update(Mesh *mesh, bContext *C, bool calc_edges, bool calc_edges_lo
WM_event_add_notifier(C, NC_GEOM | ND_DATA, mesh);
}
bool ED_mesh_edge_is_loose(const Mesh *mesh, const int index)
{
using namespace blender;
const bke::LooseEdgeCache &loose_edges = mesh->loose_edges();
return loose_edges.count > 0 && loose_edges.is_loose_bits[index];
}
static void mesh_add_verts(Mesh *mesh, int len)
{
using namespace blender;

View File

@ -614,7 +614,7 @@ bool ED_object_modifier_convert_psys_to_mesh(ReportList * /*reports*/,
if (k) {
medge->v1 = cvert - 1;
medge->v2 = cvert;
medge->flag = ME_EDGEDRAW | ME_LOOSEEDGE;
medge->flag = ME_EDGEDRAW;
medge++;
}
else {
@ -633,7 +633,7 @@ bool ED_object_modifier_convert_psys_to_mesh(ReportList * /*reports*/,
if (k) {
medge->v1 = cvert - 1;
medge->v2 = cvert;
medge->flag = ME_EDGEDRAW | ME_LOOSEEDGE;
medge->flag = ME_EDGEDRAW;
medge++;
}
else {

View File

@ -1434,9 +1434,6 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
MEdge *me = &dst_edges[dest_index];
me->v1 = vert_final[wegrp->v1];
me->v2 = vert_final[wegrp->v2];
/* "For now, assume that all merged edges are loose. This flag will be cleared in the
* Polys/Loops step". */
me->flag |= ME_LOOSEEDGE;
edge_final[i] = dest_index;
dest_index++;
@ -1485,10 +1482,6 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
r_ml->e = e;
r_ml++;
loop_cur++;
if (iter.type) {
dst_edges[e].flag &= ~ME_LOOSEEDGE;
}
BLI_assert((dst_edges[e].flag & ME_LOOSEEDGE) == 0);
}
}
@ -1520,10 +1513,6 @@ static Mesh *create_merged_mesh(const Mesh &mesh,
r_ml->e = e;
r_ml++;
loop_cur++;
if (iter.type) {
dst_edges[e].flag &= ~ME_LOOSEEDGE;
}
BLI_assert((dst_edges[e].flag & ME_LOOSEEDGE) == 0);
}
r_mp->loopstart = loop_start;
@ -1600,11 +1589,19 @@ std::optional<Mesh *> mesh_merge_by_distance_connected(const Mesh &mesh,
range_vn_i(vert_dest_map.data(), mesh.totvert, 0);
/* Collapse Edges that are shorter than the threshold. */
const bke::LooseEdgeCache *loose_edges = nullptr;
if (only_loose_edges) {
loose_edges = &mesh.loose_edges();
if (loose_edges->count == 0) {
return {};
}
}
for (const int i : edges.index_range()) {
int v1 = edges[i].v1;
int v2 = edges[i].v2;
if (only_loose_edges && (edges[i].flag & ME_LOOSEEDGE) == 0) {
if (loose_edges && !loose_edges->is_loose_bits[i]) {
continue;
}
while (v1 != vert_dest_map[v1]) {

View File

@ -1713,69 +1713,9 @@ static void lineart_identify_mlooptri_feature_edges(void *__restrict userdata,
struct LooseEdgeData {
int loose_count;
int loose_max;
int *loose_array;
const MEdge *edges;
};
static void lineart_loose_data_reallocate(LooseEdgeData *loose_data, int count)
{
int *new_arr = static_cast<int *>(MEM_calloc_arrayN(count, sizeof(int), "loose edge array"));
if (loose_data->loose_array) {
memcpy(new_arr, loose_data->loose_array, sizeof(int) * loose_data->loose_max);
MEM_SAFE_FREE(loose_data->loose_array);
}
loose_data->loose_max = count;
loose_data->loose_array = new_arr;
}
static void lineart_join_loose_edge_arr(LooseEdgeData *loose_data, LooseEdgeData *to_be_joined)
{
if (!to_be_joined->loose_array) {
return;
}
int new_count = loose_data->loose_count + to_be_joined->loose_count;
if (new_count >= loose_data->loose_max) {
lineart_loose_data_reallocate(loose_data, new_count);
}
memcpy(&loose_data->loose_array[loose_data->loose_count],
to_be_joined->loose_array,
sizeof(int) * to_be_joined->loose_count);
loose_data->loose_count += to_be_joined->loose_count;
MEM_freeN(to_be_joined->loose_array);
to_be_joined->loose_array = nullptr;
}
static void lineart_add_loose_edge(LooseEdgeData *loose_data, const int i)
{
if (loose_data->loose_count >= loose_data->loose_max) {
int min_amount = MAX2(100, loose_data->loose_count * 2);
lineart_loose_data_reallocate(loose_data, min_amount);
}
loose_data->loose_array[loose_data->loose_count] = i;
loose_data->loose_count++;
}
static void lineart_identify_loose_edges(void *__restrict /*userdata*/,
const int i,
const TaskParallelTLS *__restrict tls)
{
LooseEdgeData *loose_data = (LooseEdgeData *)tls->userdata_chunk;
if (loose_data->edges[i].flag & ME_LOOSEEDGE) {
lineart_add_loose_edge(loose_data, i);
}
}
static void loose_data_sum_reduce(const void *__restrict /*userdata*/,
void *__restrict chunk_join,
void *__restrict chunk)
{
LooseEdgeData *final = (LooseEdgeData *)chunk_join;
LooseEdgeData *loose_chunk = (LooseEdgeData *)chunk;
lineart_join_loose_edge_arr(final, loose_chunk);
}
void lineart_add_edge_to_array(LineartPendingEdges *pe, LineartEdge *e)
{
if (pe->next >= pe->max || !pe->max) {
@ -1985,6 +1925,7 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info,
LineartData *la_data,
ListBase *shadow_elns)
{
using namespace blender;
Mesh *me = ob_info->original_me;
if (!me->totedge) {
return;
@ -2158,15 +2099,18 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info,
if (la_data->conf.use_loose) {
/* Only identifying floating edges at this point because other edges has been taken care of
* inside #lineart_identify_mlooptri_feature_edges function. */
TaskParallelSettings edge_loose_settings;
BLI_parallel_range_settings_defaults(&edge_loose_settings);
edge_loose_settings.min_iter_per_thread = 4000;
edge_loose_settings.func_reduce = loose_data_sum_reduce;
edge_loose_settings.userdata_chunk = &loose_data;
edge_loose_settings.userdata_chunk_size = sizeof(LooseEdgeData);
loose_data.edges = BKE_mesh_edges(me);
BLI_task_parallel_range(
0, me->totedge, &loose_data, lineart_identify_loose_edges, &edge_loose_settings);
const bke::LooseEdgeCache &loose_edges = me->loose_edges();
loose_data.loose_array = static_cast<int *>(
MEM_malloc_arrayN(loose_edges.count, sizeof(int), __func__));
if (loose_edges.count > 0) {
int loose_i = 0;
for (const int64_t edge_i : IndexRange(me->totedge)) {
if (loose_edges.is_loose_bits[edge_i]) {
loose_data.loose_array[loose_i] = int(edge_i);
loose_i++;
}
}
}
}
int allocate_la_e = edge_reduce.feat_edges + loose_data.loose_count;
@ -2271,8 +2215,9 @@ static void lineart_geometry_object_load(LineartObjectInfo *ob_info,
}
if (loose_data.loose_array) {
const Span<MEdge> edges = me->edges();
for (int i = 0; i < loose_data.loose_count; i++) {
const MEdge *edge = &loose_data.edges[loose_data.loose_array[i]];
const MEdge *edge = &edges[loose_data.loose_array[i]];
la_edge->v1 = &la_v_arr[edge->v1];
la_edge->v2 = &la_v_arr[edge->v2];
la_edge->flags = LRT_EDGE_FLAG_LOOSE;

View File

@ -200,21 +200,23 @@ void GeometryExporter::export_key_mesh(Object *ob, Mesh *me, KeyBlock *kb)
void GeometryExporter::createLooseEdgeList(Object *ob, Mesh *me, std::string &geom_id)
{
using namespace blender;
const Span<MEdge> edges = me->edges();
int totedges = me->totedge;
int edges_in_linelist = 0;
std::vector<uint> edge_list;
int index;
/* Find all loose edges in Mesh
* and save vertex indices in edge_list */
for (index = 0; index < totedges; index++) {
const MEdge *edge = &edges[index];
if (edge->flag & ME_LOOSEEDGE) {
edges_in_linelist += 1;
edge_list.push_back(edge->v1);
edge_list.push_back(edge->v2);
const bke::LooseEdgeCache &loose_edges = me->loose_edges();
if (loose_edges.count > 0) {
for (const int64_t i : edges.index_range()) {
if (loose_edges.is_loose_bits[i]) {
const MEdge *edge = &edges[i];
edges_in_linelist += 1;
edge_list.push_back(edge->v1);
edge_list.push_back(edge->v2);
}
}
}

View File

@ -593,7 +593,6 @@ void MeshImporter::read_lines(COLLADAFW::Mesh *mesh, Mesh *me)
uint *indices = mp->getPositionIndices().getData();
for (int j = 0; j < edge_count; j++, med++) {
med->flag |= ME_LOOSEEDGE;
med->v1 = indices[2 * j];
med->v2 = indices[2 * j + 1];
}

View File

@ -416,11 +416,16 @@ void OBJWriter::write_edges_indices(FormatHandler &fh,
const IndexOffsets &offsets,
const OBJMesh &obj_mesh_data) const
{
/* NOTE: ensure_mesh_edges should be called before. */
const Span<MEdge> edges = obj_mesh_data.get_mesh()->edges();
for (const int i : edges.index_range()) {
const MEdge &edge = edges[i];
if (edge.flag & ME_LOOSEEDGE) {
const Mesh &mesh = *obj_mesh_data.get_mesh();
const bke::LooseEdgeCache &loose_edges = mesh.loose_edges();
if (loose_edges.count == 0) {
return;
}
const Span<MEdge> edges = mesh.edges();
for (const int64_t i : edges.index_range()) {
if (loose_edges.is_loose_bits[i]) {
const MEdge &edge = edges[i];
fh.write_obj_edge(edge.v1 + offsets.vertex_offset + 1, edge.v2 + offsets.vertex_offset + 1);
}
}

View File

@ -181,11 +181,6 @@ void OBJMesh::ensure_mesh_normals() const
BKE_mesh_calc_normals_split(export_mesh_eval_);
}
void OBJMesh::ensure_mesh_edges() const
{
BKE_mesh_calc_edges_loose(export_mesh_eval_);
}
void OBJMesh::calc_smooth_groups(const bool use_bitflags)
{
const Span<MEdge> edges = export_mesh_eval_->edges();

View File

@ -132,7 +132,6 @@ class OBJMesh : NonCopyable {
const Material *get_object_material(int16_t mat_nr) const;
void ensure_mesh_normals() const;
void ensure_mesh_edges() const;
/**
* Calculate smooth groups of a smooth-shaded object.

View File

@ -160,7 +160,6 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
if (export_params.export_normals) {
obj.ensure_mesh_normals();
}
obj.ensure_mesh_edges();
}
/* Parallel over meshes: store normal coords & indices, uv coords and indices. */

View File

@ -259,7 +259,6 @@ void MeshFromGeometry::create_edges(Mesh *mesh)
/* Set argument `update` to true so that existing, explicitly imported edges can be merged
* with the new ones created from polygons. */
BKE_mesh_calc_edges(mesh, true, false);
BKE_mesh_calc_edges_loose(mesh);
}
void MeshFromGeometry::create_uv_verts(Mesh *mesh)

View File

@ -22,6 +22,7 @@ namespace bke {
struct MeshRuntime;
class AttributeAccessor;
class MutableAttributeAccessor;
struct LooseEdgeCache;
} // namespace bke
} // namespace blender
using MeshRuntimeHandle = blender::bke::MeshRuntime;
@ -253,6 +254,19 @@ typedef struct Mesh {
* Cached triangulation of the mesh.
*/
blender::Span<MLoopTri> looptris() const;
/**
* Cached information about loose edges, calculated lazily when necessary.
*/
const blender::bke::LooseEdgeCache &loose_edges() const;
/**
* Explicitly set the cached number of loose edges to zero. This can improve performance
* later on, because finding loose edges lazily can be skipped entirely.
*
* \note To allow setting this status on meshes without changing them, this This does not tag the
* cache dirty. If the mesh was changed first, the relevant dirty tags should be called first.
*/
void loose_edges_tag_none() const;
#endif
} Mesh;

View File

@ -71,9 +71,12 @@ enum {
/* SELECT = (1 << 0), */
ME_EDGEDRAW = (1 << 1),
ME_SEAM = (1 << 2),
/** Deprecated hide status. Now stored in ".hide_edge" attribute. */
/* ME_HIDE = (1 << 4), */
/** Deprecated hide status. Now stored in ".hide_edge" attribute. */
/* ME_HIDE = (1 << 4), */
#ifdef DNA_DEPRECATED_ALLOW
/** Deprecated loose edge status. Now stored in #Mesh::loose_edges() runtime cache. */
ME_LOOSEEDGE = (1 << 7),
#endif
ME_SHARP = (1 << 9), /* only reason this flag remains a 'short' */
};

View File

@ -1485,6 +1485,13 @@ static void rna_MeshEdge_select_set(PointerRNA *ptr, bool value)
select_edge[index] = value;
}
static bool rna_MeshEdge_is_loose_get(PointerRNA *ptr)
{
const Mesh *mesh = rna_mesh(ptr);
const int index = rna_MeshEdge_index_get(ptr);
return ED_mesh_edge_is_loose(mesh, index);
}
static int rna_MeshLoopTriangle_material_index_get(PointerRNA *ptr)
{
const Mesh *me = rna_mesh(ptr);
@ -2341,8 +2348,9 @@ static void rna_def_medge(BlenderRNA *brna)
RNA_def_property_update(prop, 0, "rna_Mesh_update_data_legacy_deg_tag_all");
prop = RNA_def_property(srna, "is_loose", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_LOOSEEDGE);
RNA_def_property_ui_text(prop, "Loose", "Loose edge");
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_boolean_funcs(prop, "rna_MeshEdge_is_loose_get", NULL);
RNA_def_property_ui_text(prop, "Loose", "Edge is not connected to any faces");
prop = RNA_def_property(srna, "use_freestyle_mark", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(

View File

@ -774,8 +774,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext * /*ctx*/, M
edges_add_num);
}
BKE_mesh_calc_edges_loose(result);
return result;
}

View File

@ -431,19 +431,13 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
mv_new = mvert_new;
mv_orig = mvert_orig;
BLI_bitmap *vert_tag = BLI_BITMAP_NEW(totvert, __func__);
/* Copy the first set of edges */
const MEdge *med_orig = medge_orig;
med_new = medge_new;
for (i = 0; i < totedge; i++, med_orig++, med_new++) {
med_new->v1 = med_orig->v1;
med_new->v2 = med_orig->v2;
med_new->flag = med_orig->flag & ~ME_LOOSEEDGE;
/* Tag #MVert as not loose. */
BLI_BITMAP_ENABLE(vert_tag, med_orig->v1);
BLI_BITMAP_ENABLE(vert_tag, med_orig->v2);
med_new->flag = med_orig->flag;
}
/* build polygon -> edge map */
@ -815,9 +809,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
med_new->v1 = varray_stride + j;
med_new->v2 = med_new->v1 - totvert;
med_new->flag = ME_EDGEDRAW;
if (!BLI_BITMAP_TEST(vert_tag, j)) {
med_new->flag |= ME_LOOSEEDGE;
}
med_new++;
}
}
@ -836,9 +827,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
med_new->v1 = i;
med_new->v2 = varray_stride + i;
med_new->flag = ME_EDGEDRAW;
if (!BLI_BITMAP_TEST(vert_tag, i)) {
med_new->flag |= ME_LOOSEEDGE;
}
med_new++;
}
}
@ -994,7 +982,7 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
/* new vertical edge */
med_new->v1 = i1;
med_new->v2 = i2;
med_new->flag = med_new_firstloop->flag & ~ME_LOOSEEDGE;
med_new->flag = med_new_firstloop->flag;
med_new++;
}
@ -1025,8 +1013,6 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *
}
#endif
MEM_freeN(vert_tag);
if (edge_poly_map) {
MEM_freeN(edge_poly_map);
}

View File

@ -101,7 +101,7 @@ static Mesh *hull_from_bullet(const Mesh *mesh, Span<float3> coords)
MEdge &edge = edges[0];
edge.v1 = 0;
edge.v2 = 1;
edge.flag |= ME_EDGEDRAW | ME_LOOSEEDGE;
edge.flag = ME_EDGEDRAW;
edge_index++;
}
BLI_assert(edge_index == edges_num);

View File

@ -1053,7 +1053,6 @@ static void do_mesh_separation(GeometrySet &geometry_set,
}
}
BKE_mesh_calc_edges_loose(mesh_out);
geometry_set.replace_mesh(mesh_out);
}

View File

@ -579,6 +579,8 @@ static void duplicate_faces(GeometrySet &geometry_set,
}
}
new_mesh->loose_edges_tag_none();
copy_face_attributes_without_id(geometry_set,
edge_mapping,
vert_mapping,
@ -745,7 +747,6 @@ static void duplicate_edges(GeometrySet &geometry_set,
MEdge &new_edge = new_edges[edge_range[i_duplicate]];
new_edge.v1 = vert_range[i_duplicate * 2];
new_edge.v2 = vert_range[i_duplicate * 2] + 1;
new_edge.flag = ME_LOOSEEDGE;
}
}
});

View File

@ -158,15 +158,6 @@ static MEdge new_edge(const int v1, const int v2)
return edge;
}
static MEdge new_loose_edge(const int v1, const int v2)
{
MEdge edge;
edge.v1 = v1;
edge.v2 = v2;
edge.flag = ME_LOOSEEDGE;
return edge;
}
static MPoly new_poly(const int loopstart, const int totloop)
{
MPoly poly;
@ -234,7 +225,7 @@ static void extrude_mesh_vertices(Mesh &mesh,
MutableSpan<MEdge> new_edges = mesh.edges_for_write().slice(new_edge_range);
for (const int i_selection : selection.index_range()) {
new_edges[i_selection] = new_loose_edge(selection[i_selection], new_vert_range[i_selection]);
new_edges[i_selection] = new_edge(selection[i_selection], new_vert_range[i_selection]);
}
MutableAttributeAccessor attributes = mesh.attributes_for_write();

View File

@ -125,14 +125,11 @@ static Mesh *create_circle_mesh(const float radius,
}
/* Create outer edges. */
const short edge_flag = (fill_type == GEO_NODE_MESH_CIRCLE_FILL_NONE) ?
ME_LOOSEEDGE :
ME_EDGEDRAW; /* NGON or TRIANGLE_FAN */
for (const int i : IndexRange(verts_num)) {
MEdge &edge = edges[i];
edge.v1 = i;
edge.v2 = (i + 1) % verts_num;
edge.flag = edge_flag;
edge.flag = ME_EDGEDRAW;
}
/* Create triangle fan edges. */

View File

@ -697,6 +697,8 @@ Mesh *create_cylinder_or_cone_mesh(const float radius_top,
calculate_cone_uvs(mesh, config);
calculate_selection_outputs(mesh, config, attribute_outputs);
mesh->loose_edges_tag_none();
return mesh;
}

View File

@ -76,7 +76,6 @@ Mesh *create_grid_mesh(const int verts_x,
const int y_edges_start = 0;
const int x_edges_start = verts_x * edges_y;
const short edge_flag = (edges_x == 0 || edges_y == 0) ? ME_LOOSEEDGE : ME_EDGEDRAW;
/* Build the horizontal edges in the X direction. */
threading::parallel_for(IndexRange(verts_x), 512, [&](IndexRange x_range) {
@ -89,7 +88,7 @@ Mesh *create_grid_mesh(const int verts_x,
MEdge &edge = edges[y_edge_offset + y];
edge.v1 = vert_index;
edge.v2 = vert_index + 1;
edge.flag = edge_flag;
edge.flag = ME_EDGEDRAW;
}
});
}
@ -105,7 +104,7 @@ Mesh *create_grid_mesh(const int verts_x,
MEdge &edge = edges[x_edge_offset + x];
edge.v1 = vert_index;
edge.v2 = vert_index + verts_y;
edge.flag = edge_flag;
edge.flag = ME_EDGEDRAW;
}
});
}
@ -144,6 +143,8 @@ Mesh *create_grid_mesh(const int verts_x,
calculate_uvs(mesh, verts, loops, size_x, size_y);
}
mesh->loose_edges_tag_none();
return mesh;
}

View File

@ -196,7 +196,6 @@ Mesh *create_line_mesh(const float3 start, const float3 delta, const int count)
for (const int i : range) {
edges[i].v1 = i;
edges[i].v2 = i + 1;
edges[i].flag |= ME_LOOSEEDGE;
}
});
});

View File

@ -319,6 +319,8 @@ static Mesh *create_uv_sphere_mesh(const float radius, const int segments, const
[&]() { calculate_sphere_corners(loops, segments, rings); },
[&]() { calculate_sphere_uvs(mesh, segments, rings); });
mesh->loose_edges_tag_none();
return mesh;
}

View File

@ -62,6 +62,8 @@ static void geometry_set_points_to_vertices(GeometrySet &geometry_set,
}
}
mesh->loose_edges_tag_none();
geometry_set.keep_only_during_modify({GEO_COMPONENT_TYPE_MESH});
}

View File

@ -334,7 +334,7 @@ add_blender_test(
--run={'FINISHED'}&bpy.ops.export_scene.obj\(filepath='${TEST_OUT_DIR}/io_tests/export_obj_cube.obj',use_selection=False\)
--md5_source=${TEST_OUT_DIR}/io_tests/export_obj_cube.obj
--md5_source=${TEST_OUT_DIR}/io_tests/export_obj_cube.mtl
--md5=95832f81160f07101dc566cb286a9f76 --md5_method=FILE
--md5=e80660437ad9bfe082849641c361a233 --md5_method=FILE
)
add_blender_test(