From 0f455765907528ea9d6c15e9d224d81d507e51ad Mon Sep 17 00:00:00 2001 From: Johnny Matthews Date: Tue, 3 Aug 2021 23:14:03 -0400 Subject: Geometry Nodes: Curve Set Spline Type This node sets the selected (or all) splines in curve to a chosen target spline type. Poly, Bezier, and NURB splines can be converted to any of the other types. This is meant to be a building block node, useful in many procedural situations. In the future the node could be optimized with multi-threading, or by avoiding copying in many cases, either by retrieving the curve for write access or by passing the raw vectors to the new splines where possible. With edits from Hans Goudey (@HooglyBoogly) Differential Revision: https://developer.blender.org/D12013 --- source/blender/nodes/CMakeLists.txt | 1 + source/blender/nodes/NOD_geometry.h | 1 + source/blender/nodes/NOD_static_types.h | 1 + .../geometry/nodes/node_geo_curve_spline_type.cc | 307 +++++++++++++++++++++ 4 files changed, 310 insertions(+) create mode 100644 source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc (limited to 'source/blender/nodes') diff --git a/source/blender/nodes/CMakeLists.txt b/source/blender/nodes/CMakeLists.txt index 36e5be6a292..7defb36bb83 100644 --- a/source/blender/nodes/CMakeLists.txt +++ b/source/blender/nodes/CMakeLists.txt @@ -176,6 +176,7 @@ set(SRC geometry/nodes/node_geo_curve_resample.cc geometry/nodes/node_geo_curve_reverse.cc geometry/nodes/node_geo_curve_set_handles.cc + geometry/nodes/node_geo_curve_spline_type.cc geometry/nodes/node_geo_curve_subdivide.cc geometry/nodes/node_geo_curve_to_mesh.cc geometry/nodes/node_geo_curve_to_points.cc diff --git a/source/blender/nodes/NOD_geometry.h b/source/blender/nodes/NOD_geometry.h index 868fcbb33af..c2297796b97 100644 --- a/source/blender/nodes/NOD_geometry.h +++ b/source/blender/nodes/NOD_geometry.h @@ -63,6 +63,7 @@ void register_node_type_geo_curve_primitive_star(void); void register_node_type_geo_curve_resample(void); void register_node_type_geo_curve_reverse(void); void register_node_type_geo_curve_set_handles(void); +void register_node_type_geo_curve_spline_type(void); void register_node_type_geo_curve_subdivide(void); void register_node_type_geo_curve_to_mesh(void); void register_node_type_geo_curve_to_points(void); diff --git a/source/blender/nodes/NOD_static_types.h b/source/blender/nodes/NOD_static_types.h index a091f28f3a0..b368ac74894 100644 --- a/source/blender/nodes/NOD_static_types.h +++ b/source/blender/nodes/NOD_static_types.h @@ -303,6 +303,7 @@ DefNode(GeometryNode, GEO_NODE_CURVE_PRIMITIVE_STAR, 0, "CURVE_PRIMITIVE_STAR", DefNode(GeometryNode, GEO_NODE_CURVE_RESAMPLE, def_geo_curve_resample, "CURVE_RESAMPLE", CurveResample, "Resample Curve", "") DefNode(GeometryNode, GEO_NODE_CURVE_REVERSE, 0, "CURVE_REVERSE", CurveReverse, "Curve Reverse", "") DefNode(GeometryNode, GEO_NODE_CURVE_SET_HANDLES, def_geo_curve_set_handles, "CURVE_SET_HANDLES", CurveSetHandles, "Set Handle Type", "") +DefNode(GeometryNode, GEO_NODE_CURVE_SPLINE_TYPE, def_geo_curve_spline_type, "CURVE_SPLINE_TYPE", CurveSplineType, "Set Spline Type", "") DefNode(GeometryNode, GEO_NODE_CURVE_SUBDIVIDE, def_geo_curve_subdivide, "CURVE_SUBDIVIDE", CurveSubdivide, "Curve Subdivide", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_MESH, 0, "CURVE_TO_MESH", CurveToMesh, "Curve to Mesh", "") DefNode(GeometryNode, GEO_NODE_CURVE_TO_POINTS, def_geo_curve_to_points, "CURVE_TO_POINTS", CurveToPoints, "Curve to Points", "") diff --git a/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc new file mode 100644 index 00000000000..fe3f42625ae --- /dev/null +++ b/source/blender/nodes/geometry/nodes/node_geo_curve_spline_type.cc @@ -0,0 +1,307 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "BKE_spline.hh" + +#include "BLI_task.hh" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "node_geometry_util.hh" + +static bNodeSocketTemplate geo_node_curve_spline_type_in[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {SOCK_STRING, N_("Selection")}, + {-1, ""}, +}; + +static bNodeSocketTemplate geo_node_curve_spline_type_out[] = { + {SOCK_GEOMETRY, N_("Curve")}, + {-1, ""}, +}; + +static void geo_node_curve_spline_type_layout(uiLayout *layout, + bContext *UNUSED(C), + PointerRNA *ptr) +{ + uiItemR(layout, ptr, "spline_type", 0, "", ICON_NONE); +} + +namespace blender::nodes { + +static void geo_node_curve_spline_type_init(bNodeTree *UNUSED(tree), bNode *node) +{ + NodeGeometryCurveSplineType *data = (NodeGeometryCurveSplineType *)MEM_callocN( + sizeof(NodeGeometryCurveSplineType), __func__); + + data->spline_type = GEO_NODE_SPLINE_TYPE_POLY; + node->storage = data; +} + +template +static void scale_input_assign(const Span input, + const int scale, + const int offset, + const MutableSpan r_output) +{ + for (const int i : IndexRange(r_output.size())) { + r_output[i] = input[i * scale + offset]; + } +} + +template +static void scale_output_assign(const Span input, + const int scale, + const int offset, + const MutableSpan &r_output) +{ + for (const int i : IndexRange(input.size())) { + r_output[i * scale + offset] = input[i]; + } +} + +template +static void copy_attributes(const Spline &input_spline, Spline &output_spline, CopyFn copy_fn) +{ + input_spline.attributes.foreach_attribute( + [&](StringRefNull name, const AttributeMetaData &meta_data) { + std::optional src = input_spline.attributes.get_for_read(name); + BLI_assert(src); + if (!output_spline.attributes.create(name, meta_data.data_type)) { + BLI_assert_unreachable(); + return false; + } + std::optional dst = output_spline.attributes.get_for_write(name); + if (!dst) { + BLI_assert_unreachable(); + return false; + } + + copy_fn(*src, *dst); + + return true; + }, + ATTR_DOMAIN_POINT); +} + +static SplinePtr convert_to_poly_spline(const Spline &input) +{ + std::unique_ptr output = std::make_unique(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr poly_to_nurbs(const Spline &input) +{ + std::unique_ptr output = std::make_unique(); + output->resize(input.positions().size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + Spline::copy_base_settings(input, *output); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes = input.attributes; + return output; +} + +static SplinePtr bezier_to_nurbs(const Spline &input) +{ + const BezierSpline &bezier_spline = static_cast(input); + std::unique_ptr output = std::make_unique(); + output->resize(input.size() * 3); + + scale_output_assign(bezier_spline.handle_positions_left(), 3, 0, output->positions()); + scale_output_assign(input.radii(), 3, 0, output->radii()); + scale_output_assign(input.tilts(), 3, 0, output->tilts()); + + scale_output_assign(bezier_spline.positions(), 3, 1, output->positions()); + scale_output_assign(input.radii(), 3, 1, output->radii()); + scale_output_assign(input.tilts(), 3, 1, output->tilts()); + + scale_output_assign(bezier_spline.handle_positions_right(), 3, 2, output->positions()); + scale_output_assign(input.radii(), 3, 2, output->radii()); + scale_output_assign(input.tilts(), 3, 2, output->tilts()); + + Spline::copy_base_settings(input, *output); + output->weights().fill(1.0f); + output->set_resolution(12); + output->set_order(4); + output->set_cyclic(input.is_cyclic()); + output->knots_mode = NURBSpline::KnotsMode::Bezier; + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_output_assign(src.typed(), 3, 0, dst.typed()); + scale_output_assign(src.typed(), 3, 1, dst.typed()); + scale_output_assign(src.typed(), 3, 2, dst.typed()); + }); + }); + return output; +} + +static SplinePtr poly_to_bezier(const Spline &input) +{ + std::unique_ptr output = std::make_unique(); + output->resize(input.size()); + output->positions().copy_from(input.positions()); + output->radii().copy_from(input.radii()); + output->tilts().copy_from(input.tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Vector); + output->handle_types_right().fill(BezierSpline::HandleType::Vector); + output->set_resolution(12); + Spline::copy_base_settings(input, *output); + output->attributes = input.attributes; + return output; +} + +static SplinePtr nurbs_to_bezier(const Spline &input) +{ + const NURBSpline &nurbs_spline = static_cast(input); + std::unique_ptr output = std::make_unique(); + output->resize(input.size() / 3); + scale_input_assign(input.positions(), 3, 1, output->positions()); + scale_input_assign(input.positions(), 3, 0, output->handle_positions_left()); + scale_input_assign(input.positions(), 3, 2, output->handle_positions_right()); + scale_input_assign(input.radii(), 3, 2, output->radii()); + scale_input_assign(input.tilts(), 3, 2, output->tilts()); + output->handle_types_left().fill(BezierSpline::HandleType::Align); + output->handle_types_right().fill(BezierSpline::HandleType::Align); + output->set_resolution(nurbs_spline.resolution()); + Spline::copy_base_settings(input, *output); + output->attributes.reallocate(output->size()); + copy_attributes(input, *output, [](GSpan src, GMutableSpan dst) { + attribute_math::convert_to_static_type(src.type(), [&](auto dummy) { + using T = decltype(dummy); + scale_input_assign(src.typed(), 3, 1, dst.typed()); + }); + }); + return output; +} + +static SplinePtr convert_to_bezier(const Spline &input, GeoNodeExecParams params) +{ + switch (input.type()) { + case Spline::Type::Bezier: + return input.copy(); + case Spline::Type::Poly: + return poly_to_bezier(input); + case Spline::Type::NURBS: + if (input.size() < 6) { + params.error_message_add( + NodeWarningType::Info, + TIP_("NURBS must have minimum of 6 points for Bezier Conversion")); + return input.copy(); + } + else { + if (input.size() % 3 != 0) { + params.error_message_add(NodeWarningType::Info, + TIP_("NURBS must have multiples of 3 points for full Bezier " + "conversion, curve truncated")); + } + return nurbs_to_bezier(input); + } + } + BLI_assert_unreachable(); + return {}; +} + +static SplinePtr convert_to_nurbs(const Spline &input) +{ + switch (input.type()) { + case Spline::Type::NURBS: + return input.copy(); + case Spline::Type::Bezier: + return bezier_to_nurbs(input); + case Spline::Type::Poly: + return poly_to_nurbs(input); + } + BLI_assert_unreachable(); + return {}; +} + +static void geo_node_curve_spline_type_exec(GeoNodeExecParams params) +{ + const NodeGeometryCurveSplineType *storage = + (const NodeGeometryCurveSplineType *)params.node().storage; + const GeometryNodeSplineType output_type = (const GeometryNodeSplineType)storage->spline_type; + + GeometrySet geometry_set = params.extract_input("Curve"); + geometry_set = bke::geometry_set_realize_instances(geometry_set); + if (!geometry_set.has_curve()) { + params.set_output("Curve", geometry_set); + return; + } + + const CurveComponent *curve_component = geometry_set.get_component_for_read(); + const CurveEval &curve = *curve_component->get_for_read(); + + const std::string selection_name = params.extract_input("Selection"); + GVArray_Typed selection = curve_component->attribute_get_for_read( + selection_name, ATTR_DOMAIN_CURVE, true); + + std::unique_ptr new_curve = std::make_unique(); + for (const int i : curve.splines().index_range()) { + if (selection[i]) { + switch (output_type) { + case GEO_NODE_SPLINE_TYPE_POLY: + new_curve->add_spline(convert_to_poly_spline(*curve.splines()[i])); + break; + case GEO_NODE_SPLINE_TYPE_BEZIER: + new_curve->add_spline(convert_to_bezier(*curve.splines()[i], params)); + break; + case GEO_NODE_SPLINE_TYPE_NURBS: + new_curve->add_spline(convert_to_nurbs(*curve.splines()[i])); + break; + } + } + else { + new_curve->add_spline(curve.splines()[i]->copy()); + } + } + + new_curve->attributes = curve.attributes; + params.set_output("Curve", GeometrySet::create_with_curve(new_curve.release())); +} + +} // namespace blender::nodes + +void register_node_type_geo_curve_spline_type() +{ + static bNodeType ntype; + geo_node_type_base( + &ntype, GEO_NODE_CURVE_SPLINE_TYPE, "Set Spline Type", NODE_CLASS_GEOMETRY, 0); + node_type_socket_templates( + &ntype, geo_node_curve_spline_type_in, geo_node_curve_spline_type_out); + ntype.geometry_node_execute = blender::nodes::geo_node_curve_spline_type_exec; + node_type_init(&ntype, blender::nodes::geo_node_curve_spline_type_init); + node_type_storage(&ntype, + "NodeGeometryCurveSplineType", + node_free_standard_storage, + node_copy_standard_storage); + ntype.draw_buttons = geo_node_curve_spline_type_layout; + + nodeRegisterType(&ntype); +} -- cgit v1.2.3