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
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<float3> positions,
|
||||
const Span<int2> edges,
|
||||
MutableSpan<float3> edge_centers)
|
||||
{
|
||||
BVHTreeFromMesh bvhtree = {nullptr};
|
||||
BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
|
||||
const Span<float3> 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<float3> positions,
|
||||
const OffsetIndices<int> faces,
|
||||
const Span<int> corner_verts,
|
||||
MutableSpan<float3> 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<float3> positions,
|
||||
BVHTreeFromMesh &bvhtree,
|
||||
MutableSpan<int> 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<float3> positions,
|
||||
BVHTreeFromMesh &bvhtree,
|
||||
MutableSpan<int> 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<float3> positions,
|
||||
const Span<int> corner_verts,
|
||||
const Span<MLoopTri> src_tris,
|
||||
const Span<float3> dst_positions,
|
||||
const Span<int> nearest_vert_tris,
|
||||
MutableSpan<int> 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<float, 3> 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<int> src_tri_faces,
|
||||
const Span<float3> dst_positions,
|
||||
const OffsetIndices<int> dst_faces,
|
||||
const Span<int> dst_corner_verts,
|
||||
BVHTreeFromMesh &bvhtree,
|
||||
MutableSpan<int> nearest_faces)
|
||||
{
|
||||
using namespace blender;
|
||||
using namespace blender::bke;
|
||||
const AttributeAccessor src_attributes = source->attributes();
|
||||
MutableAttributeAccessor dst_attributes = target->attributes_for_write();
|
||||
const Span<float3> target_positions = target->vert_positions();
|
||||
const OffsetIndices target_faces = target->faces();
|
||||
const Span<int> target_corner_verts = target->corner_verts();
|
||||
struct TLS {
|
||||
Vector<float3> face_centers;
|
||||
Vector<int> tri_indices;
|
||||
};
|
||||
threading::EnumerableThreadSpecific<TLS> all_tls;
|
||||
threading::parallel_for(dst_faces.index_range(), 512, [&](const IndexRange range) {
|
||||
TLS &tls = all_tls.local();
|
||||
Vector<float3> &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<int>(".sculpt_face_set", ATTR_DOMAIN_FACE);
|
||||
if (!src_face_sets) {
|
||||
return;
|
||||
}
|
||||
SpanAttributeWriter<int> dst_face_sets = dst_attributes.lookup_or_add_for_write_only_span<int>(
|
||||
".sculpt_face_set", ATTR_DOMAIN_FACE);
|
||||
if (!dst_face_sets) {
|
||||
return;
|
||||
}
|
||||
Vector<int> &tri_indices = tls.tri_indices;
|
||||
tri_indices.reinitialize(range.size());
|
||||
find_nearest_tris(face_centers, bvhtree, tri_indices);
|
||||
|
||||
const VArraySpan<int> src(src_face_sets);
|
||||
MutableSpan<int> dst = dst_face_sets.span;
|
||||
|
||||
const blender::Span<int> 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<float3> src_positions,
|
||||
const OffsetIndices<int> src_faces,
|
||||
const Span<int> src_corner_verts,
|
||||
const Span<int> src_tri_faces,
|
||||
const Span<float3> dst_positions,
|
||||
const Span<int> dst_corner_verts,
|
||||
const Span<int> nearest_vert_tris,
|
||||
MutableSpan<int> 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<float, 64> 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<int> 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<float3> src_positions,
|
||||
const Span<int2> src_edges,
|
||||
const OffsetIndices<int> src_faces,
|
||||
const Span<int> src_corner_edges,
|
||||
const Span<int> src_tri_faces,
|
||||
const Span<float3> dst_positions,
|
||||
const Span<int2> dst_edges,
|
||||
BVHTreeFromMesh &bvhtree,
|
||||
MutableSpan<int> nearest_edges)
|
||||
{
|
||||
struct TLS {
|
||||
Vector<float3> edge_centers;
|
||||
Vector<int> tri_indices;
|
||||
Vector<int> face_indices;
|
||||
Vector<float> distances;
|
||||
};
|
||||
threading::EnumerableThreadSpecific<TLS> all_tls;
|
||||
threading::parallel_for(nearest_edges.index_range(), 512, [&](const IndexRange range) {
|
||||
TLS &tls = all_tls.local();
|
||||
Vector<float3> &edge_centers = tls.edge_centers;
|
||||
edge_centers.reinitialize(range.size());
|
||||
calc_edge_centers(dst_positions, dst_edges.slice(range), edge_centers);
|
||||
|
||||
Vector<int> &tri_indices = tls.tri_indices;
|
||||
tri_indices.reinitialize(range.size());
|
||||
find_nearest_tris_parallel(edge_centers, bvhtree, tri_indices);
|
||||
|
||||
Vector<int> &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<float, 64> 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<int> 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<AttributeIDRef> ids,
|
||||
const AttributeAccessor src_attributes,
|
||||
const eAttrDomain domain,
|
||||
const Span<int> 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<AttributeIDRef> point_ids;
|
||||
Vector<AttributeIDRef> edge_ids;
|
||||
Vector<AttributeIDRef> face_ids;
|
||||
Vector<AttributeIDRef> 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<int> source_lmap;
|
||||
GroupedSpan<int> 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<float3> src_positions = src.vert_positions();
|
||||
const OffsetIndices src_faces = src.faces();
|
||||
const Span<int> src_corner_verts = src.corner_verts();
|
||||
const Span<MLoopTri> src_tris = src.looptris();
|
||||
|
||||
const Span<float3> target_positions = target->vert_positions();
|
||||
Array<int> 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<float3> dst_positions = dst.vert_positions();
|
||||
const OffsetIndices dst_faces = dst.faces();
|
||||
const Span<int> dst_corner_verts = dst.corner_verts();
|
||||
|
||||
MutableAttributeAccessor dst_attributes = dst.attributes_for_write();
|
||||
|
||||
if (!point_ids.is_empty() || !corner_ids.is_empty()) {
|
||||
Array<int> vert_nearest_tris(dst_positions.size());
|
||||
find_nearest_tris_parallel(dst_positions, bvhtree, vert_nearest_tris);
|
||||
|
||||
if (!point_ids.is_empty()) {
|
||||
Array<int> 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<ColorGeometry4b, ColorGeometry4f>([&](auto type_tag) {
|
||||
using T = typename decltype(type_tag)::type;
|
||||
if constexpr (std::is_void_v<T>) {
|
||||
BLI_assert_unreachable();
|
||||
}
|
||||
else {
|
||||
const Span<T> src_typed = src.typed<T>();
|
||||
MutableSpan<T> dst_typed = dst.span.typed<T>();
|
||||
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<T> 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<int> src_tri_faces = src.looptri_faces();
|
||||
Array<int> 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<int2> src_edges = src.edges();
|
||||
const Span<int> src_corner_edges = src.corner_edges();
|
||||
const Span<int> src_tri_faces = src.looptri_faces();
|
||||
const Span<int2> dst_edges = dst.edges();
|
||||
Array<int> 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<int> src_tri_faces = src.looptri_faces();
|
||||
Array<int> 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);
|
||||
|
||||
@@ -86,7 +86,7 @@ template<typename T> 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<T> data() const
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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<bool> sharp_faces = *attributes.lookup_or_default<bool>(
|
||||
"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<Mesh *>(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",
|
||||
|
||||
@@ -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 \
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user