diff --git a/source/blender/geometry/CMakeLists.txt b/source/blender/geometry/CMakeLists.txt index cd4ef46d717..0239c095358 100644 --- a/source/blender/geometry/CMakeLists.txt +++ b/source/blender/geometry/CMakeLists.txt @@ -18,6 +18,7 @@ set(SRC intern/add_curves_on_mesh.cc intern/curve_constraints.cc intern/fillet_curves.cc + intern/mesh_flip_faces.cc intern/mesh_merge_by_distance.cc intern/mesh_primitive_cuboid.cc intern/mesh_split_edges.cc @@ -37,6 +38,7 @@ set(SRC GEO_add_curves_on_mesh.hh GEO_curve_constraints.hh GEO_fillet_curves.hh + GEO_mesh_flip_faces.hh GEO_mesh_merge_by_distance.hh GEO_mesh_primitive_cuboid.hh GEO_mesh_split_edges.hh diff --git a/source/blender/geometry/GEO_mesh_flip_faces.hh b/source/blender/geometry/GEO_mesh_flip_faces.hh new file mode 100644 index 00000000000..0ab8f38898a --- /dev/null +++ b/source/blender/geometry/GEO_mesh_flip_faces.hh @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +#include "BLI_index_mask.hh" + +struct Mesh; + +namespace blender::geometry { + +/** + * Reverse the order of the vertices in the selected faces. This effectively changes the + * direction of the face normal. + */ +void flip_faces(Mesh &mesh, const IndexMask &selection); + +} // namespace blender::geometry diff --git a/source/blender/geometry/intern/mesh_flip_faces.cc b/source/blender/geometry/intern/mesh_flip_faces.cc new file mode 100644 index 00000000000..61305534307 --- /dev/null +++ b/source/blender/geometry/intern/mesh_flip_faces.cc @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "GEO_mesh_flip_faces.hh" + +#include "DNA_mesh_types.h" + +#include "BLI_task.hh" + +#include "BKE_attribute.hh" +#include "BKE_attribute_math.hh" +#include "BKE_mesh.hh" + +namespace blender::geometry { + +void flip_faces(Mesh &mesh, const IndexMask &selection) +{ + if (mesh.totpoly == 0 || selection.is_empty()) { + return; + } + + const Span polys = mesh.polys(); + MutableSpan corner_verts = mesh.corner_verts_for_write(); + MutableSpan corner_edges = mesh.corner_edges_for_write(); + + threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange poly(polys[i].loopstart, polys[i].totloop); + for (const int j : IndexRange(poly.size() / 2)) { + const int a = poly[j + 1]; + const int b = poly.last(j); + std::swap(corner_verts[a], corner_verts[b]); + std::swap(corner_edges[a - 1], corner_edges[b]); + } + } + }); + + bke::MutableAttributeAccessor attributes = mesh.attributes_for_write(); + attributes.for_all( + [&](const bke::AttributeIDRef &attribute_id, const bke::AttributeMetaData &meta_data) { + if (meta_data.data_type == CD_PROP_STRING) { + return true; + } + if (meta_data.domain != ATTR_DOMAIN_CORNER) { + return true; + } + if (ELEM(attribute_id.name(), ".corner_vert", ".corner_edge")) { + return true; + } + bke::GSpanAttributeWriter attribute = attributes.lookup_for_write_span(attribute_id); + attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { + using T = decltype(dummy); + MutableSpan dst_span = attribute.span.typed(); + threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) { + for (const int i : selection.slice(range)) { + const IndexRange poly(polys[i].loopstart, polys[i].totloop); + dst_span.slice(poly.drop_front(1)).reverse(); + } + }); + }); + attribute.finish(); + return true; + }); + + BKE_mesh_tag_topology_changed(&mesh); +} + +} // namespace blender::geometry diff --git a/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc b/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc index 0345868e963..7eefd871d00 100644 --- a/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc +++ b/source/blender/nodes/geometry/nodes/node_geo_flip_faces.cc @@ -7,6 +7,8 @@ #include "BKE_attribute_math.hh" +#include "GEO_mesh_flip_faces.hh" + #include "node_geometry_util.hh" namespace blender::nodes::node_geo_flip_faces_cc { @@ -29,48 +31,7 @@ static void mesh_flip_faces(Mesh &mesh, const Field &selection_field) evaluator.evaluate(); const IndexMask selection = evaluator.get_evaluated_as_mask(0); - const Span polys = mesh.polys(); - MutableSpan corner_verts = mesh.corner_verts_for_write(); - MutableSpan corner_edges = mesh.corner_edges_for_write(); - - threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) { - for (const int i : selection.slice(range)) { - const IndexRange poly(polys[i].loopstart, polys[i].totloop); - for (const int j : IndexRange(poly.size() / 2)) { - const int a = poly[j + 1]; - const int b = poly.last(j); - std::swap(corner_verts[a], corner_verts[b]); - std::swap(corner_edges[a - 1], corner_edges[b]); - } - } - }); - - MutableAttributeAccessor attributes = mesh.attributes_for_write(); - attributes.for_all( - [&](const bke::AttributeIDRef &attribute_id, const AttributeMetaData &meta_data) { - if (meta_data.data_type == CD_PROP_STRING) { - return true; - } - if (meta_data.domain != ATTR_DOMAIN_CORNER) { - return true; - } - if (ELEM(attribute_id.name(), ".corner_vert", ".corner_edge")) { - return true; - } - GSpanAttributeWriter attribute = attributes.lookup_for_write_span(attribute_id); - attribute_math::convert_to_static_type(meta_data.data_type, [&](auto dummy) { - using T = decltype(dummy); - MutableSpan dst_span = attribute.span.typed(); - threading::parallel_for(selection.index_range(), 1024, [&](const IndexRange range) { - for (const int i : selection.slice(range)) { - const IndexRange poly(polys[i].loopstart, polys[i].totloop); - dst_span.slice(poly.drop_front(1)).reverse(); - } - }); - }); - attribute.finish(); - return true; - }); + geometry::flip_faces(mesh, selection); } static void node_geo_exec(GeoNodeExecParams params)