Geometry Node: Edge Rings node #104623

Open
Iliya Katushenock wants to merge 14 commits from mod_moder/blender:edge_rings into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
6 changed files with 349 additions and 0 deletions

View File

@ -351,6 +351,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")

View File

@ -235,6 +235,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<int2> edges,
const Span<int> corner_edges,
const Span<int> 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.

View File

@ -1312,6 +1312,7 @@ void BKE_nodetree_remove_layer_n(struct bNodeTree *ntree, struct Scene *scene, i
#define GEO_NODE_TOOL_FACE_SET 2112
#define GEO_NODE_TOOL_SET_FACE_SET 2113
#define GEO_NODE_POINTS_TO_CURVES 2114
#define GEO_NODE_INPUT_MESH_EDGE_RINGS 2115
/** \} */

View File

@ -346,6 +346,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")

View File

@ -91,6 +91,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

View File

@ -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<decl::Int>("Parallel Group Index")
.field_source()
.description("Edge group index of the edge ring from parallel edges");
b.add_output<decl::Int>("Linear Group Index").field_source();
}
static void make_ordered(const int2 prev, int2 &current)
{
if (ELEM(current[1], prev[0], prev[1])) {
std::swap(current[0], current[1]);
}
}
template<typename T, typename Func> static T &find_if(MutableSpan<T> span, const Func &&func)
{
return *std::find_if(span.begin(), span.end(), func);
}
template<typename T, typename Func> static const T &find_if(const Span<T> span, const Func &&func)
{
return *std::find_if(span.begin(), span.end(), func);
}
static Array<int2> corner_edge_pairs(const OffsetIndices<int> faces,
const Span<int2> edges,
const Span<int> corner_edges,
const Span<int> corner_verts,
const int totloop,
const int faces_num)
{
Array<int2> 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<int> face_corner_edges = corner_edges.slice(face);
const Span<int> 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<int> offset,
MutableSpan<int2> r_pairs,
MutableSpan<int> r_indices)
{
mask.foreach_index(GrainSize(1024), [&](const int vert_index) {
if (offset[vert_index].is_empty()) {
return;
}
MutableSpan<int2> vertex_stars = r_pairs.slice(offset[vert_index]);
MutableSpan<int> 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 &current = vertex_stars[index];
MutableSpan<int2> 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<int>(), "Edges Line Rings Field")
{
}
GVArray get_varray_for_context(const Mesh &mesh,
const eAttrDomain domain,
const IndexMask & /*mask*/) const final
{
const Span<int2> edges = mesh.edges();
const Span<int> corner_edges = mesh.corner_edges();
const Span<int> corner_verts = mesh.corner_verts();
Array<int> vert_to_edge_offsets;
Array<int> vert_to_edge_indices;
const GroupedSpan<int> vert_to_edge_map = bke::mesh::build_vert_to_edge_map(
edges, mesh.totvert, vert_to_edge_offsets, vert_to_edge_indices);
Array<int> vert_to_loop_offsets;
Array<int> vert_to_loop_indices;
bke::mesh::build_vert_to_loop_map(
corner_verts, mesh.totvert, vert_to_loop_offsets, vert_to_loop_indices);
const OffsetIndices<int> vert_to_loop_offset(vert_to_loop_offsets);
Array<int> edge_faces_total(mesh.totedge, 0);
array_utils::count_indices(corner_edges, edge_faces_total.as_mutable_span());
IndexMaskMemory memory;
const IndexMask vert_mask = IndexMask::from_predicate(
IndexMask(mesh.totvert), 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<int2> stars_pairs(mesh.totloop);
const Array<int2> corner_pairs = corner_edge_pairs(
mesh.faces(), edges, corner_edges, corner_verts, mesh.totloop, mesh.faces_num);
array_utils::gather(
corner_pairs.as_span(), vert_to_loop_indices.as_span(), stars_pairs.as_mutable_span());
Array<int> star_indices(mesh.totloop);
sort_corner_edge_pairs(vert_mask, vert_to_loop_offset, stars_pairs, star_indices);
AtomicDisjointSet linear_edges(mesh.totedge);
vert_mask.foreach_index(GrainSize(1024), [&](const int vert_index) {
for (IndexRange range = vert_to_loop_offset[vert_index]; !range.is_empty();) {
const Span<int> 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<int2> 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<int2> north_corners = current_edge_star.take_front(semicircle_size);
const Span<int2> 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.totvert), 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<int> 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<int> edge_group(mesh.totedge);
linear_edges.calc_reduced_ids(edge_group);
return mesh.attributes().adapt_domain<int>(
VArray<int>::ForContainer(std::move(edge_group)), ATTR_DOMAIN_EDGE, domain);
}
uint64_t hash() const final
{
return 736758993181174;
}
bool is_equal_to(const fn::FieldNode &other) const final
{
return dynamic_cast<const EdgesLineGroupFieldInput *>(&other) != nullptr;
}
std::optional<eAttrDomain> preferred_domain(const Mesh & /*mesh*/) const final
{
return ATTR_DOMAIN_EDGE;
}
};
class ParallelEdgeGroupFieldInput final : public bke::MeshFieldInput {
public:
ParallelEdgeGroupFieldInput()
: bke::MeshFieldInput(CPPType::get<int>(), "Parallel Edge Rings Field")
{
}
GVArray get_varray_for_context(const Mesh &mesh,
const eAttrDomain domain,
const IndexMask & /*mask*/) const final
{
AtomicDisjointSet parallel_edges(mesh.totedge);
const Span<int> corner_edges = mesh.corner_edges();
const blender::OffsetIndices<int> 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<int> north_corners = corner_edges.slice(poly_range).take_front(semicircle_size);
const Span<int> 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<int> edge_group(mesh.totedge);
parallel_edges.calc_reduced_ids(edge_group);
return mesh.attributes().adapt_domain<int>(
VArray<int>::ForContainer(std::move(edge_group)), ATTR_DOMAIN_EDGE, domain);
}
uint64_t hash() const final
{
return 736758776181174;
}
bool is_equal_to(const fn::FieldNode &other) const final
{
return dynamic_cast<const ParallelEdgeGroupFieldInput *>(&other) != nullptr;
}
std::optional<eAttrDomain> preferred_domain(const Mesh & /*mesh*/) const final
{
return ATTR_DOMAIN_EDGE;
}
};
static void node_geo_exec(GeoNodeExecParams params)
{
Field<int> parallel_edges{std::make_shared<ParallelEdgeGroupFieldInput>()};
params.set_output("Parallel Group Index", std::move(parallel_edges));
Field<int> linear_edges{std::make_shared<EdgesLineGroupFieldInput>()};
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