diff options
-rw-r--r-- | release/scripts/startup/nodeitems_builtins.py | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/BKE_node.h | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_geometry.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/NOD_static_types.h | 1 | ||||
-rw-r--r-- | source/blender/nodes/geometry/CMakeLists.txt | 1 | ||||
-rw-r--r-- | source/blender/nodes/geometry/nodes/node_geo_input_control_point_neighbors.cc | 174 |
7 files changed, 180 insertions, 0 deletions
diff --git a/release/scripts/startup/nodeitems_builtins.py b/release/scripts/startup/nodeitems_builtins.py index 95bb9fd7e40..38629c18ca0 100644 --- a/release/scripts/startup/nodeitems_builtins.py +++ b/release/scripts/startup/nodeitems_builtins.py @@ -82,6 +82,7 @@ def curve_node_items(context): yield NodeItem("GeometryNodeSubdivideCurve") yield NodeItem("GeometryNodeTrimCurve") yield NodeItemCustom(draw=lambda self, layout, context: layout.separator()) + yield NodeItem("GeometryNodeInputControlPointNeighbors") yield NodeItem("GeometryNodeInputCurveHandlePositions") yield NodeItem("GeometryNodeInputTangent") yield NodeItem("GeometryNodeInputCurveTilt") diff --git a/source/blender/blenkernel/BKE_node.h b/source/blender/blenkernel/BKE_node.h index 14cf8164b79..625c4d87bcd 100644 --- a/source/blender/blenkernel/BKE_node.h +++ b/source/blender/blenkernel/BKE_node.h @@ -1531,6 +1531,7 @@ struct TexResult; #define GEO_NODE_SAMPLE_INDEX 1174 #define GEO_NODE_SAMPLE_NEAREST 1175 #define GEO_NODE_SAMPLE_NEAREST_SURFACE 1176 +#define GEO_NODE_INPUT_CONTROL_POINT_NEIGHBORS 1177 /** \} */ diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 4ed0bf12cb6..0dcb2aa3139 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -4743,6 +4743,7 @@ static void registerGeometryNodes() register_node_type_geo_flip_faces(); register_node_type_geo_geometry_to_instance(); register_node_type_geo_image_texture(); + register_node_type_geo_input_control_point_neighbors(); register_node_type_geo_input_curve_handles(); register_node_type_geo_input_curve_tilt(); register_node_type_geo_input_id(); diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index d02bbeb67f7..4ead6326295 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -61,6 +61,7 @@ void register_node_type_geo_field_at_index(void); void register_node_type_geo_flip_faces(void); void register_node_type_geo_geometry_to_instance(void); void register_node_type_geo_image_texture(void); +void register_node_type_geo_input_control_point_neighbors(void); void register_node_type_geo_input_curve_handles(void); void register_node_type_geo_input_curve_tilt(void); void register_node_type_geo_input_id(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index 2db8be661c3..574a8dcab96 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -319,6 +319,7 @@ DefNode(GeometryNode, GEO_NODE_FLIP_FACES, 0, "FLIP_FACES", FlipFaces, "Flip Fac DefNode(GeometryNode, GEO_NODE_GEOMETRY_TO_INSTANCE, 0, "GEOMETRY_TO_INSTANCE", GeometryToInstance, "Geometry to Instance", "Convert each input geometry into an instance, which can be much faster than the Join Geometry node when the inputs are large") DefNode(GeometryNode, GEO_NODE_IMAGE_TEXTURE, def_geo_image_texture, "IMAGE_TEXTURE", ImageTexture, "Image Texture", "Sample values from an image texture") DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_HANDLES, 0, "INPUT_CURVE_HANDLES",InputCurveHandlePositions,"Curve Handle Positions", "Retrieve the position of each Bézier control point's handles") +DefNode(GeometryNode, GEO_NODE_INPUT_CONTROL_POINT_NEIGHBORS, 0, "INPUT_CONTROL_POINT_NEIGHBORS", InputControlPointNeighbors, "Control Point Neighbors", "Offset a control point index within its curve") DefNode(GeometryNode, GEO_NODE_INPUT_CURVE_TILT, 0, "INPUT_CURVE_TILT", InputCurveTilt, "Curve Tilt", "Retrieve the angle at each control point used to twist the curve's normal around its tangent") DefNode(GeometryNode, GEO_NODE_INPUT_ID, 0, "INPUT_ID", InputID, "ID", "Retrieve a stable random identifier value from the \"id\" attribute on the point domain, or the index if the attribute does not exist") DefNode(GeometryNode, GEO_NODE_INPUT_INDEX, 0, "INDEX", InputIndex, "Index", "Retrieve an integer value indicating the position of each element in the list, starting at zero") 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<decl::Int>(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<decl::Int>(N_("Offset")) + .dependent_field() + .description(N_("The number of control points along the curve to traverse")); + b.add_output<decl::Bool>(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<decl::Int>(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<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: + Field<int> index_; + Field<int> offset_; + + public: + ControlPointNeighborFieldInput(Field<int> index, Field<int> offset) + : CurvesFieldInput(CPPType::get<int>(), "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<bool> cyclic = curves.cyclic(); + const Array<int> 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<int> indices = evaluator.get_evaluated<int>(0); + const VArray<int> offsets = evaluator.get_evaluated<int>(1); + Array<int> 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<int>::ForContainer(std::move(output)); + } +}; + +class OffsetValidFieldInput final : public bke::CurvesFieldInput { + private: + Field<int> index_; + Field<int> offset_; + + public: + OffsetValidFieldInput(Field<int> index, Field<int> offset) + : CurvesFieldInput(CPPType::get<bool>(), "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<int> indices = evaluator.get_evaluated<int>(0); + const VArray<int> offsets = evaluator.get_evaluated<int>(1); + Array<int> parent_curves = build_parent_curves(curves); + VArray<bool> cyclic = curves.cyclic(); + Array<bool> 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<bool>::ForContainer(std::move(output)); + } +}; + +static void node_geo_exec(GeoNodeExecParams params) +{ + Field<int> index = params.extract_input<Field<int>>("Point Index"); + Field<int> offset = params.extract_input<Field<int>>("Offset"); + + if (params.output_is_required("Point Index")) { + Field<int> curve_point_field{std::make_shared<ControlPointNeighborFieldInput>(index, offset)}; + params.set_output("Point Index", std::move(curve_point_field)); + } + if (params.output_is_required("Is Valid Offset")) { + Field<bool> valid_field{std::make_shared<OffsetValidFieldInput>(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); +} |