Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHans Goudey <h.goudey@me.com>2022-09-28 22:38:27 +0300
committerHans Goudey <h.goudey@me.com>2022-09-28 22:38:27 +0300
commit482d431bb6735e8206961bd1115d2be7e63572b1 (patch)
treef47f10616f1f943def2bff975a6a869534feccc1 /source/blender/nodes/geometry
parent25533dbe219f8bbcb3f04ffe2ff1e57599addf3f (diff)
Geometry Nodes: Curve and mesh topology access nodes
This patch contains an initial set of nodes to access basic mesh topology information, as explored in T100020. The nodes allow six direct topology mappings for meshes: - **Corner -> Face** The face a corner is in, the index in the face - **Vertex -> Edge** Choose an edge attached to the vertex - **Vertex -> Corner** Choose a corner attached to the vertex - **Corner -> Edge** The next and previous edge at each face corner - **Corner -> Vertex** The vertex associated with a corner - **Corner -> Corner** Offset a corner index within a face And two new topology mappings for curves: - **Curve -> Points** Choose a point within a curve - **Point -> Curve** The curve a point is in, the index in the curve The idea is that some of the 16 possible mesh mappings are more important, and that this is a useful set of nodes to start exploring this area. For mappings with an arbitrary number of connections, we must sort them and use an index to choose a single element, because geometry nodes does not support list fields. Note that the sort index has repeating behavior as it goes over the "Total" number of connections, and negative sort indices choose from the end. Currently which of the "start" elements is used is determined by the field context, so the "Field at Index" and "Interpolate Domain" nodes will be quite important. Also, currently the "Sort Index" inputs are clamped to the number of connections. One important feature that isn't implemented here is using the winding order for the output elements. This can be a separate mode for some of these nodes. It will be optional because of the performance impact. There are several todos for separate commits after this: - Rename "Control Point Neighbors" to be consistent with this naming - Version away the "Vertex Neighbors" node which is fully redundant now - Implement a special case for when no weights are used for performance - De-duplicating some of the sorting logic between the nodes - Improve performance and memory use of topology mappings - Look into caching some of the mappings on meshes Differential Revision: https://developer.blender.org/D16029
Diffstat (limited to 'source/blender/nodes/geometry')
-rw-r--r--source/blender/nodes/geometry/CMakeLists.txt9
-rw-r--r--source/blender/nodes/geometry/node_geometry_util.hh20
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc121
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc182
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc121
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc45
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc189
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc205
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc136
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc207
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc122
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc113
-rw-r--r--source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc79
13 files changed, 1462 insertions, 87 deletions
diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt
index b80e87e80ac..4515f308ca3 100644
--- a/source/blender/nodes/geometry/CMakeLists.txt
+++ b/source/blender/nodes/geometry/CMakeLists.txt
@@ -56,6 +56,8 @@ set(SRC
nodes/node_geo_curve_subdivide.cc
nodes/node_geo_curve_to_mesh.cc
nodes/node_geo_curve_to_points.cc
+ nodes/node_geo_curve_topology_curve_of_point.cc
+ nodes/node_geo_curve_topology_points_of_curve.cc
nodes/node_geo_curve_trim.cc
nodes/node_geo_deform_curves_on_surface.cc
nodes/node_geo_delete_geometry.cc
@@ -120,6 +122,13 @@ set(SRC
nodes/node_geo_mesh_to_curve.cc
nodes/node_geo_mesh_to_points.cc
nodes/node_geo_mesh_to_volume.cc
+ nodes/node_geo_mesh_topology_corners_of_face.cc
+ nodes/node_geo_mesh_topology_corners_of_vertex.cc
+ nodes/node_geo_mesh_topology_edges_of_corner.cc
+ nodes/node_geo_mesh_topology_edges_of_vertex.cc
+ nodes/node_geo_mesh_topology_face_of_corner.cc
+ nodes/node_geo_mesh_topology_offset_corner_in_face.cc
+ nodes/node_geo_mesh_topology_vertex_of_corner.cc
nodes/node_geo_object_info.cc
nodes/node_geo_points.cc
nodes/node_geo_points_to_vertices.cc
diff --git a/source/blender/nodes/geometry/node_geometry_util.hh b/source/blender/nodes/geometry/node_geometry_util.hh
index 5aeb68b3fdc..adcf47f57fe 100644
--- a/source/blender/nodes/geometry/node_geometry_util.hh
+++ b/source/blender/nodes/geometry/node_geometry_util.hh
@@ -87,7 +87,27 @@ void get_closest_in_bvhtree(BVHTreeFromMesh &tree_data,
const MutableSpan<float> r_distances_sq,
const MutableSpan<float3> r_positions);
+int apply_offset_in_cyclic_range(IndexRange range, int start_index, int offset);
+
std::optional<eCustomDataType> node_data_type_to_custom_data_type(eNodeSocketDatatype type);
std::optional<eCustomDataType> node_socket_to_custom_data_type(const bNodeSocket &socket);
+class FieldAtIndexInput final : public bke::GeometryFieldInput {
+ private:
+ Field<int> index_field_;
+ GField value_field_;
+ eAttrDomain value_field_domain_;
+
+ public:
+ FieldAtIndexInput(Field<int> index_field, GField value_field, eAttrDomain value_field_domain);
+
+ GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
+ const IndexMask mask) const final;
+
+ std::optional<eAttrDomain> preferred_domain(const GeometryComponent & /*component*/) const final
+ {
+ return value_field_domain_;
+ }
+};
+
} // namespace blender::nodes
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc
new file mode 100644
index 00000000000..4d60ab939ca
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_curve_of_point.cc
@@ -0,0 +1,121 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_curves.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_curve_topology_curve_of_point_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Point Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(N_("The control point to retrieve data from"));
+ b.add_output<decl::Int>(N_("Curve Index"))
+ .dependent_field()
+ .description(N_("The curve the control point is part of"));
+ b.add_output<decl::Int>(N_("Index in Curve"))
+ .dependent_field()
+ .description(N_("How far along the control point is along its curve"));
+}
+
+class CurveOfPointInput final : public bke::CurvesFieldInput {
+ public:
+ CurveOfPointInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Point Curve Index")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_POINT) {
+ return {};
+ }
+ return VArray<int>::ForContainer(curves.point_to_curve_map());
+ }
+
+ uint64_t hash() const override
+ {
+ return 413209687345908697;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ if (dynamic_cast<const CurveOfPointInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+class PointIndexInCurveInput final : public bke::CurvesFieldInput {
+ public:
+ PointIndexInCurveInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Point Index in Curve")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_POINT) {
+ return {};
+ }
+ const Span<int> offsets = curves.offsets();
+ Array<int> point_to_curve_map = curves.point_to_curve_map();
+ return VArray<int>::ForFunc(
+ curves.points_num(),
+ [offsets, point_to_curve_map = std::move(point_to_curve_map)](const int point_i) {
+ const int curve_i = point_to_curve_map[point_i];
+ return point_i - offsets[curve_i];
+ });
+ }
+
+ uint64_t hash() const final
+ {
+ return 9834765987345677;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const PointIndexInCurveInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> point_index = params.extract_input<Field<int>>("Point Index");
+ if (params.output_is_required("Curve Index")) {
+ params.set_output(
+ "Curve Index",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ point_index, Field<int>(std::make_shared<CurveOfPointInput>()), ATTR_DOMAIN_POINT)));
+ }
+ if (params.output_is_required("Index in Curve")) {
+ params.set_output("Index in Curve",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ point_index,
+ Field<int>(std::make_shared<PointIndexInCurveInput>()),
+ ATTR_DOMAIN_POINT)));
+ }
+}
+
+} // namespace blender::nodes::node_geo_curve_topology_curve_of_point_cc
+
+void register_node_type_geo_curve_topology_curve_of_point()
+{
+ namespace file_ns = blender::nodes::node_geo_curve_topology_curve_of_point_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_CURVE_TOPOLOGY_CURVE_OF_POINT, "Curve of Point", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc
new file mode 100644
index 00000000000..62a8ba9a976
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_curve_topology_points_of_curve.cc
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_curves.hh"
+
+#include "BLI_task.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_curve_topology_points_of_curve_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Curve Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(N_("The curve to retrieve data from. Defaults to the curve from the context"));
+ b.add_input<decl::Float>(N_("Weights"))
+ .supports_field()
+ .hide_value()
+ .description(N_("Values used to sort the curve's points. Uses indices by default"));
+ b.add_input<decl::Int>(N_("Sort Index"))
+ .min(0)
+ .supports_field()
+ .description(N_("Which of the sorted points to output"));
+ b.add_output<decl::Int>(N_("Total"))
+ .dependent_field()
+ .description(N_("The number of points in the curve"));
+ b.add_output<decl::Int>(N_("Point Index"))
+ .dependent_field()
+ .description(N_("A point of the curve, chosen by the sort index"));
+}
+
+class PointsOfCurveInput final : public bke::CurvesFieldInput {
+ const Field<int> curve_index_;
+ const Field<int> sort_index_;
+ const Field<float> sort_weight_;
+
+ public:
+ PointsOfCurveInput(Field<int> curve_index, Field<int> sort_index, Field<float> sort_weight)
+ : bke::CurvesFieldInput(CPPType::get<int>(), "Point of Curve"),
+ curve_index_(std::move(curve_index)),
+ sort_index_(std::move(sort_index)),
+ sort_weight_(std::move(sort_weight))
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
+ const eAttrDomain domain,
+ const IndexMask mask) const final
+ {
+ const bke::CurvesFieldContext context{curves, domain};
+ fn::FieldEvaluator evaluator{context, &mask};
+ evaluator.add(curve_index_);
+ evaluator.add(sort_index_);
+ evaluator.evaluate();
+ const VArray<int> curve_indices = evaluator.get_evaluated<int>(0);
+ const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1);
+
+ const bke::CurvesFieldContext point_context{curves, ATTR_DOMAIN_POINT};
+ fn::FieldEvaluator point_evaluator{point_context, curves.points_num()};
+ point_evaluator.add(sort_weight_);
+ point_evaluator.evaluate();
+ const VArray<float> all_sort_weights = point_evaluator.get_evaluated<float>(0);
+
+ Array<int> point_of_curve(mask.min_array_size());
+ threading::parallel_for(mask.index_range(), 256, [&](const IndexRange range) {
+ /* Reuse arrays to avoid allocation. */
+ Array<float> sort_weights;
+ Array<int> sort_indices;
+
+ for (const int selection_i : mask.slice(range)) {
+ const int curve_i = curve_indices[selection_i];
+ const int index_in_sort = indices_in_sort[selection_i];
+ if (!curves.curves_range().contains(curve_i)) {
+ point_of_curve[selection_i] = 0;
+ continue;
+ }
+
+ const IndexRange points = curves.points_for_curve(curve_i);
+
+ /* Retrieve the weights for each point. */
+ sort_weights.reinitialize(points.size());
+ all_sort_weights.materialize_compressed(IndexMask(points), sort_weights.as_mutable_span());
+
+ /* Sort a separate array of compressed indices corresponding to the compressed weights.
+ * This allows using `materialize_compressed` to avoid virtual function call overhead
+ * when accessing values in the sort weights. However, it means a separate array of
+ * indices within the compressed array is necessary for sorting. */
+ sort_indices.reinitialize(points.size());
+ std::iota(sort_indices.begin(), sort_indices.end(), 0);
+ std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
+ return sort_weights[a] < sort_weights[b];
+ });
+
+ const int index_in_sort_wrapped = mod_i(index_in_sort, points.size());
+ point_of_curve[selection_i] = points[sort_indices[index_in_sort_wrapped]];
+ }
+ });
+
+ return VArray<int>::ForContainer(std::move(point_of_curve));
+ }
+
+ uint64_t hash() const override
+ {
+ return 26978695677882;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const override
+ {
+ if (const auto *typed = dynamic_cast<const PointsOfCurveInput *>(&other)) {
+ return typed->curve_index_ == curve_index_ && typed->sort_index_ == sort_index_ &&
+ typed->sort_weight_ == sort_weight_;
+ }
+ return false;
+ }
+};
+
+class CurvePointCountInput final : public bke::CurvesFieldInput {
+ public:
+ CurvePointCountInput() : bke::CurvesFieldInput(CPPType::get<int>(), "Curve Point Count")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const bke::CurvesGeometry &curves,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_CURVE) {
+ return {};
+ }
+ return VArray<int>::ForFunc(curves.curves_num(), [&, curves](const int64_t curve_i) {
+ return curves.points_num_for_curve(curve_i);
+ });
+ }
+
+ uint64_t hash() const final
+ {
+ return 903847569873762;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CurvePointCountInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> curve_index = params.extract_input<Field<int>>("Curve Index");
+ if (params.output_is_required("Total")) {
+ params.set_output("Total",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ curve_index,
+ Field<int>(std::make_shared<CurvePointCountInput>()),
+ ATTR_DOMAIN_CURVE)));
+ }
+ if (params.output_is_required("Point Index")) {
+ params.set_output("Point Index",
+ Field<int>(std::make_shared<PointsOfCurveInput>(
+ curve_index,
+ params.extract_input<Field<int>>("Sort Index"),
+ params.extract_input<Field<float>>("Weights"))));
+ }
+}
+
+} // namespace blender::nodes::node_geo_curve_topology_points_of_curve_cc
+
+void register_node_type_geo_curve_topology_points_of_curve()
+{
+ namespace file_ns = blender::nodes::node_geo_curve_topology_points_of_curve_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_CURVE_TOPOLOGY_POINTS_OF_CURVE, "Points of Curve", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc
index d5feaae46ed..af6f15da60c 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_field_at_index.cc
@@ -11,6 +11,63 @@
#include "NOD_socket_search_link.hh"
+namespace blender::nodes {
+
+FieldAtIndexInput::FieldAtIndexInput(Field<int> index_field,
+ GField value_field,
+ eAttrDomain value_field_domain)
+ : bke::GeometryFieldInput(value_field.cpp_type(), "Field at Index"),
+ index_field_(std::move(index_field)),
+ value_field_(std::move(value_field)),
+ value_field_domain_(value_field_domain)
+{
+}
+
+GVArray FieldAtIndexInput::get_varray_for_context(const bke::GeometryFieldContext &context,
+ const IndexMask mask) const
+{
+ const std::optional<AttributeAccessor> attributes = context.attributes();
+ if (!attributes) {
+ return {};
+ }
+
+ const bke::GeometryFieldContext value_field_context{
+ context.geometry(), context.type(), value_field_domain_};
+ FieldEvaluator value_evaluator{value_field_context,
+ attributes->domain_size(value_field_domain_)};
+ value_evaluator.add(value_field_);
+ value_evaluator.evaluate();
+ const GVArray &values = value_evaluator.get_evaluated(0);
+
+ FieldEvaluator index_evaluator{context, &mask};
+ index_evaluator.add(index_field_);
+ index_evaluator.evaluate();
+ const VArray<int> indices = index_evaluator.get_evaluated<int>(0);
+
+ GVArray output_array;
+ attribute_math::convert_to_static_type(*type_, [&](auto dummy) {
+ using T = decltype(dummy);
+ Array<T> dst_array(mask.min_array_size());
+ VArray<T> src_values = values.typed<T>();
+ threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
+ for (const int i : mask.slice(range)) {
+ const int index = indices[i];
+ if (src_values.index_range().contains(index)) {
+ dst_array[i] = src_values[index];
+ }
+ else {
+ dst_array[i] = {};
+ }
+ }
+ });
+ output_array = VArray<T>::ForContainer(std::move(dst_array));
+ });
+
+ return output_array;
+}
+
+} // namespace blender::nodes
+
namespace blender::nodes::node_geo_field_at_index_cc {
static void node_declare(NodeDeclarationBuilder &b)
@@ -89,66 +146,6 @@ static void node_gather_link_searches(GatherLinkSearchOpParams &params)
}
}
-class FieldAtIndex final : public bke::GeometryFieldInput {
- private:
- Field<int> index_field_;
- GField value_field_;
- eAttrDomain value_field_domain_;
-
- public:
- FieldAtIndex(Field<int> index_field, GField value_field, eAttrDomain value_field_domain)
- : bke::GeometryFieldInput(value_field.cpp_type(), "Field at Index"),
- index_field_(std::move(index_field)),
- value_field_(std::move(value_field)),
- value_field_domain_(value_field_domain)
- {
- }
-
- GVArray get_varray_for_context(const bke::GeometryFieldContext &context,
- const IndexMask mask) const final
- {
- const bke::GeometryFieldContext value_field_context{
- context.geometry(), context.type(), value_field_domain_};
- FieldEvaluator value_evaluator{value_field_context,
- context.attributes()->domain_size(value_field_domain_)};
- value_evaluator.add(value_field_);
- value_evaluator.evaluate();
- const GVArray &values = value_evaluator.get_evaluated(0);
-
- FieldEvaluator index_evaluator{context, &mask};
- index_evaluator.add(index_field_);
- index_evaluator.evaluate();
- const VArray<int> indices = index_evaluator.get_evaluated<int>(0);
-
- GVArray output_array;
- attribute_math::convert_to_static_type(*type_, [&](auto dummy) {
- using T = decltype(dummy);
- Array<T> dst_array(mask.min_array_size());
- VArray<T> src_values = values.typed<T>();
- threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
- for (const int i : mask.slice(range)) {
- const int index = indices[i];
- if (src_values.index_range().contains(index)) {
- dst_array[i] = src_values[index];
- }
- else {
- dst_array[i] = {};
- }
- }
- });
- output_array = VArray<T>::ForContainer(std::move(dst_array));
- });
-
- return output_array;
- }
-
- std::optional<eAttrDomain> preferred_domain(
- const GeometryComponent & /*component*/) const override
- {
- return value_field_domain_;
- }
-};
-
static StringRefNull identifier_suffix(eCustomDataType data_type)
{
switch (data_type) {
@@ -179,8 +176,8 @@ static void node_geo_exec(GeoNodeExecParams params)
using T = decltype(dummy);
static const std::string identifier = "Value_" + identifier_suffix(data_type);
Field<T> value_field = params.extract_input<Field<T>>(identifier);
- Field<T> output_field{
- std::make_shared<FieldAtIndex>(std::move(index_field), std::move(value_field), domain)};
+ Field<T> output_field{std::make_shared<FieldAtIndexInput>(
+ std::move(index_field), std::move(value_field), domain)};
params.set_output(identifier, std::move(output_field));
});
}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc b/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc
index b5d0c8cdd74..a0f285f0904 100644
--- a/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc
+++ b/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc
@@ -6,6 +6,24 @@
#include "node_geometry_util.hh"
+namespace blender::nodes {
+
+ int apply_offset_in_cyclic_range(const IndexRange range,
+ const int start_index,
+ const int offset)
+{
+ BLI_assert(range.contains(start_index));
+ const int start_in_range = start_index - range.first();
+ const int offset_in_range = start_in_range + offset;
+ const int mod_offset = offset_in_range % range.size();
+ if (mod_offset >= 0) {
+ return range[mod_offset];
+ }
+ return range.last(-(mod_offset + 1));
+}
+
+} // namespace blender::nodes
+
namespace blender::nodes::node_geo_input_control_point_neighbors_cc {
static void node_declare(NodeDeclarationBuilder &b)
@@ -28,29 +46,6 @@ static void node_declare(NodeDeclarationBuilder &b)
"curves data-block"));
}
-static int apply_offset_in_cyclic_range(const IndexRange range,
- const int start_index,
- const int offset)
-{
- BLI_assert(range.contains(start_index));
- const int start_in_range = start_index - range.first();
- const int offset_in_range = start_in_range + offset;
- const int mod_offset = offset_in_range % range.size();
- if (mod_offset >= 0) {
- return range[mod_offset];
- }
- return range.last(-(mod_offset + 1));
-}
-
-static Array<int> build_parent_curves(const bke::CurvesGeometry &curves)
-{
- Array<int> parent_curves(curves.points_num());
- for (const int i : curves.curves_range()) {
- parent_curves.as_mutable_span().slice(curves.points_for_curve(i)).fill(i);
- }
- return parent_curves;
-}
-
class ControlPointNeighborFieldInput final : public bke::CurvesFieldInput {
private:
const Field<int> index_;
@@ -70,7 +65,7 @@ class ControlPointNeighborFieldInput final : public bke::CurvesFieldInput {
const IndexMask mask) const final
{
const VArray<bool> cyclic = curves.cyclic();
- const Array<int> parent_curves = build_parent_curves(curves);
+ const Array<int> parent_curves = curves.point_to_curve_map();
const bke::CurvesFieldContext context{curves, domain};
fn::FieldEvaluator evaluator{context, &mask};
@@ -118,7 +113,7 @@ class OffsetValidFieldInput final : public bke::CurvesFieldInput {
const IndexMask mask) const final
{
const VArray<bool> cyclic = curves.cyclic();
- const Array<int> parent_curves = build_parent_curves(curves);
+ const Array<int> parent_curves = curves.point_to_curve_map();
const bke::CurvesFieldContext context{curves, domain};
fn::FieldEvaluator evaluator{context, &mask};
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc
new file mode 100644
index 00000000000..cf35f9dbdc5
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_face.cc
@@ -0,0 +1,189 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_task.hh"
+
+#include "BKE_mesh.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_corners_of_face_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Face Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(N_("The face to retrieve data from. Defaults to the face from the context"));
+ b.add_input<decl::Float>(N_("Weights"))
+ .supports_field()
+ .hide_value()
+ .description(N_("Values used to sort the face's corners. Uses indices by default"));
+ b.add_input<decl::Int>(N_("Sort Index"))
+ .min(0)
+ .supports_field()
+ .description(N_("Which of the sorted corners to output"));
+ b.add_output<decl::Int>(N_("Total"))
+ .dependent_field()
+ .description(N_("The number of corners in the face"));
+ b.add_output<decl::Int>(N_("Corner Index"))
+ .dependent_field()
+ .description(N_("A corner of the face, chosen by the sort index"));
+}
+
+class CornersOfFaceInput final : public bke::MeshFieldInput {
+ const Field<int> face_index_;
+ const Field<int> sort_index_;
+ const Field<float> sort_weight_;
+
+ public:
+ CornersOfFaceInput(Field<int> face_index, Field<int> sort_index, Field<float> sort_weight)
+ : bke::MeshFieldInput(CPPType::get<int>(), "Corner of Face"),
+ face_index_(std::move(face_index)),
+ sort_index_(std::move(sort_index)),
+ sort_weight_(std::move(sort_weight))
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask mask) const final
+ {
+ const Span<MPoly> polys = mesh.polys();
+
+ const bke::MeshFieldContext context{mesh, domain};
+ fn::FieldEvaluator evaluator{context, &mask};
+ evaluator.add(face_index_);
+ evaluator.add(sort_index_);
+ evaluator.evaluate();
+ const VArray<int> face_indices = evaluator.get_evaluated<int>(0);
+ const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1);
+
+ const bke::MeshFieldContext corner_context{mesh, ATTR_DOMAIN_CORNER};
+ fn::FieldEvaluator corner_evaluator{corner_context, mesh.totloop};
+ corner_evaluator.add(sort_weight_);
+ corner_evaluator.evaluate();
+ const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0);
+
+ Array<int> corner_of_face(mask.min_array_size());
+ threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
+ /* Reuse arrays to avoid allocation. */
+ Array<float> sort_weights;
+ Array<int> sort_indices;
+
+ for (const int selection_i : mask.slice(range)) {
+ const int poly_i = face_indices[selection_i];
+ const int index_in_sort = indices_in_sort[selection_i];
+ if (!polys.index_range().contains(poly_i)) {
+ corner_of_face[selection_i] = 0;
+ continue;
+ }
+
+ const MPoly &poly = polys[poly_i];
+ const IndexRange corners(poly.loopstart, poly.totloop);
+
+ /* Retrieve the weights for each corner. */
+ sort_weights.reinitialize(corners.size());
+ all_sort_weights.materialize_compressed(IndexMask(corners),
+ sort_weights.as_mutable_span());
+
+ /* Sort a separate array of compressed indices corresponding to the compressed weights.
+ * This allows using `materialize_compressed` to avoid virtual function call overhead
+ * when accessing values in the sort weights. However, it means a separate array of
+ * indices within the compressed array is necessary for sorting. */
+ sort_indices.reinitialize(corners.size());
+ std::iota(sort_indices.begin(), sort_indices.end(), 0);
+ std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
+ return sort_weights[a] < sort_weights[b];
+ });
+
+ const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size());
+ corner_of_face[selection_i] = corners[sort_indices[index_in_sort_wrapped]];
+ }
+ });
+
+ return VArray<int>::ForContainer(std::move(corner_of_face));
+ }
+
+ uint64_t hash() const final
+ {
+ return 6927982716657;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (const auto *typed = dynamic_cast<const CornersOfFaceInput *>(&other)) {
+ return typed->face_index_ == face_index_ && typed->sort_index_ == sort_index_ &&
+ typed->sort_weight_ == sort_weight_;
+ }
+ return false;
+ }
+};
+
+static int get_poly_totloop(const MPoly &poly)
+{
+ return poly.totloop;
+}
+
+class CornersOfFaceCountInput final : public bke::MeshFieldInput {
+ public:
+ CornersOfFaceCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Face Corner Count")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_FACE) {
+ return {};
+ }
+ return VArray<int>::ForDerivedSpan<MPoly, get_poly_totloop>(mesh.polys());
+ }
+
+ uint64_t hash() const final
+ {
+ return 8345908765432698;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CornersOfFaceCountInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> face_index = params.extract_input<Field<int>>("Face Index");
+ if (params.output_is_required("Total")) {
+ params.set_output("Total",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ face_index,
+ Field<int>(std::make_shared<CornersOfFaceCountInput>()),
+ ATTR_DOMAIN_FACE)));
+ }
+ if (params.output_is_required("Corner Index")) {
+ params.set_output("Corner Index",
+ Field<int>(std::make_shared<CornersOfFaceInput>(
+ face_index,
+ params.extract_input<Field<int>>("Sort Index"),
+ params.extract_input<Field<float>>("Weights"))));
+ }
+}
+
+} // namespace blender::nodes::node_geo_mesh_topology_corners_of_face_cc
+
+void register_node_type_geo_mesh_topology_corners_of_face()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_corners_of_face_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_FACE, "Corners of Face", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc
new file mode 100644
index 00000000000..9c3d3819a7c
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_corners_of_vertex.cc
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+
+#include "BLI_task.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_corners_of_vertex_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Vertex Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(
+ N_("The vertex to retrieve data from. Defaults to the vertex from the context"));
+ b.add_input<decl::Float>(N_("Weights"))
+ .supports_field()
+ .hide_value()
+ .description(
+ N_("Values used to sort corners attached to the vertex. Uses indices by default"));
+ b.add_input<decl::Int>(N_("Sort Index"))
+ .min(0)
+ .supports_field()
+ .description(N_("Which of the sorted corners to output"));
+ b.add_output<decl::Int>(N_("Total"))
+ .dependent_field()
+ .description(N_("The number of faces or corners connected to each vertex"));
+ b.add_output<decl::Int>(N_("Corner Index"))
+ .dependent_field()
+ .description(N_("A corner connected to the face, chosen by the sort index"));
+}
+
+static void convert_span(const Span<int> src, MutableSpan<int64_t> dst)
+{
+ for (const int i : src.index_range()) {
+ dst[i] = src[i];
+ }
+}
+
+class CornersOfVertInput final : public bke::MeshFieldInput {
+ const Field<int> vert_index_;
+ const Field<int> sort_index_;
+ const Field<float> sort_weight_;
+
+ public:
+ CornersOfVertInput(Field<int> vert_index, Field<int> sort_index, Field<float> sort_weight)
+ : bke::MeshFieldInput(CPPType::get<int>(), "Corner of Vertex"),
+ vert_index_(std::move(vert_index)),
+ sort_index_(std::move(sort_index)),
+ sort_weight_(std::move(sort_weight))
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask mask) const final
+ {
+ const IndexRange vert_range(mesh.totvert);
+ const Span<MLoop> loops = mesh.loops();
+ Array<Vector<int>> vert_to_corner_map = mesh_topology::build_vert_to_corner_map(loops,
+ mesh.totvert);
+
+ const bke::MeshFieldContext context{mesh, domain};
+ fn::FieldEvaluator evaluator{context, &mask};
+ evaluator.add(vert_index_);
+ evaluator.add(sort_index_);
+ evaluator.evaluate();
+ const VArray<int> vert_indices = evaluator.get_evaluated<int>(0);
+ const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1);
+
+ const bke::MeshFieldContext corner_context{mesh, ATTR_DOMAIN_CORNER};
+ fn::FieldEvaluator corner_evaluator{corner_context, loops.size()};
+ corner_evaluator.add(sort_weight_);
+ corner_evaluator.evaluate();
+ const VArray<float> all_sort_weights = corner_evaluator.get_evaluated<float>(0);
+
+ Array<int> corner_of_vertex(mask.min_array_size());
+ threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
+ /* Reuse arrays to avoid allocation. */
+ Array<int64_t> corner_indices;
+ Array<float> sort_weights;
+ Array<int> sort_indices;
+
+ for (const int selection_i : mask.slice(range)) {
+ const int vert_i = vert_indices[selection_i];
+ const int index_in_sort = indices_in_sort[selection_i];
+ if (!vert_range.contains(vert_i)) {
+ corner_of_vertex[selection_i] = 0;
+ continue;
+ }
+
+ const Span<int> corners = vert_to_corner_map[vert_i];
+
+ /* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */
+ corner_indices.reinitialize(corners.size());
+ convert_span(corners, corner_indices);
+
+ /* Retrieve a compressed array of weights for each edge. */
+ sort_weights.reinitialize(corners.size());
+ all_sort_weights.materialize_compressed(IndexMask(corner_indices),
+ sort_weights.as_mutable_span());
+
+ /* Sort a separate array of compressed indices corresponding to the compressed weights.
+ * This allows using `materialize_compressed` to avoid virtual function call overhead
+ * when accessing values in the sort weights. However, it means a separate array of
+ * indices within the compressed array is necessary for sorting. */
+ sort_indices.reinitialize(corners.size());
+ std::iota(sort_indices.begin(), sort_indices.end(), 0);
+ std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
+ return sort_weights[a] < sort_weights[b];
+ });
+
+ const int index_in_sort_wrapped = mod_i(index_in_sort, corners.size());
+ corner_of_vertex[selection_i] = corner_indices[sort_indices[index_in_sort_wrapped]];
+ }
+ });
+
+ return VArray<int>::ForContainer(std::move(corner_of_vertex));
+ }
+
+ uint64_t hash() const final
+ {
+ return 3541871368173645;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (const auto *typed = dynamic_cast<const CornersOfVertInput *>(&other)) {
+ return typed->vert_index_ == vert_index_ && typed->sort_index_ == sort_index_ &&
+ typed->sort_weight_ == sort_weight_;
+ }
+ return false;
+ }
+};
+
+class CornersOfVertCountInput final : public bke::MeshFieldInput {
+ public:
+ CornersOfVertCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Vertex Corner Count")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_POINT) {
+ return {};
+ }
+ const Span<MLoop> loops = mesh.loops();
+ Array<int> counts(mesh.totvert, 0);
+ for (const int i : loops.index_range()) {
+ counts[loops[i].v]++;
+ }
+ return VArray<int>::ForContainer(std::move(counts));
+ }
+
+ uint64_t hash() const final
+ {
+ return 253098745374645;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CornersOfVertCountInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> vert_index = params.extract_input<Field<int>>("Vertex Index");
+ if (params.output_is_required("Total")) {
+ params.set_output("Total",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ vert_index,
+ Field<int>(std::make_shared<CornersOfVertCountInput>()),
+ ATTR_DOMAIN_POINT)));
+ }
+ if (params.output_is_required("Corner Index")) {
+ params.set_output("Corner Index",
+ Field<int>(std::make_shared<CornersOfVertInput>(
+ vert_index,
+ params.extract_input<Field<int>>("Sort Index"),
+ params.extract_input<Field<float>>("Weights"))));
+ }
+}
+} // namespace blender::nodes::node_geo_mesh_topology_corners_of_vertex_cc
+
+void register_node_type_geo_mesh_topology_corners_of_vertex()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_corners_of_vertex_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_TOPOLOGY_CORNERS_OF_VERTEX, "Corners of Vertex", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc
new file mode 100644
index 00000000000..866db4cf1b0
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_corner.cc
@@ -0,0 +1,136 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+
+#include "BLI_task.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_edges_of_corner_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Corner Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(
+ N_("The corner to retrieve data from. Defaults to the corner from the context"));
+ b.add_output<decl::Int>(N_("Next Edge Index"))
+ .dependent_field()
+ .description(
+ N_("The edge after the corner in the face, in the direction of increasing indices"));
+ b.add_output<decl::Int>(N_("Previous Edge Index"))
+ .dependent_field()
+ .description(
+ N_("The edge before the corner in the face, in the direction of decreasing indices"));
+}
+
+static int get_loop_edge(const MLoop &loop)
+{
+ return loop.e;
+}
+
+class CornerNextEdgeFieldInput final : public bke::MeshFieldInput {
+ public:
+ CornerNextEdgeFieldInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Next Edge")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_CORNER) {
+ return {};
+ }
+ return VArray<int>::ForDerivedSpan<MLoop, get_loop_edge>(mesh.loops());
+ }
+
+ uint64_t hash() const final
+ {
+ return 1892753404495;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CornerNextEdgeFieldInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+class CornerPreviousEdgeFieldInput final : public bke::MeshFieldInput {
+ public:
+ CornerPreviousEdgeFieldInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Previous Edge")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_CORNER) {
+ return {};
+ }
+ const Span<MPoly> polys = mesh.polys();
+ const Span<MLoop> loops = mesh.loops();
+ Array<int> corner_to_poly_map = mesh_topology::build_corner_to_poly_map(polys, mesh.totloop);
+ return VArray<int>::ForFunc(
+ mesh.totloop,
+ [polys, loops, corner_to_poly_map = std::move(corner_to_poly_map)](const int corner_i) {
+ const int poly_i = corner_to_poly_map[corner_i];
+ const MPoly &poly = polys[poly_i];
+ const int corner_i_prev = mesh_topology::previous_poly_corner(poly, corner_i);
+ return loops[corner_i_prev].e;
+ });
+ }
+
+ uint64_t hash() const final
+ {
+ return 987298345762465;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CornerPreviousEdgeFieldInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> corner_index = params.extract_input<Field<int>>("Corner Index");
+ if (params.output_is_required("Next Edge Index")) {
+ params.set_output("Next Edge Index",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ corner_index,
+ Field<int>(std::make_shared<CornerNextEdgeFieldInput>()),
+ ATTR_DOMAIN_CORNER)));
+ }
+ if (params.output_is_required("Previous Edge Index")) {
+ params.set_output("Previous Edge Index",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ corner_index,
+ Field<int>(std::make_shared<CornerPreviousEdgeFieldInput>()),
+ ATTR_DOMAIN_CORNER)));
+ }
+}
+
+} // namespace blender::nodes::node_geo_mesh_topology_edges_of_corner_cc
+
+void register_node_type_geo_mesh_topology_edges_of_corner()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_edges_of_corner_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_TOPOLOGY_EDGES_OF_CORNER, "Edges of Corner", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc
new file mode 100644
index 00000000000..11b41ac5605
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_edges_of_vertex.cc
@@ -0,0 +1,207 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+
+#include "BLI_task.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_edges_of_vertex_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Vertex Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(
+ N_("The vertex to retrieve data from. Defaults to the vertex from the context"));
+ b.add_input<decl::Float>(N_("Weights"))
+ .supports_field()
+ .hide_value()
+ .description(
+ N_("Values used to sort the edges connected to the vertex. Uses indices by default"));
+ b.add_input<decl::Int>(N_("Sort Index"))
+ .min(0)
+ .supports_field()
+ .description(N_("Which of the sorted edges to output"));
+ b.add_output<decl::Int>(N_("Total"))
+ .dependent_field()
+ .description(N_("The number of edges connected to each vertex"));
+ b.add_output<decl::Int>(N_("Edge Index"))
+ .dependent_field()
+ .description(N_("An edge connected to the face, chosen by the sort index"));
+}
+
+static void convert_span(const Span<int> src, MutableSpan<int64_t> dst)
+{
+ for (const int i : src.index_range()) {
+ dst[i] = src[i];
+ }
+}
+
+class EdgesOfVertInput final : public bke::MeshFieldInput {
+ const Field<int> vert_index_;
+ const Field<int> sort_index_;
+ const Field<float> sort_weight_;
+
+ public:
+ EdgesOfVertInput(Field<int> vert_index, Field<int> sort_index, Field<float> sort_weight)
+ : bke::MeshFieldInput(CPPType::get<int>(), "Edge of Vertex"),
+ vert_index_(std::move(vert_index)),
+ sort_index_(std::move(sort_index)),
+ sort_weight_(std::move(sort_weight))
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask mask) const final
+ {
+ const IndexRange vert_range(mesh.totvert);
+ const Span<MEdge> edges = mesh.edges();
+ Array<Vector<int>> vert_to_edge_map = mesh_topology::build_vert_to_edge_map(edges,
+ mesh.totvert);
+
+ const bke::MeshFieldContext context{mesh, domain};
+ fn::FieldEvaluator evaluator{context, &mask};
+ evaluator.add(vert_index_);
+ evaluator.add(sort_index_);
+ evaluator.evaluate();
+ const VArray<int> vert_indices = evaluator.get_evaluated<int>(0);
+ const VArray<int> indices_in_sort = evaluator.get_evaluated<int>(1);
+
+ const bke::MeshFieldContext edge_context{mesh, ATTR_DOMAIN_EDGE};
+ fn::FieldEvaluator edge_evaluator{edge_context, mesh.totedge};
+ edge_evaluator.add(sort_weight_);
+ edge_evaluator.evaluate();
+ const VArray<float> all_sort_weights = edge_evaluator.get_evaluated<float>(0);
+
+ Array<int> edge_of_vertex(mask.min_array_size());
+ threading::parallel_for(mask.index_range(), 1024, [&](const IndexRange range) {
+ /* Reuse arrays to avoid allocation. */
+ Array<int64_t> edge_indices;
+ Array<float> sort_weights;
+ Array<int> sort_indices;
+
+ for (const int selection_i : mask.slice(range)) {
+ const int vert_i = vert_indices[selection_i];
+ const int index_in_sort = indices_in_sort[selection_i];
+ if (!vert_range.contains(vert_i)) {
+ edge_of_vertex[selection_i] = 0;
+ continue;
+ }
+
+ const Span<int> edges = vert_to_edge_map[vert_i];
+
+ /* Retrieve the connected edge indices as 64 bit integers for #materialize_compressed. */
+ edge_indices.reinitialize(edges.size());
+ convert_span(edges, edge_indices);
+
+ /* Retrieve a compressed array of weights for each edge. */
+ sort_weights.reinitialize(edges.size());
+ all_sort_weights.materialize_compressed(IndexMask(edge_indices),
+ sort_weights.as_mutable_span());
+
+ /* Sort a separate array of compressed indices corresponding to the compressed weights.
+ * This allows using `materialize_compressed` to avoid virtual function call overhead
+ * when accessing values in the sort weights. However, it means a separate array of
+ * indices within the compressed array is necessary for sorting. */
+ sort_indices.reinitialize(edges.size());
+ std::iota(sort_indices.begin(), sort_indices.end(), 0);
+ std::stable_sort(sort_indices.begin(), sort_indices.end(), [&](int a, int b) {
+ return sort_weights[a] < sort_weights[b];
+ });
+
+ const int index_in_sort_wrapped = mod_i(index_in_sort, edges.size());
+ edge_of_vertex[selection_i] = edge_indices[sort_indices[index_in_sort_wrapped]];
+ }
+ });
+
+ return VArray<int>::ForContainer(std::move(edge_of_vertex));
+ }
+
+ uint64_t hash() const final
+ {
+ return 98762349875636;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (const auto *typed = dynamic_cast<const EdgesOfVertInput *>(&other)) {
+ return typed->vert_index_ == vert_index_ && typed->sort_index_ == sort_index_ &&
+ typed->sort_weight_ == sort_weight_;
+ }
+ return false;
+ }
+};
+
+class EdgesOfVertCountInput final : public bke::MeshFieldInput {
+ public:
+ EdgesOfVertCountInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Face Index")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_POINT) {
+ return {};
+ }
+ const Span<MEdge> edges = mesh.edges();
+ Array<int> counts(mesh.totvert, 0);
+ for (const int i : edges.index_range()) {
+ counts[edges[i].v1]++;
+ counts[edges[i].v2]++;
+ }
+ return VArray<int>::ForContainer(std::move(counts));
+ }
+
+ uint64_t hash() const final
+ {
+ return 436758278618374;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const EdgesOfVertCountInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> vert_index = params.extract_input<Field<int>>("Vertex Index");
+ if (params.output_is_required("Total")) {
+ params.set_output("Total",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ vert_index,
+ Field<int>(std::make_shared<EdgesOfVertCountInput>()),
+ ATTR_DOMAIN_POINT)));
+ }
+ if (params.output_is_required("Edge Index")) {
+ params.set_output("Edge Index",
+ Field<int>(std::make_shared<EdgesOfVertInput>(
+ vert_index,
+ params.extract_input<Field<int>>("Sort Index"),
+ params.extract_input<Field<float>>("Weights"))));
+ }
+}
+
+} // namespace blender::nodes::node_geo_mesh_topology_edges_of_vertex_cc
+
+void register_node_type_geo_mesh_topology_edges_of_vertex()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_edges_of_vertex_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_TOPOLOGY_EDGES_OF_VERTEX, "Edges of Vertex", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc
new file mode 100644
index 00000000000..06a69682c90
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_face_of_corner.cc
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_face_of_corner_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Corner Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(
+ N_("The corner to retrieve data from. Defaults to the corner from the context"));
+ b.add_output<decl::Int>(N_("Face Index"))
+ .dependent_field()
+ .description(N_("The index of the face the corner is a part of"));
+ b.add_output<decl::Int>(N_("Index in Face"))
+ .dependent_field()
+ .description(N_("The index of the corner starting from the first corner in the face"));
+}
+
+class CornerFaceIndexInput final : public bke::MeshFieldInput {
+ public:
+ CornerFaceIndexInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Face Index")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_CORNER) {
+ return {};
+ }
+ return VArray<int>::ForContainer(
+ mesh_topology::build_corner_to_poly_map(mesh.polys(), mesh.totloop));
+ }
+
+ uint64_t hash() const final
+ {
+ return 2348712958475728;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ return dynamic_cast<const CornerFaceIndexInput *>(&other) != nullptr;
+ }
+};
+
+class CornerIndexInFaceInput final : public bke::MeshFieldInput {
+ public:
+ CornerIndexInFaceInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Index In Face")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_CORNER) {
+ return {};
+ }
+ const Span<MPoly> polys = mesh.polys();
+ Array<int> corner_to_poly_map = mesh_topology::build_corner_to_poly_map(polys, mesh.totloop);
+ return VArray<int>::ForFunc(
+ mesh.totloop,
+ [polys, corner_to_poly_map = std::move(corner_to_poly_map)](const int corner_i) {
+ const int poly_i = corner_to_poly_map[corner_i];
+ return corner_i - polys[poly_i].loopstart;
+ });
+ }
+
+ uint64_t hash() const final
+ {
+ return 97837176448;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CornerIndexInFaceInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ const Field<int> corner_index = params.extract_input<Field<int>>("Corner Index");
+ if (params.output_is_required("Face Index")) {
+ params.set_output("Face Index",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ corner_index,
+ Field<int>(std::make_shared<CornerFaceIndexInput>()),
+ ATTR_DOMAIN_CORNER)));
+ }
+ if (params.output_is_required("Index in Face")) {
+ params.set_output("Index in Face",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ corner_index,
+ Field<int>(std::make_shared<CornerIndexInFaceInput>()),
+ ATTR_DOMAIN_CORNER)));
+ }
+}
+
+} // namespace blender::nodes::node_geo_mesh_topology_face_of_corner_cc
+
+void register_node_type_geo_mesh_topology_face_of_corner()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_face_of_corner_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_TOPOLOGY_FACE_OF_CORNER, "Face of Corner", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc
new file mode 100644
index 00000000000..e1b3572aa75
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_offset_corner_in_face.cc
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_mapping.h"
+
+#include "BLI_task.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_offset_corner_in_face_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Corner Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(
+ N_("The corner to retrieve data from. Defaults to the corner from the context"));
+ b.add_input<decl::Int>(N_("Offset"))
+ .supports_field()
+ .description(N_("The number of corners to move around the face before finding the result, "
+ "circling around the start of the face if necessary"));
+ b.add_output<decl::Int>(N_("Corner Index"))
+ .dependent_field()
+ .description(N_("The index of the offset corner"));
+}
+
+class OffsetCornerInFaceFieldInput final : public bke::MeshFieldInput {
+ const Field<int> corner_index_;
+ const Field<int> offset_;
+
+ public:
+ OffsetCornerInFaceFieldInput(Field<int> corner_index, Field<int> offset)
+ : bke::MeshFieldInput(CPPType::get<int>(), "Offset Corner in Face"),
+ corner_index_(std::move(corner_index)),
+ offset_(std::move(offset))
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask mask) const final
+ {
+ const IndexRange corner_range(mesh.totloop);
+ const Span<MPoly> polys = mesh.polys();
+
+ const bke::MeshFieldContext context{mesh, domain};
+ fn::FieldEvaluator evaluator{context, &mask};
+ evaluator.add(corner_index_);
+ evaluator.add(offset_);
+ evaluator.evaluate();
+ const VArray<int> corner_indices = evaluator.get_evaluated<int>(0);
+ const VArray<int> offsets = evaluator.get_evaluated<int>(1);
+
+ Array<int> corner_to_poly_map = mesh_topology::build_corner_to_poly_map(polys, mesh.totloop);
+
+ Array<int> offset_corners(mask.min_array_size());
+ threading::parallel_for(mask.index_range(), 2048, [&](const IndexRange range) {
+ for (const int selection_i : range) {
+ const int corner_i = corner_indices[selection_i];
+ const int offset = offsets[selection_i];
+ if (!corner_range.contains(corner_i)) {
+ offset_corners[selection_i] = 0;
+ continue;
+ }
+
+ const int poly_i = corner_to_poly_map[corner_i];
+ const IndexRange poly_range(polys[poly_i].loopstart, polys[poly_i].totloop);
+ offset_corners[selection_i] = apply_offset_in_cyclic_range(poly_range, corner_i, offset);
+ }
+ });
+
+ return VArray<int>::ForContainer(std::move(offset_corners));
+ }
+
+ uint64_t hash() const final
+ {
+ return get_default_hash(offset_);
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (const OffsetCornerInFaceFieldInput *other_field =
+ dynamic_cast<const OffsetCornerInFaceFieldInput *>(&other)) {
+ return other_field->corner_index_ == corner_index_ && other_field->offset_ == offset_;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ params.set_output("Corner Index",
+ Field<int>(std::make_shared<OffsetCornerInFaceFieldInput>(
+ params.extract_input<Field<int>>("Corner Index"),
+ params.extract_input<Field<int>>("Offset"))));
+}
+
+} // namespace blender::nodes::node_geo_mesh_topology_offset_corner_in_face_cc
+
+void register_node_type_geo_mesh_topology_offset_corner_in_face()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_offset_corner_in_face_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(&ntype,
+ GEO_NODE_MESH_TOPOLOGY_OFFSET_CORNER_IN_FACE,
+ "Offset Corner in Face",
+ NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}
diff --git a/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc
new file mode 100644
index 00000000000..f0163fa553a
--- /dev/null
+++ b/source/blender/nodes/geometry/nodes/node_geo_mesh_topology_vertex_of_corner.cc
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BKE_mesh.h"
+
+#include "BLI_task.hh"
+
+#include "node_geometry_util.hh"
+
+namespace blender::nodes::node_geo_mesh_topology_vertex_of_corner_cc {
+
+static void node_declare(NodeDeclarationBuilder &b)
+{
+ b.add_input<decl::Int>(N_("Corner Index"))
+ .implicit_field(implicit_field_inputs::index)
+ .description(
+ N_("The corner to retrieve data from. Defaults to the corner from the context"));
+ b.add_output<decl::Int>(N_("Vertex Index"))
+ .dependent_field()
+ .description(N_("The vertex the corner is attached to"));
+}
+
+static int get_loop_vert(const MLoop &loop)
+{
+ return loop.v;
+}
+
+class CornerVertFieldInput final : public bke::MeshFieldInput {
+ public:
+ CornerVertFieldInput() : bke::MeshFieldInput(CPPType::get<int>(), "Corner Vertex")
+ {
+ category_ = Category::Generated;
+ }
+
+ GVArray get_varray_for_context(const Mesh &mesh,
+ const eAttrDomain domain,
+ const IndexMask /*mask*/) const final
+ {
+ if (domain != ATTR_DOMAIN_CORNER) {
+ return {};
+ }
+ return VArray<int>::ForDerivedSpan<MLoop, get_loop_vert>(mesh.loops());
+ }
+
+ uint64_t hash() const final
+ {
+ return 30495867093876;
+ }
+
+ bool is_equal_to(const fn::FieldNode &other) const final
+ {
+ if (dynamic_cast<const CornerVertFieldInput *>(&other)) {
+ return true;
+ }
+ return false;
+ }
+};
+
+static void node_geo_exec(GeoNodeExecParams params)
+{
+ params.set_output("Vertex Index",
+ Field<int>(std::make_shared<FieldAtIndexInput>(
+ params.extract_input<Field<int>>("Corner Index"),
+ Field<int>(std::make_shared<CornerVertFieldInput>()),
+ ATTR_DOMAIN_CORNER)));
+}
+
+} // namespace blender::nodes::node_geo_mesh_topology_vertex_of_corner_cc
+
+void register_node_type_geo_mesh_topology_vertex_of_corner()
+{
+ namespace file_ns = blender::nodes::node_geo_mesh_topology_vertex_of_corner_cc;
+
+ static bNodeType ntype;
+ geo_node_type_base(
+ &ntype, GEO_NODE_MESH_TOPOLOGY_VERTEX_OF_CORNER, "Vertex of Corner", NODE_CLASS_INPUT);
+ ntype.geometry_node_execute = file_ns::node_geo_exec;
+ ntype.declare = file_ns::node_declare;
+ nodeRegisterType(&ntype);
+}