From 8a6dc0fac71cc5eb6fc945295b9c1f51f72cc407 Mon Sep 17 00:00:00 2001 From: Johnny Matthews Date: Mon, 26 Sep 2022 13:03:28 -0500 Subject: Geometry Nodes: Control Point Neighbors Node This node allows access to the indices of neighboring control points within a curve via an offset. This includes taking into consideration curves that are cyclic. Differential Revision: D13373 --- source/blender/nodes/geometry/CMakeLists.txt | 1 + .../node_geo_input_control_point_neighbors.cc | 174 +++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc (limited to 'source/blender/nodes/geometry') diff --git a/source/blender/nodes/geometry/CMakeLists.txt b/source/blender/nodes/geometry/CMakeLists.txt index 19a325eca89..b80e87e80ac 100644 --- a/source/blender/nodes/geometry/CMakeLists.txt +++ b/source/blender/nodes/geometry/CMakeLists.txt @@ -71,6 +71,7 @@ set(SRC nodes/node_geo_flip_faces.cc nodes/node_geo_geometry_to_instance.cc nodes/node_geo_image_texture.cc + nodes/node_geo_input_control_point_neighbors.cc nodes/node_geo_input_curve_handles.cc nodes/node_geo_input_curve_tilt.cc nodes/node_geo_input_id.cc 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 new file mode 100644 index 00000000000..33b15e0056e --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc @@ -0,0 +1,174 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_task.hh" + +#include "node_geometry_util.hh" + +#include "BKE_curves.hh" + +namespace blender::nodes::node_geo_input_control_point_neighbors_cc { + +static void node_declare(NodeDeclarationBuilder &b) +{ + b.add_input(N_("Point Index")) + .implicit_field(implicit_field_inputs::index) + .hide_value() + .description( + N_("The index of the control point to evaluate. Defaults to the current index")); + b.add_input(N_("Offset")) + .dependent_field() + .description(N_("The number of control points along the curve to traverse")); + b.add_output(N_("Is Valid Offset")) + .field_source() + .description(N_("Outputs true if the evaluated control point plus the offset " + "is a valid index of the original curve")); + b.add_output(N_("Point Index")) + .field_source() + .description(N_("The index of the control point plus the offset within the entire " + "curves object")); +} + +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 build_parent_curves(const bke::CurvesGeometry &curves) +{ + Array 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: + Field index_; + Field offset_; + + public: + ControlPointNeighborFieldInput(Field index, Field offset) + : CurvesFieldInput(CPPType::get(), "Control Point Neighbors node"), + index_(index), + offset_(offset) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const bke::CurvesGeometry &curves, + const eAttrDomain domain, + IndexMask mask) const final + { + const VArray cyclic = curves.cyclic(); + const Array parent_curves = build_parent_curves(curves); + bke::CurvesFieldContext context{curves, domain}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(index_); + evaluator.add(offset_); + evaluator.evaluate(); + const VArray indices = evaluator.get_evaluated(0); + const VArray offsets = evaluator.get_evaluated(1); + Array output(curves.points_num()); + + for (const int i_selection : mask) { + const int i_point = std::clamp(indices[i_selection], 0, curves.points_num() - 1); + const int i_curve = parent_curves[i_point]; + const IndexRange curve_points = curves.points_for_curve(i_curve); + const int offset_point = i_point + offsets[i_point]; + + if (cyclic[i_curve]) { + output[i_selection] = apply_offset_in_cyclic_range( + curve_points, i_point, offsets[i_selection]); + continue; + } + output[i_selection] = std::clamp(offset_point, 0, int(curves.points_num() - 1)); + } + + return VArray::ForContainer(std::move(output)); + } +}; + +class OffsetValidFieldInput final : public bke::CurvesFieldInput { + private: + Field index_; + Field offset_; + + public: + OffsetValidFieldInput(Field index, Field offset) + : CurvesFieldInput(CPPType::get(), "Offset Valid"), index_(index), offset_(offset) + { + category_ = Category::Generated; + } + + GVArray get_varray_for_context(const bke::CurvesGeometry &curves, + const eAttrDomain domain, + const IndexMask mask) const final + { + bke::CurvesFieldContext context{curves, ATTR_DOMAIN_POINT}; + fn::FieldEvaluator evaluator{context, &mask}; + evaluator.add(index_); + evaluator.add(offset_); + evaluator.evaluate(); + const VArray indices = evaluator.get_evaluated(0); + const VArray offsets = evaluator.get_evaluated(1); + Array parent_curves = build_parent_curves(curves); + VArray cyclic = curves.cyclic(); + Array output(curves.points_num()); + + for (const int i_selection : mask) { + const int i_point = indices[i_selection]; + + if (!curves.points_range().contains(i_point)) { + output[i_selection] = false; + continue; + } + const int i_curve = parent_curves[i_point]; + const IndexRange curve_points = curves.points_for_curve(i_curve); + if (cyclic[i_curve]) { + output[i_selection] = true; + continue; + } + output[i_selection] = curve_points.contains(i_point + offsets[i_selection]); + }; + return VArray::ForContainer(std::move(output)); + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field index = params.extract_input>("Point Index"); + Field offset = params.extract_input>("Offset"); + + if (params.output_is_required("Point Index")) { + Field curve_point_field{std::make_shared(index, offset)}; + params.set_output("Point Index", std::move(curve_point_field)); + } + if (params.output_is_required("Is Valid Offset")) { + Field valid_field{std::make_shared(index, offset)}; + params.set_output("Is Valid Offset", std::move(valid_field)); + } + params.set_default_remaining_outputs(); +} + +} // namespace blender::nodes::node_geo_input_control_point_neighbors_cc + +void register_node_type_geo_input_control_point_neighbors() +{ + namespace file_ns = blender::nodes::node_geo_input_control_point_neighbors_cc; + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_INPUT_CONTROL_POINT_NEIGHBORS, "Control Point Neighbors", NODE_CLASS_INPUT); + ntype.geometry_node_execute = file_ns::node_geo_exec; + ntype.declare = file_ns::node_declare; + nodeRegisterType(&ntype); +} -- cgit v1.2.3