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
This commit is contained in:
Johnny Matthews 2022-09-26 13:03:28 -05:00
parent 46cfd345e4
commit 8a6dc0fac7
Notes: blender-bot 2023-02-13 14:50:34 +01:00
Referenced by issue #100020, Curve and Mesh Topology Inputs
7 changed files with 180 additions and 0 deletions

View File

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

View File

@ -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
/** \} */

View File

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

View File

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

View File

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

View File

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

View File

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