diff --git a/scripts/startup/bl_ui/node_add_menu_geometry.py b/scripts/startup/bl_ui/node_add_menu_geometry.py index bd8f4229dcb..4348f44eefb 100644 --- a/scripts/startup/bl_ui/node_add_menu_geometry.py +++ b/scripts/startup/bl_ui/node_add_menu_geometry.py @@ -363,6 +363,7 @@ class NODE_MT_geometry_node_GEO_MESH_READ(Menu): layout = self.layout node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeAngle") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeNeighbors") + node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeRings") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshEdgeVertices") node_add_menu.add_node_type(layout, "GeometryNodeEdgesToFaceGroups") node_add_menu.add_node_type(layout, "GeometryNodeInputMeshFaceArea") diff --git a/source/blender/blenkernel/BKE_mesh.hh b/source/blender/blenkernel/BKE_mesh.hh index afd45ba31e2..97cacaef74d 100644 --- a/source/blender/blenkernel/BKE_mesh.hh +++ b/source/blender/blenkernel/BKE_mesh.hh @@ -257,6 +257,33 @@ inline int face_corner_next(const IndexRange face, const int corner) return corner + 1; } +inline const int2 &corner_vert_edges(const int2 &prev_edge, + const int2 &next_edge, + const int vertex_index) +{ + BLI_assert(ELEM(vertex_index, prev_edge[0], prev_edge[1]) != + ELEM(vertex_index, next_edge[0], next_edge[1])); + if (ELEM(vertex_index, prev_edge[0], prev_edge[1])) { + return prev_edge; + } + return next_edge; +} + +inline int other_corner_edge(const Span edges, + const Span corner_edges, + const Span corner_vertx, + const int corner_i) +{ + BLI_assert(corner_edges.size() == corner_vertx.size()); + const IndexRange face = corner_edges.index_range(); + const int vertex = corner_vertx[corner_i]; + const int prev_corner = face_corner_prev(face, corner_i); + const int next_corner = face_corner_next(face, corner_i); + const int2 &edge = corner_vert_edges( + edges[corner_edges[prev_corner]], edges[corner_edges[next_corner]], vertex); + return int(&edge - edges.begin()); +} + /** * Find the index of the corner in the face that uses the given vertex. * The index is into the entire corners array, not just the face's corners. diff --git a/source/blender/blenkernel/BKE_node.hh b/source/blender/blenkernel/BKE_node.hh index 609ef7a7093..868cf51a30e 100644 --- a/source/blender/blenkernel/BKE_node.hh +++ b/source/blender/blenkernel/BKE_node.hh @@ -1299,6 +1299,7 @@ void BKE_nodetree_remove_layer_n(bNodeTree *ntree, Scene *scene, int layer_index #define GEO_NODE_TOOL_VIEWPORT_TRANSFORM 2132 #define GEO_NODE_TOOL_MOUSE_POSITION 2133 #define GEO_NODE_SAMPLE_GRID_INDEX 2134 +#define GEO_NODE_INPUT_MESH_EDGE_RINGS 2135 /** \} */ diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 2d8ce559d8a..3968fd38432 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -367,6 +367,7 @@ DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL_INDEX, 0, "INPUT_MATERIAL_INDEX", DefNode(GeometryNode, GEO_NODE_INPUT_MATERIAL, def_geo_input_material, "INPUT_MATERIAL", InputMaterial, "Material", "Output a single material") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_ANGLE, 0, "MESH_EDGE_ANGLE", InputMeshEdgeAngle, "Edge Angle", "Calculate the surface area of each face in a mesh") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_NEIGHBORS, 0, "MESH_EDGE_NEIGHBORS",InputMeshEdgeNeighbors, "Edge Neighbors", "Retrieve the number of faces that use each edge as one of their sides") +DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_RINGS, 0, "INPUT_MESH_EDGE_RINGS", InputMeshEdgeRings, "Edge Rings", "") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_EDGE_VERTICES, 0, "MESH_EDGE_VERTICES", InputMeshEdgeVertices, "Edge Vertices", "Retrieve topology information relating to each edge of a mesh") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_AREA, 0, "MESH_FACE_AREA", InputMeshFaceArea, "Face Area", "Calculate the surface area of a mesh's faces") DefNode(GeometryNode, GEO_NODE_INPUT_MESH_FACE_IS_PLANAR, 0, "MESH_FACE_IS_PLANAR", InputMeshFaceIsPlanar, "Is Face Planar", "Retrieve whether all triangles in a face are on the same plane, i.e. whether they have the same normal") diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index d979622c095..7d4d1c40bed 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -100,6 +100,7 @@ set(SRC nodes/node_geo_input_material_index.cc nodes/node_geo_input_mesh_edge_angle.cc nodes/node_geo_input_mesh_edge_neighbors.cc + nodes/node_geo_input_mesh_edge_rings.cc nodes/node_geo_input_mesh_edge_vertices.cc nodes/node_geo_input_mesh_face_area.cc nodes/node_geo_input_mesh_face_is_planar.cc diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_rings.cc b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_rings.cc new file mode 100644 index 00000000000..07031a48d88 --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_mesh_edge_rings.cc @@ -0,0 +1,318 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" + +#include "BKE_mesh_mapping.hh" + +#include "BLI_array_utils.hh" +#include "BLI_atomic_disjoint_set.hh" +#include "BLI_vector_set.hh" + +#include "node_geometry_util.hh" + +namespace blender::nodes::node_geo_input_mesh_edge_rings_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_output("Parallel Group Index") + .field_source() + .description("Edge group index of the edge ring from parallel edges"); + b.add_output("Linear Group Index").field_source(); +} + +static void make_ordered(const int2 prev, int2 ¤t) +{ + if (ELEM(current[1], prev[0], prev[1])) { + std::swap(current[0], current[1]); + } +} + +template static T &find_if(MutableSpan span, const Func &&func) +{ + return *std::find_if(span.begin(), span.end(), func); +} + +template static const T &find_if(const Span span, const Func &&func) +{ + return *std::find_if(span.begin(), span.end(), func); +} + +static Array corner_edge_pairs(const OffsetIndices faces, + const Span edges, + const Span corner_edges, + const Span corner_verts, + const int totloop, + const int faces_num) +{ + Array pairs(totloop); + threading::parallel_for(IndexRange(faces_num), 1024, [&](const IndexRange range) { + for (const int face_index : range) { + const IndexRange face = faces[face_index]; + const Span face_corner_edges = corner_edges.slice(face); + const Span face_corner_verts = corner_verts.slice(face); + for (const int corner_i : face.index_range()) { + const int corner_index = face[corner_i]; + pairs[corner_index][0] = face_corner_edges[corner_i]; + pairs[corner_index][1] = bke::mesh::other_corner_edge( + edges, face_corner_edges, face_corner_verts, corner_i); + } + } + }); + return pairs; +} + +static void sort_corner_edge_pairs(const IndexMask mask, + const OffsetIndices offset, + MutableSpan r_pairs, + MutableSpan r_indices) +{ + mask.foreach_index(GrainSize(1024), [&](const int vert_index) { + if (offset[vert_index].is_empty()) { + return; + } + MutableSpan vertex_stars = r_pairs.slice(offset[vert_index]); + MutableSpan vertex_star_index = r_indices.slice(offset[vert_index]); + vertex_star_index.first() = 0; + for (const int index : vertex_stars.index_range().drop_back(1)) { + int2 ¤t = vertex_stars[index]; + MutableSpan next_range = vertex_stars.drop_front(index + 1); + vertex_star_index[index + 1] = vertex_star_index[index]; + int2 &next = find_if(next_range, + [&](const int2 next) { return ELEM(current[1], next[0], next[1]); }); + if (&next != next_range.end()) { + make_ordered(current, next); + std::swap(next_range.first(), next); + continue; + } + /* This is the first element in a non-cyclic star and looking for unconnected edge was + * failure. Try again. */ + int2 &other_next = find_if( + next_range, [&](const int2 next) { return ELEM(current[0], next[0], next[1]); }); + if (&other_next != next_range.end()) { + std::swap(current[0], current[1]); + make_ordered(current, other_next); + std::swap(next_range.first(), other_next); + continue; + } + vertex_star_index[index + 1]++; + } + }); +} + +class EdgesLineGroupFieldInput final : public bke::MeshFieldInput { + public: + EdgesLineGroupFieldInput() : bke::MeshFieldInput(CPPType::get(), "Edges Line Rings Field") + { + } + + GVArray get_varray_for_context(const Mesh &mesh, + const AttrDomain domain, + const IndexMask & /*mask*/) const final + { + const Span edges = mesh.edges(); + const Span corner_edges = mesh.corner_edges(); + const Span corner_verts = mesh.corner_verts(); + + Array vert_to_edge_offsets; + Array vert_to_edge_indices; + const GroupedSpan vert_to_edge_map = bke::mesh::build_vert_to_edge_map( + edges, mesh.verts_num, vert_to_edge_offsets, vert_to_edge_indices); + + Array vert_to_loop_offsets; + Array vert_to_loop_indices; + bke::mesh::build_vert_to_corner_map( + corner_verts, mesh.verts_num, vert_to_loop_offsets, vert_to_loop_indices); + const OffsetIndices vert_to_loop_offset(vert_to_loop_offsets); + + Array edge_faces_total(mesh.edges_num, 0); + array_utils::count_indices(corner_edges, edge_faces_total.as_mutable_span()); + + IndexMaskMemory memory; + const IndexMask vert_mask = IndexMask::from_predicate( + IndexMask(mesh.verts_num), GrainSize(1024), memory, [&](const int vert_index) { + for (const int edge_index : vert_to_edge_map[vert_index]) { + if (!ELEM(edge_faces_total[edge_index], 0, 1, 2)) { + return false; + } + } + return true; + }); + + Array stars_pairs(mesh.corners_num); + const Array corner_pairs = corner_edge_pairs( + mesh.faces(), edges, corner_edges, corner_verts, mesh.corners_num, mesh.faces_num); + array_utils::gather( + corner_pairs.as_span(), vert_to_loop_indices.as_span(), stars_pairs.as_mutable_span()); + + Array star_indices(mesh.corners_num); + sort_corner_edge_pairs(vert_mask, vert_to_loop_offset, stars_pairs, star_indices); + + AtomicDisjointSet linear_edges(mesh.edges_num); + + vert_mask.foreach_index(GrainSize(1024), [&](const int vert_index) { + for (IndexRange range = vert_to_loop_offset[vert_index]; !range.is_empty();) { + const Span vertex_star_index = star_indices.as_span().slice(range); + const int &first = vertex_star_index.first(); + const int &end = find_if(vertex_star_index, + [&](const int other) { return other != first; }); + const int size = int(&end - &first); + const Span current_edge_star = stars_pairs.as_span().slice(range.take_front(size)); + range = range.drop_front(size); + const bool is_cyclic = current_edge_star.first()[0] == current_edge_star.last()[1]; + if (!is_cyclic) { + linear_edges.join(current_edge_star.first()[0], current_edge_star.last()[1]); + continue; + } + if (size % 2) { + continue; + } + const int semicircle_size = size / 2; + const Span north_corners = current_edge_star.take_front(semicircle_size); + const Span south_corners = current_edge_star.take_back(semicircle_size); + for (const int index : IndexRange(semicircle_size)) { + linear_edges.join(north_corners[index][0], south_corners[index][0]); + } + } + }); + + threading::parallel_for(IndexRange(mesh.verts_num), 2048, [&](const IndexRange range) { + for (const int vert_index : range) { + if (vert_to_edge_map[vert_index].size() != 2) { + continue; + } + if (edge_faces_total[vert_to_edge_map[vert_index][0]] != 0) { + continue; + } + if (edge_faces_total[vert_to_edge_map[vert_index][1]] != 0) { + continue; + } + linear_edges.join(vert_to_edge_map[vert_index][0], vert_to_edge_map[vert_index][1]); + } + }); + + vert_mask.foreach_index(GrainSize(1024), [&](const int vert_index) { + if (vert_to_edge_map[vert_index].size() != 4) { + return; + } + const IndexRange range = vert_to_loop_offset[vert_index]; + if (range.size() != 2) { + return; + } + const Span vertex_star_index = star_indices.as_span().slice(range); + const int &first = vertex_star_index.first(); + const int &end = find_if(vertex_star_index, [&](const int other) { return other != first; }); + const int size = int(&end - &first); + if (size != range.size()) { + return; + } + const int central_edge = stars_pairs.as_span().slice(range).first()[1]; + const int loos_edge = [&]() { + for (const int edge_index : vert_to_edge_map[vert_index]) { + if (edge_faces_total[edge_index] == 0) { + return edge_index; + } + } + BLI_assert_unreachable(); + return 0; + }(); + linear_edges.join(central_edge, loos_edge); + }); + + Array edge_group(mesh.edges_num); + linear_edges.calc_reduced_ids(edge_group); + return mesh.attributes().adapt_domain( + VArray::ForContainer(std::move(edge_group)), AttrDomain::Edge, domain); + } + + uint64_t hash() const final + { + return 736758993181174; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + return dynamic_cast(&other) != nullptr; + } + + std::optional preferred_domain(const Mesh & /*mesh*/) const final + { + return AttrDomain::Edge; + } +}; + +class ParallelEdgeGroupFieldInput final : public bke::MeshFieldInput { + public: + ParallelEdgeGroupFieldInput() + : bke::MeshFieldInput(CPPType::get(), "Parallel Edge Rings Field") + { + } + + GVArray get_varray_for_context(const Mesh &mesh, + const AttrDomain domain, + const IndexMask & /*mask*/) const final + { + AtomicDisjointSet parallel_edges(mesh.edges_num); + + const Span corner_edges = mesh.corner_edges(); + const blender::OffsetIndices faces = mesh.faces(); + threading::parallel_for(IndexRange(mesh.faces_num), 2048, [&](const IndexRange range) { + for (const int poly_index : range) { + const IndexRange poly_range = faces[poly_index]; + if (poly_range.size() % 2) { + continue; + } + /* Split corners of polygon to semicircle segments. */ + const int semicircle_size = poly_range.size() / 2; + const Span north_corners = corner_edges.slice(poly_range).take_front(semicircle_size); + const Span south_corners = corner_edges.slice(poly_range).take_back(semicircle_size); + for (const int index : IndexRange(semicircle_size)) { + const int north_edge = north_corners[index]; + const int south_edge = south_corners[index]; + parallel_edges.join(north_edge, south_edge); + } + } + }); + + Array edge_group(mesh.edges_num); + parallel_edges.calc_reduced_ids(edge_group); + + return mesh.attributes().adapt_domain( + VArray::ForContainer(std::move(edge_group)), AttrDomain::Edge, domain); + } + + uint64_t hash() const final + { + return 736758776181174; + } + + bool is_equal_to(const fn::FieldNode &other) const final + { + return dynamic_cast(&other) != nullptr; + } + + std::optional preferred_domain(const Mesh & /*mesh*/) const final + { + return AttrDomain::Edge; + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field parallel_edges{std::make_shared()}; + params.set_output("Parallel Group Index", std::move(parallel_edges)); + Field linear_edges{std::make_shared()}; + params.set_output("Linear Group Index", std::move(linear_edges)); +} + +static void node_register() +{ + static bNodeType ntype; + geo_node_type_base(&ntype, GEO_NODE_INPUT_MESH_EDGE_RINGS, "Edge Rings", NODE_CLASS_INPUT); + ntype.declare = node_declare; + ntype.geometry_node_execute = node_geo_exec; + nodeRegisterType(&ntype); +} +NOD_REGISTER_NODE(node_register) + +} // namespace blender::nodes::node_geo_input_mesh_edge_rings_cc