From ba1c8fe6a53a00c9324607db6a1836132cef5352 Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Wed, 22 Nov 2023 15:21:58 +0100 Subject: [PATCH] Mesh: Improve remesh attribute transfer Currently we have options to transfer the paint mask, face sets, and color attributes to the new mesh created by voxel remesh. This doesn't make use of the generic attribute design, where it would be clearer to transfer all attributes with the same methods. That's reflected in the code as well-- we do duplicate work for the mask and vertex colors, for example. This commit replaces the transfer options with a single checkbox for all attributes. All attribute types on all domains are transferred with basically the same method as the "Sample Nearest" node from geometry nodes-- they take the value from the nearest source element of the same domain. Face corners are handled differently than before. Instead of retrieving the mixed value of all the corners from the nearest source vertex, the value from the nearest corner of the nearest face. --- Some timing information, showing that when interpolating the same data, the attribute propagation is significantly faster than before. Edge and corner attributes would add some cost (edges more than corners), but might not always be present. Before ``` voxel_remesh_exec: 3834.63 ms BKE_shrinkwrap_remesh_target_project: 1141.17 ms BKE_mesh_remesh_reproject_paint_mask: 689.35 ms BKE_remesh_reproject_sculpt_face_sets: 257.14 ms BKE_remesh_reproject_vertex_paint: 54.64 ms BKE_mesh_smooth_flag_set: 0.15 ms ``` After ``` voxel_remesh_exec: 3339.32 ms BKE_shrinkwrap_remesh_target_project: 1158.76 ms mesh_remesh_reproject_attributes: 517.52 ms ``` Pull Request: https://projects.blender.org/blender/blender/pulls/115116 --- scripts/startup/bl_ui/properties_data_mesh.py | 4 +- scripts/startup/bl_ui/space_view3d_toolbar.py | 4 +- .../blenkernel/BKE_mesh_remesh_voxel.hh | 7 +- .../blenkernel/intern/mesh_remesh_voxel.cc | 429 ++++++++++++------ source/blender/blenlib/BLI_offset_indices.hh | 2 +- .../blenloader/intern/versioning_280.cc | 2 +- .../blenloader/intern/versioning_defaults.cc | 3 +- .../blender/editors/object/object_remesh.cc | 34 +- source/blender/makesdna/DNA_mesh_defaults.h | 2 +- source/blender/makesdna/DNA_mesh_types.h | 6 +- source/blender/makesrna/intern/rna_mesh.cc | 22 +- 11 files changed, 309 insertions(+), 206 deletions(-) diff --git a/scripts/startup/bl_ui/properties_data_mesh.py b/scripts/startup/bl_ui/properties_data_mesh.py index f510c37f9bb..61a552bd3dd 100644 --- a/scripts/startup/bl_ui/properties_data_mesh.py +++ b/scripts/startup/bl_ui/properties_data_mesh.py @@ -426,9 +426,7 @@ class DATA_PT_remesh(MeshButtonsPanel, Panel): col = layout.column(heading="Preserve") col.prop(mesh, "use_remesh_preserve_volume", text="Volume") - col.prop(mesh, "use_remesh_preserve_paint_mask", text="Paint Mask") - col.prop(mesh, "use_remesh_preserve_sculpt_face_sets", text="Face Sets") - col.prop(mesh, "use_remesh_preserve_vertex_colors", text="Color Attributes") + col.prop(mesh, "use_remesh_preserve_attributes", text="Attributes") col.operator("object.voxel_remesh", text="Voxel Remesh") else: diff --git a/scripts/startup/bl_ui/space_view3d_toolbar.py b/scripts/startup/bl_ui/space_view3d_toolbar.py index 1f4f886c5d3..d2ea04f8f14 100644 --- a/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -1045,9 +1045,7 @@ class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel): col = layout.column(heading="Preserve", align=True) col.prop(mesh, "use_remesh_preserve_volume", text="Volume") - col.prop(mesh, "use_remesh_preserve_paint_mask", text="Paint Mask") - col.prop(mesh, "use_remesh_preserve_sculpt_face_sets", text="Face Sets") - col.prop(mesh, "use_remesh_preserve_vertex_colors", text="Color Attributes") + col.prop(mesh, "use_remesh_preserve_attributes", text="Attributes") layout.operator("object.voxel_remesh", text="Remesh") diff --git a/source/blender/blenkernel/BKE_mesh_remesh_voxel.hh b/source/blender/blenkernel/BKE_mesh_remesh_voxel.hh index 0d37e935669..621a0ddcacf 100644 --- a/source/blender/blenkernel/BKE_mesh_remesh_voxel.hh +++ b/source/blender/blenkernel/BKE_mesh_remesh_voxel.hh @@ -21,7 +21,6 @@ Mesh *BKE_mesh_remesh_quadriflow(const Mesh *mesh, void (*update_cb)(void *, float progress, int *cancel), void *update_cb_data); -/* Data reprojection functions */ -void BKE_mesh_remesh_reproject_paint_mask(Mesh *target, const Mesh *source); -void BKE_remesh_reproject_vertex_paint(Mesh *target, const Mesh *source); -void BKE_remesh_reproject_sculpt_face_sets(Mesh *target, const Mesh *source); +namespace blender::bke { +void mesh_remesh_reproject_attributes(const Mesh &src, Mesh &dst); +} \ No newline at end of file diff --git a/source/blender/blenkernel/intern/mesh_remesh_voxel.cc b/source/blender/blenkernel/intern/mesh_remesh_voxel.cc index c3b518017b8..ee20022f2a9 100644 --- a/source/blender/blenkernel/intern/mesh_remesh_voxel.cc +++ b/source/blender/blenkernel/intern/mesh_remesh_voxel.cc @@ -17,6 +17,7 @@ #include "BLI_array.hh" #include "BLI_array_utils.hh" +#include "BLI_enumerable_thread_specific.hh" #include "BLI_index_range.hh" #include "BLI_math_vector.h" #include "BLI_span.hh" @@ -277,191 +278,329 @@ Mesh *BKE_mesh_remesh_voxel(const Mesh *mesh, #endif } -void BKE_mesh_remesh_reproject_paint_mask(Mesh *target, const Mesh *source) +namespace blender::bke { + +static void calc_edge_centers(const Span positions, + const Span edges, + MutableSpan edge_centers) { - BVHTreeFromMesh bvhtree = {nullptr}; - BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2); - const Span target_positions = target->vert_positions(); - const float *source_mask = (const float *)CustomData_get_layer_named( - &source->vert_data, CD_PROP_FLOAT, ".sculpt_mask"); - if (source_mask == nullptr) { - return; + for (const int i : edges.index_range()) { + edge_centers[i] = math::midpoint(positions[edges[i][0]], positions[edges[i][1]]); } +} - float *target_mask; - if (CustomData_has_layer_named(&target->vert_data, CD_PROP_FLOAT, ".sculpt_mask")) { - target_mask = (float *)CustomData_get_layer_named( - &target->vert_data, CD_PROP_FLOAT, ".sculpt_mask"); - } - else { - target_mask = (float *)CustomData_add_layer_named( - &target->vert_data, CD_PROP_FLOAT, CD_CONSTRUCT, target->totvert, ".sculpt_mask"); +static void calc_face_centers(const Span positions, + const OffsetIndices faces, + const Span corner_verts, + MutableSpan face_centers) +{ + for (const int i : faces.index_range()) { + face_centers[i] = mesh::face_center_calc(positions, corner_verts.slice(faces[i])); } +} - blender::threading::parallel_for(IndexRange(target->totvert), 4096, [&](const IndexRange range) { - for (const int i : range) { - BVHTreeNearest nearest; - nearest.index = -1; - nearest.dist_sq = FLT_MAX; - BLI_bvhtree_find_nearest( - bvhtree.tree, target_positions[i], &nearest, bvhtree.nearest_callback, &bvhtree); - if (nearest.index != -1) { - target_mask[i] = source_mask[nearest.index]; +static void find_nearest_tris(const Span positions, + BVHTreeFromMesh &bvhtree, + MutableSpan tris) +{ + for (const int i : positions.index_range()) { + BVHTreeNearest nearest; + nearest.index = -1; + nearest.dist_sq = FLT_MAX; + BLI_bvhtree_find_nearest( + bvhtree.tree, positions[i], &nearest, bvhtree.nearest_callback, &bvhtree); + tris[i] = nearest.index; + } +} + +static void find_nearest_tris_parallel(const Span positions, + BVHTreeFromMesh &bvhtree, + MutableSpan tris) +{ + threading::parallel_for(tris.index_range(), 512, [&](const IndexRange range) { + find_nearest_tris(positions.slice(range), bvhtree, tris.slice(range)); + }); +} + +static void find_nearest_verts(const Span positions, + const Span corner_verts, + const Span src_tris, + const Span dst_positions, + const Span nearest_vert_tris, + MutableSpan nearest_verts) +{ + threading::parallel_for(dst_positions.index_range(), 512, [&](const IndexRange range) { + for (const int dst_vert : range) { + const float3 &dst_position = dst_positions[dst_vert]; + const MLoopTri &src_tri = src_tris[nearest_vert_tris[dst_vert]]; + + std::array distances; + for (const int i : IndexRange(3)) { + const int src_vert = corner_verts[src_tri.tri[i]]; + distances[i] = math::distance_squared(positions[src_vert], dst_position); } + + const int min = std::min_element(distances.begin(), distances.end()) - distances.begin(); + nearest_verts[dst_vert] = corner_verts[src_tri.tri[min]]; } }); - free_bvhtree_from_mesh(&bvhtree); } -void BKE_remesh_reproject_sculpt_face_sets(Mesh *target, const Mesh *source) +static void find_nearest_faces(const Span src_tri_faces, + const Span dst_positions, + const OffsetIndices dst_faces, + const Span dst_corner_verts, + BVHTreeFromMesh &bvhtree, + MutableSpan nearest_faces) { - using namespace blender; - using namespace blender::bke; - const AttributeAccessor src_attributes = source->attributes(); - MutableAttributeAccessor dst_attributes = target->attributes_for_write(); - const Span target_positions = target->vert_positions(); - const OffsetIndices target_faces = target->faces(); - const Span target_corner_verts = target->corner_verts(); + struct TLS { + Vector face_centers; + Vector tri_indices; + }; + threading::EnumerableThreadSpecific all_tls; + threading::parallel_for(dst_faces.index_range(), 512, [&](const IndexRange range) { + TLS &tls = all_tls.local(); + Vector &face_centers = tls.face_centers; + face_centers.reinitialize(range.size()); + calc_face_centers(dst_positions, dst_faces.slice(range), dst_corner_verts, face_centers); - const VArray src_face_sets = *src_attributes.lookup(".sculpt_face_set", ATTR_DOMAIN_FACE); - if (!src_face_sets) { - return; - } - SpanAttributeWriter dst_face_sets = dst_attributes.lookup_or_add_for_write_only_span( - ".sculpt_face_set", ATTR_DOMAIN_FACE); - if (!dst_face_sets) { - return; - } + Vector &tri_indices = tls.tri_indices; + tri_indices.reinitialize(range.size()); + find_nearest_tris(face_centers, bvhtree, tri_indices); - const VArraySpan src(src_face_sets); - MutableSpan dst = dst_face_sets.span; - - const blender::Span looptri_faces = source->looptri_faces(); - BVHTreeFromMesh bvhtree = {nullptr}; - BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_LOOPTRI, 2); - - blender::threading::parallel_for( - IndexRange(target->faces_num), 2048, [&](const IndexRange range) { - for (const int i : range) { - BVHTreeNearest nearest; - nearest.index = -1; - nearest.dist_sq = FLT_MAX; - const float3 from_co = mesh::face_center_calc( - target_positions, target_corner_verts.slice(target_faces[i])); - BLI_bvhtree_find_nearest( - bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree); - if (nearest.index != -1) { - dst[i] = src[looptri_faces[nearest.index]]; - } - else { - dst[i] = 1; - } - } - }); - free_bvhtree_from_mesh(&bvhtree); - dst_face_sets.finish(); + array_utils::gather(src_tri_faces, tri_indices.as_span(), nearest_faces.slice(range)); + }); } -void BKE_remesh_reproject_vertex_paint(Mesh *target, const Mesh *source) +static void find_nearest_corners(const Span src_positions, + const OffsetIndices src_faces, + const Span src_corner_verts, + const Span src_tri_faces, + const Span dst_positions, + const Span dst_corner_verts, + const Span nearest_vert_tris, + MutableSpan nearest_corners) { - using namespace blender; - using namespace blender::bke; - const AttributeAccessor src_attributes = source->attributes(); - MutableAttributeAccessor dst_attributes = target->attributes_for_write(); + threading::parallel_for(nearest_corners.index_range(), 512, [&](const IndexRange range) { + Vector distances; + for (const int dst_corner : range) { + const int dst_vert = dst_corner_verts[dst_corner]; + const float3 &dst_position = dst_positions[dst_vert]; + const int src_tri = nearest_vert_tris[dst_vert]; + const IndexRange src_face = src_faces[src_tri_faces[src_tri]]; + const Span src_face_verts = src_corner_verts.slice(src_face); + + /* Find the corner in the face that's closest in the closest face. */ + distances.reinitialize(src_face_verts.size()); + for (const int i : src_face_verts.index_range()) { + const int src_vert = src_face_verts[i]; + distances[i] = math::distance_squared(src_positions[src_vert], dst_position); + } + + const int min = std::min_element(distances.begin(), distances.end()) - distances.begin(); + nearest_corners[dst_corner] = src_face[min]; + } + }); +} + +static void find_nearest_edges(const Span src_positions, + const Span src_edges, + const OffsetIndices src_faces, + const Span src_corner_edges, + const Span src_tri_faces, + const Span dst_positions, + const Span dst_edges, + BVHTreeFromMesh &bvhtree, + MutableSpan nearest_edges) +{ + struct TLS { + Vector edge_centers; + Vector tri_indices; + Vector face_indices; + Vector distances; + }; + threading::EnumerableThreadSpecific all_tls; + threading::parallel_for(nearest_edges.index_range(), 512, [&](const IndexRange range) { + TLS &tls = all_tls.local(); + Vector &edge_centers = tls.edge_centers; + edge_centers.reinitialize(range.size()); + calc_edge_centers(dst_positions, dst_edges.slice(range), edge_centers); + + Vector &tri_indices = tls.tri_indices; + tri_indices.reinitialize(range.size()); + find_nearest_tris_parallel(edge_centers, bvhtree, tri_indices); + + Vector &face_indices = tls.face_indices; + face_indices.reinitialize(range.size()); + array_utils::gather(src_tri_faces, tri_indices.as_span(), face_indices.as_mutable_span()); + + /* Find the source edge that's closest to the destination edge in the nearest face. Search + * through the whole face instead of just the triangle because the triangle has edges that + * might not be actual mesh edges. */ + Vector distances; + for (const int i : range.index_range()) { + const int dst_edge = range[i]; + const float3 &dst_position = edge_centers[i]; + + const int src_face = face_indices[i]; + const Span src_face_edges = src_corner_edges.slice(src_faces[src_face]); + + distances.reinitialize(src_face_edges.size()); + for (const int i : src_face_edges.index_range()) { + const int2 src_edge = src_edges[src_face_edges[i]]; + const float3 src_center = math::midpoint(src_positions[src_edge[0]], + src_positions[src_edge[1]]); + distances[i] = math::distance_squared(src_center, dst_position); + } + + const int min = std::min_element(distances.begin(), distances.end()) - distances.begin(); + nearest_edges[dst_edge] = src_face_edges[min]; + } + }); +} + +static void gather_attributes(const Span ids, + const AttributeAccessor src_attributes, + const eAttrDomain domain, + const Span index_map, + MutableAttributeAccessor dst_attributes) +{ + for (const AttributeIDRef &id : ids) { + const GVArraySpan src = *src_attributes.lookup(id, domain); + const eCustomDataType type = cpp_type_to_custom_data_type(src.type()); + GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span(id, domain, type); + attribute_math::gather(src, index_map, dst.span); + dst.finish(); + } +} + +void mesh_remesh_reproject_attributes(const Mesh &src, Mesh &dst) +{ + /* Gather attributes to tranfer for each domain. This makes it possible to skip + * building index maps and even the main BVH tree if there are no attributes. */ + const AttributeAccessor src_attributes = src.attributes(); Vector point_ids; + Vector edge_ids; + Vector face_ids; Vector corner_ids; - source->attributes().for_all([&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { - if (CD_TYPE_AS_MASK(meta_data.data_type) & CD_MASK_COLOR_ALL) { - if (meta_data.domain == ATTR_DOMAIN_POINT) { + src_attributes.for_all([&](const AttributeIDRef &id, const AttributeMetaData &meta_data) { + if (ELEM(id.name(), "position", ".edge_verts", ".corner_vert", ".corner_edge")) { + return true; + } + switch (meta_data.domain) { + case ATTR_DOMAIN_POINT: point_ids.append(id); - } - else if (meta_data.domain == ATTR_DOMAIN_CORNER) { + break; + case ATTR_DOMAIN_EDGE: + edge_ids.append(id); + break; + case ATTR_DOMAIN_FACE: + face_ids.append(id); + break; + case ATTR_DOMAIN_CORNER: corner_ids.append(id); - } + break; + default: + BLI_assert_unreachable(); + break; } return true; }); - if (point_ids.is_empty() && corner_ids.is_empty()) { + if (point_ids.is_empty() && edge_ids.is_empty() && face_ids.is_empty() && corner_ids.is_empty()) + { return; } - GroupedSpan source_lmap; - GroupedSpan target_lmap; - BVHTreeFromMesh bvhtree = {nullptr}; - threading::parallel_invoke( - [&]() { BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2); }, - [&]() { source_lmap = source->vert_to_corner_map(); }, - [&]() { target_lmap = target->vert_to_corner_map(); }); + const Span src_positions = src.vert_positions(); + const OffsetIndices src_faces = src.faces(); + const Span src_corner_verts = src.corner_verts(); + const Span src_tris = src.looptris(); - const Span target_positions = target->vert_positions(); - Array nearest_src_verts(target_positions.size()); - threading::parallel_for(target_positions.index_range(), 1024, [&](const IndexRange range) { - for (const int i : range) { - BVHTreeNearest nearest; - nearest.index = -1; - nearest.dist_sq = FLT_MAX; - BLI_bvhtree_find_nearest( - bvhtree.tree, target_positions[i], &nearest, bvhtree.nearest_callback, &bvhtree); - nearest_src_verts[i] = nearest.index; + /* The main idea in the following code is to trade some complexity in sampling for the benefit of + * only using and building a single BVH tree. Since sculpt mode doesn't generally deal with loose + * vertices and edges, we use the standard "triangles" BVH which won't contain them. Also, only + * relying on a single BVH should reduce memory usage, and work better if the BVH and PBVH are + * ever merged. + * + * One key decision is separating building transfer index maps from actually transferring any + * attribute data. This is important to keep attribute storage independent from the specifics of + * the decisions made here, which mainly results in easier refactoring, more generic code, and + * possibly improved performance from lower cache usage in the "complex" sampling part of the + * algorithm and the copying itself. */ + BVHTreeFromMesh bvhtree{}; + BKE_bvhtree_from_mesh_get(&bvhtree, &src, BVHTREE_FROM_LOOPTRI, 2); + + const Span dst_positions = dst.vert_positions(); + const OffsetIndices dst_faces = dst.faces(); + const Span dst_corner_verts = dst.corner_verts(); + + MutableAttributeAccessor dst_attributes = dst.attributes_for_write(); + + if (!point_ids.is_empty() || !corner_ids.is_empty()) { + Array vert_nearest_tris(dst_positions.size()); + find_nearest_tris_parallel(dst_positions, bvhtree, vert_nearest_tris); + + if (!point_ids.is_empty()) { + Array map(dst.totvert); + find_nearest_verts( + src_positions, src_corner_verts, src_tris, dst_positions, vert_nearest_tris, map); + gather_attributes(point_ids, src_attributes, ATTR_DOMAIN_POINT, map, dst_attributes); } - }); - for (const AttributeIDRef &id : point_ids) { - const GVArraySpan src = *src_attributes.lookup(id, ATTR_DOMAIN_POINT); - GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - id, ATTR_DOMAIN_POINT, cpp_type_to_custom_data_type(src.type())); - attribute_math::gather(src, nearest_src_verts, dst.span); - dst.finish(); - } - - if (!corner_ids.is_empty()) { - for (const AttributeIDRef &id : corner_ids) { - const GVArraySpan src = *src_attributes.lookup(id, ATTR_DOMAIN_CORNER); - GSpanAttributeWriter dst = dst_attributes.lookup_or_add_for_write_only_span( - id, ATTR_DOMAIN_CORNER, cpp_type_to_custom_data_type(src.type())); - - threading::parallel_for(target_positions.index_range(), 1024, [&](const IndexRange range) { - src.type().to_static_type_tag([&](auto type_tag) { - using T = typename decltype(type_tag)::type; - if constexpr (std::is_void_v) { - BLI_assert_unreachable(); - } - else { - const Span src_typed = src.typed(); - MutableSpan dst_typed = dst.span.typed(); - for (const int dst_vert : range) { - /* Find the average value at the corners of the closest vertex on the - * source mesh. */ - const int src_vert = nearest_src_verts[dst_vert]; - T value; - typename blender::bke::attribute_math::DefaultMixer mixer({&value, 1}); - for (const int corner : source_lmap[src_vert]) { - mixer.mix_in(0, src_typed[corner]); - } - - dst_typed.fill_indices(target_lmap[dst_vert], value); - } - } - }); - }); - - dst.finish(); + if (!corner_ids.is_empty()) { + const Span src_tri_faces = src.looptri_faces(); + Array map(dst.totloop); + find_nearest_corners(src_positions, + src_faces, + src_corner_verts, + src_tri_faces, + dst_positions, + dst_corner_verts, + vert_nearest_tris, + map); + gather_attributes(corner_ids, src_attributes, ATTR_DOMAIN_CORNER, map, dst_attributes); } } - /* Make sure active/default color attribute (names) are brought over. */ - if (source->active_color_attribute) { - BKE_id_attributes_active_color_set(&target->id, source->active_color_attribute); + if (!edge_ids.is_empty()) { + const Span src_edges = src.edges(); + const Span src_corner_edges = src.corner_edges(); + const Span src_tri_faces = src.looptri_faces(); + const Span dst_edges = dst.edges(); + Array map(dst.totedge); + find_nearest_edges(src_positions, + src_edges, + src_faces, + src_corner_edges, + src_tri_faces, + dst_positions, + dst_edges, + bvhtree, + map); + gather_attributes(edge_ids, src_attributes, ATTR_DOMAIN_EDGE, map, dst_attributes); } - if (source->default_color_attribute) { - BKE_id_attributes_default_color_set(&target->id, source->default_color_attribute); + + if (!face_ids.is_empty()) { + const Span src_tri_faces = src.looptri_faces(); + Array map(dst.faces_num); + find_nearest_faces(src_tri_faces, dst_positions, dst_faces, dst_corner_verts, bvhtree, map); + gather_attributes(face_ids, src_attributes, ATTR_DOMAIN_FACE, map, dst_attributes); + } + + if (src.active_color_attribute) { + BKE_id_attributes_active_color_set(&dst.id, src.active_color_attribute); + } + if (src.default_color_attribute) { + BKE_id_attributes_default_color_set(&dst.id, src.default_color_attribute); } free_bvhtree_from_mesh(&bvhtree); } +} // namespace blender::bke + Mesh *BKE_mesh_remesh_voxel_fix_poles(const Mesh *mesh) { const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh); diff --git a/source/blender/blenlib/BLI_offset_indices.hh b/source/blender/blenlib/BLI_offset_indices.hh index dff35644137..003d4f0889e 100644 --- a/source/blender/blenlib/BLI_offset_indices.hh +++ b/source/blender/blenlib/BLI_offset_indices.hh @@ -86,7 +86,7 @@ template class OffsetIndices { OffsetIndices slice(const IndexRange range) const { BLI_assert(offsets_.index_range().drop_back(1).contains(range.last())); - return OffsetIndices(offsets_.slice(range.start(), range.one_after_last())); + return OffsetIndices(offsets_.slice(range.start(), range.size() + 1)); } Span data() const diff --git a/source/blender/blenloader/intern/versioning_280.cc b/source/blender/blenloader/intern/versioning_280.cc index 46be4aa6a01..a18f8124b73 100644 --- a/source/blender/blenloader/intern/versioning_280.cc +++ b/source/blender/blenloader/intern/versioning_280.cc @@ -4712,7 +4712,7 @@ void blo_do_versions_280(FileData *fd, Library * /*lib*/, Main *bmain) LISTBASE_FOREACH (Mesh *, me, &bmain->meshes) { me->flag &= ~(ME_FLAG_UNUSED_0 | ME_FLAG_UNUSED_1 | ME_FLAG_UNUSED_3 | ME_FLAG_UNUSED_4 | - ME_FLAG_UNUSED_6 | ME_FLAG_UNUSED_7 | ME_REMESH_REPROJECT_VERTEX_COLORS); + ME_FLAG_UNUSED_6 | ME_FLAG_UNUSED_7 | ME_REMESH_REPROJECT_ATTRIBUTES); } LISTBASE_FOREACH (Material *, mat, &bmain->materials) { diff --git a/source/blender/blenloader/intern/versioning_defaults.cc b/source/blender/blenloader/intern/versioning_defaults.cc index 1314b38ed84..94507fd7bd3 100644 --- a/source/blender/blenloader/intern/versioning_defaults.cc +++ b/source/blender/blenloader/intern/versioning_defaults.cc @@ -594,8 +594,7 @@ void BLO_update_defaults_startup_blend(Main *bmain, const char *app_template) /* Match default for new meshes. */ mesh->smoothresh_legacy = DEG2RADF(30); /* Match voxel remesher options for all existing meshes in templates. */ - mesh->flag |= ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_PAINT_MASK | - ME_REMESH_REPROJECT_SCULPT_FACE_SETS | ME_REMESH_REPROJECT_VERTEX_COLORS; + mesh->flag |= ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_ATTRIBUTES; /* For Sculpting template. */ if (app_template && STREQ(app_template, "Sculpting")) { diff --git a/source/blender/editors/object/object_remesh.cc b/source/blender/editors/object/object_remesh.cc index a030d7ee032..8c8266fdc8a 100644 --- a/source/blender/editors/object/object_remesh.cc +++ b/source/blender/editors/object/object_remesh.cc @@ -134,12 +134,6 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) return OPERATOR_CANCELLED; } - /* Output mesh will be all smooth or all flat shading. */ - const bke::AttributeAccessor attributes = mesh->attributes(); - const VArray sharp_faces = *attributes.lookup_or_default( - "sharp_face", ATTR_DOMAIN_FACE, false); - const bool smooth_normals = !sharp_faces[0]; - float isovalue = 0.0f; if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) { isovalue = mesh->remesh_voxel_size * 0.3f; @@ -167,22 +161,12 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op) BKE_shrinkwrap_remesh_target_project(new_mesh, mesh, ob); } - if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) { - BKE_mesh_remesh_reproject_paint_mask(new_mesh, mesh); - } - - if (mesh->flag & ME_REMESH_REPROJECT_SCULPT_FACE_SETS) { - BKE_remesh_reproject_sculpt_face_sets(new_mesh, mesh); - } - - if (mesh->flag & ME_REMESH_REPROJECT_VERTEX_COLORS) { - BKE_remesh_reproject_vertex_paint(new_mesh, mesh); + if (mesh->flag & ME_REMESH_REPROJECT_ATTRIBUTES) { + bke::mesh_remesh_reproject_attributes(*mesh, *new_mesh); } BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob); - BKE_mesh_smooth_flag_set(static_cast(ob->data), smooth_normals); - if (ob->mode == OB_MODE_SCULPT) { ED_sculpt_undo_geometry_end(ob); } @@ -670,7 +654,7 @@ struct QuadriFlowJob { bool use_preserve_boundary; bool use_mesh_curvature; - bool preserve_paint_mask; + bool preserve_attributes; bool smooth_normals; int success; @@ -902,8 +886,8 @@ static void quadriflow_start_job(void *customdata, wmJobWorkerStatus *worker_sta ED_sculpt_undo_geometry_begin(ob, qj->op); } - if (qj->preserve_paint_mask) { - BKE_mesh_remesh_reproject_paint_mask(new_mesh, mesh); + if (qj->preserve_attributes) { + blender::bke::mesh_remesh_reproject_attributes(*mesh, *new_mesh); } BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob); @@ -969,7 +953,7 @@ static int quadriflow_remesh_exec(bContext *C, wmOperator *op) job->use_mesh_curvature = RNA_boolean_get(op->ptr, "use_mesh_curvature"); #endif - job->preserve_paint_mask = RNA_boolean_get(op->ptr, "preserve_paint_mask"); + job->preserve_attributes = RNA_boolean_get(op->ptr, "preserve_attributes"); job->smooth_normals = RNA_boolean_get(op->ptr, "smooth_normals"); /* Update the target face count if symmetry is enabled */ @@ -1149,10 +1133,10 @@ void OBJECT_OT_quadriflow_remesh(wmOperatorType *ot) "Take the mesh curvature into account when remeshing"); #endif RNA_def_boolean(ot->srna, - "preserve_paint_mask", + "preserve_attributes", false, - "Preserve Paint Mask", - "Reproject the paint mask onto the new mesh"); + "Preserve Attributes", + "Reproject attributes onto the new mesh"); RNA_def_boolean(ot->srna, "smooth_normals", diff --git a/source/blender/makesdna/DNA_mesh_defaults.h b/source/blender/makesdna/DNA_mesh_defaults.h index 15bf7cb4a83..c7e17c70b60 100644 --- a/source/blender/makesdna/DNA_mesh_defaults.h +++ b/source/blender/makesdna/DNA_mesh_defaults.h @@ -23,7 +23,7 @@ .remesh_voxel_adaptivity = 0.0f, \ .face_sets_color_seed = 0, \ .face_sets_color_default = 1, \ - .flag = ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_PAINT_MASK | ME_REMESH_REPROJECT_SCULPT_FACE_SETS | ME_REMESH_REPROJECT_VERTEX_COLORS, \ + .flag = ME_REMESH_REPROJECT_VOLUME | ME_REMESH_REPROJECT_ATTRIBUTES, \ .editflag = ME_EDIT_MIRROR_VERTEX_GROUPS \ } diff --git a/source/blender/makesdna/DNA_mesh_types.h b/source/blender/makesdna/DNA_mesh_types.h index 9cf25e6af9b..f57d89a6936 100644 --- a/source/blender/makesdna/DNA_mesh_types.h +++ b/source/blender/makesdna/DNA_mesh_types.h @@ -452,7 +452,7 @@ enum { ME_AUTOSMOOTH_LEGACY = 1 << 5, /* deprecated */ ME_FLAG_UNUSED_6 = 1 << 6, /* cleared */ ME_FLAG_UNUSED_7 = 1 << 7, /* cleared */ - ME_REMESH_REPROJECT_VERTEX_COLORS = 1 << 8, + ME_REMESH_REPROJECT_ATTRIBUTES = 1 << 8, ME_DS_EXPAND = 1 << 9, ME_SCULPT_DYNAMIC_TOPOLOGY = 1 << 10, /** @@ -461,10 +461,10 @@ enum { * to improve performance and it only takes one bit, it is stored in the mesh instead. */ ME_NO_OVERLAPPING_TOPOLOGY = 1 << 11, - ME_REMESH_REPROJECT_PAINT_MASK = 1 << 12, + ME_FLAG_UNUSED_8 = 1 << 12, /* deprecated */ ME_REMESH_FIX_POLES = 1 << 13, ME_REMESH_REPROJECT_VOLUME = 1 << 14, - ME_REMESH_REPROJECT_SCULPT_FACE_SETS = 1 << 15, + ME_FLAG_UNUSED_9 = 1 << 15, /* deprecated */ }; #ifdef DNA_DEPRECATED_ALLOW diff --git a/source/blender/makesrna/intern/rna_mesh.cc b/source/blender/makesrna/intern/rna_mesh.cc index 20c8f49c4ae..87cc4d1765c 100644 --- a/source/blender/makesrna/intern/rna_mesh.cc +++ b/source/blender/makesrna/intern/rna_mesh.cc @@ -3148,7 +3148,7 @@ static void rna_def_mesh(BlenderRNA *brna) prop = RNA_def_property(srna, "use_remesh_fix_poles", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, nullptr, "flag", ME_REMESH_FIX_POLES); - RNA_def_property_ui_text(prop, "Fix Poles", "Produces less poles and a better topology flow"); + RNA_def_property_ui_text(prop, "Fix Poles", "Produces fewer poles and a better topology flow"); RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); @@ -3161,23 +3161,9 @@ static void rna_def_mesh(BlenderRNA *brna) RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); - prop = RNA_def_property(srna, "use_remesh_preserve_paint_mask", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", ME_REMESH_REPROJECT_PAINT_MASK); - RNA_def_property_ui_text(prop, "Preserve Paint Mask", "Keep the current mask on the new mesh"); - RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); - RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); - - prop = RNA_def_property(srna, "use_remesh_preserve_sculpt_face_sets", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", ME_REMESH_REPROJECT_SCULPT_FACE_SETS); - RNA_def_property_ui_text( - prop, "Preserve Face Sets", "Keep the current Face Sets on the new mesh"); - RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); - RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE); - - prop = RNA_def_property(srna, "use_remesh_preserve_vertex_colors", PROP_BOOLEAN, PROP_NONE); - RNA_def_property_boolean_sdna(prop, nullptr, "flag", ME_REMESH_REPROJECT_VERTEX_COLORS); - RNA_def_property_ui_text( - prop, "Preserve Vertex Colors", "Keep the current vertex colors on the new mesh"); + prop = RNA_def_property(srna, "use_remesh_preserve_attributes", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, nullptr, "flag", ME_REMESH_REPROJECT_ATTRIBUTES); + RNA_def_property_ui_text(prop, "Preserve Attributes", "Transfer all attributes to the new mesh"); RNA_def_property_update(prop, 0, "rna_Mesh_update_draw"); RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);