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
This commit is contained in:
Johnny Matthews 2021-08-03 23:14:03 -04:00 committed by Hans Goudey
parent 26f1a5e2c8
commit 0f45576590
9 changed files with 341 additions and 0 deletions

View File

@ -510,6 +510,7 @@ geometry_node_categories = [
NodeItem("GeometryNodeCurveTrim"),
NodeItem("GeometryNodeCurveLength"),
NodeItem("GeometryNodeCurveReverse"),
NodeItem("GeometryNodeCurveSplineType"),
NodeItem("GeometryNodeCurveSetHandles"),
]),
GeometryNodeCategory("GEO_PRIMITIVES_CURVE", "Curve Primitives", items=[

View File

@ -1475,6 +1475,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_PRIMITIVE_QUADRILATERAL 1070
#define GEO_NODE_CURVE_TRIM 1071
#define GEO_NODE_CURVE_SET_HANDLES 1072
#define GEO_NODE_CURVE_SPLINE_TYPE 1073
/** \} */

View File

@ -5149,6 +5149,7 @@ static void registerGeometryNodes()
register_node_type_geo_curve_resample();
register_node_type_geo_curve_reverse();
register_node_type_geo_curve_set_handles();
register_node_type_geo_curve_spline_type();
register_node_type_geo_curve_subdivide();
register_node_type_geo_curve_to_mesh();
register_node_type_geo_curve_to_points();

View File

@ -1355,6 +1355,11 @@ typedef struct NodeSwitch {
uint8_t input_type;
} NodeSwitch;
typedef struct NodeGeometryCurveSplineType {
/* GeometryNodeSplineType. */
uint8_t spline_type;
} NodeGeometryCurveSplineType;
typedef struct NodeGeometryCurveSetHandles {
/* GeometryNodeCurveHandleType. */
uint8_t handle_type;
@ -1828,6 +1833,12 @@ typedef enum GeometryNodeBooleanOperation {
GEO_NODE_BOOLEAN_DIFFERENCE = 2,
} GeometryNodeBooleanOperation;
typedef enum GeometryNodeSplineType {
GEO_NODE_SPLINE_TYPE_BEZIER = 0,
GEO_NODE_SPLINE_TYPE_NURBS = 1,
GEO_NODE_SPLINE_TYPE_POLY = 2,
} GeometryNodeSplineType;
typedef enum GeometryNodeCurvePrimitiveCircleMode {
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_POINTS = 0,
GEO_NODE_CURVE_PRIMITIVE_CIRCLE_TYPE_RADIUS = 1

View File

@ -9441,6 +9441,23 @@ static void def_geo_attribute_vector_rotate(StructRNA *srna)
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_curve_spline_type(StructRNA *srna)
{
static const EnumPropertyItem type_items[] = {
{GEO_NODE_SPLINE_TYPE_BEZIER, "BEZIER", ICON_NONE, "Bezier", "Set the splines to Bezier"},
{GEO_NODE_SPLINE_TYPE_NURBS, "NURBS", ICON_NONE, "NURBS", "Set the splines to NURBS"},
{GEO_NODE_SPLINE_TYPE_POLY, "POLY", ICON_NONE, "Poly", "Set the splines to Poly"},
{0, NULL, 0, NULL, NULL}};
PropertyRNA *prop;
RNA_def_struct_sdna_from(srna, "NodeGeometryCurveSplineType", "storage");
prop = RNA_def_property(srna, "spline_type", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_sdna(prop, NULL, "spline_type");
RNA_def_property_enum_items(prop, type_items);
RNA_def_property_update(prop, NC_NODE | NA_EDITED, "rna_Node_socket_update");
}
static void def_geo_curve_set_handles(StructRNA *srna)
{
static const EnumPropertyItem type_items[] = {

View File

@ -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

View File

@ -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);

View File

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

View File

@ -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<class T>
static void scale_input_assign(const Span<T> input,
const int scale,
const int offset,
const MutableSpan<T> r_output)
{
for (const int i : IndexRange(r_output.size())) {
r_output[i] = input[i * scale + offset];
}
}
template<class T>
static void scale_output_assign(const Span<T> input,
const int scale,
const int offset,
const MutableSpan<T> &r_output)
{
for (const int i : IndexRange(input.size())) {
r_output[i * scale + offset] = input[i];
}
}
template<typename CopyFn>
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<GSpan> 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<GMutableSpan> 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<PolySpline> output = std::make_unique<PolySpline>();
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<NURBSpline> output = std::make_unique<NURBSpline>();
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<const BezierSpline &>(input);
std::unique_ptr<NURBSpline> output = std::make_unique<NURBSpline>();
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<T>(src.typed<T>(), 3, 0, dst.typed<T>());
scale_output_assign<T>(src.typed<T>(), 3, 1, dst.typed<T>());
scale_output_assign<T>(src.typed<T>(), 3, 2, dst.typed<T>());
});
});
return output;
}
static SplinePtr poly_to_bezier(const Spline &input)
{
std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>();
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<const NURBSpline &>(input);
std::unique_ptr<BezierSpline> output = std::make_unique<BezierSpline>();
output->resize(input.size() / 3);
scale_input_assign<float3>(input.positions(), 3, 1, output->positions());
scale_input_assign<float3>(input.positions(), 3, 0, output->handle_positions_left());
scale_input_assign<float3>(input.positions(), 3, 2, output->handle_positions_right());
scale_input_assign<float>(input.radii(), 3, 2, output->radii());
scale_input_assign<float>(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<T>(src.typed<T>(), 3, 1, dst.typed<T>());
});
});
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<GeometrySet>("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<CurveComponent>();
const CurveEval &curve = *curve_component->get_for_read();
const std::string selection_name = params.extract_input<std::string>("Selection");
GVArray_Typed<bool> selection = curve_component->attribute_get_for_read(
selection_name, ATTR_DOMAIN_CURVE, true);
std::unique_ptr<CurveEval> new_curve = std::make_unique<CurveEval>();
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);
}