Blender V4.5
node_geo_points_to_curves.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
6
7#include "BKE_attribute.hh"
8#include "BKE_curves.hh"
9
10#include "BLI_array_utils.hh"
11
13
14#include "BLI_sort.hh"
15#include "BLI_task.hh"
16
17#include "GEO_randomize.hh"
18
19#include "BKE_geometry_set.hh"
20
22
24{
25 b.add_input<decl::Geometry>("Points")
26 .supported_type(GeometryComponent::Type::PointCloud)
27 .description("Points to generate curves from");
28 b.add_input<decl::Int>("Curve Group ID")
29 .field_on_all()
30 .hide_value()
31 .description(
32 "A curve is created for every distinct group ID. All points with the same ID are put "
33 "into the same curve");
34 b.add_input<decl::Float>("Weight").field_on_all().hide_value().description(
35 "Determines the order of points in each curve");
36
37 b.add_output<decl::Geometry>("Curves").propagate_all();
38}
39
40static void grouped_sort(const OffsetIndices<int> offsets,
41 const Span<float> weights,
43{
44 const auto comparator = [&](const int index_a, const int index_b) {
45 const float weight_a = weights[index_a];
46 const float weight_b = weights[index_b];
47 if (UNLIKELY(weight_a == weight_b)) {
48 /* Approach to make it stable. */
49 return index_a < index_b;
50 }
51 return weight_a < weight_b;
52 };
53
54 threading::parallel_for(offsets.index_range(), 250, [&](const IndexRange range) {
55 for (const int group_index : range) {
56 MutableSpan<int> group = indices.slice(offsets[group_index]);
57 parallel_sort(group.begin(), group.end(), comparator);
58 }
59 });
60}
61
62static void find_points_by_group_index(const Span<int> indices_of_curves,
63 MutableSpan<int> r_offsets,
64 MutableSpan<int> r_indices)
65{
66 offset_indices::build_reverse_offsets(indices_of_curves, r_offsets);
67 Array<int> counts(r_offsets.size(), 0);
68
69 for (const int64_t index : indices_of_curves.index_range()) {
70 const int curve_index = indices_of_curves[index];
71 r_indices[r_offsets[curve_index] + counts[curve_index]] = int(index);
72 counts[curve_index]++;
73 }
74}
75
76static int identifiers_to_indices(MutableSpan<int> r_identifiers_to_indices)
77{
78 const VectorSet<int> deduplicated_groups(r_identifiers_to_indices);
80 r_identifiers_to_indices.index_range(), 2048, [&](const IndexRange range) {
81 for (int &value : r_identifiers_to_indices.slice(range)) {
82 value = deduplicated_groups.index_of(value);
83 }
84 });
85 return deduplicated_groups.size();
86}
87
89 const VArray<float> &weights_varray,
90 const AttributeFilter &attribute_filter)
91{
92 const int domain_size = weights_varray.size();
93 Curves *curves_id = bke::curves_new_nomain_single(domain_size, CURVE_TYPE_POLY);
94 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
95 if (weights_varray.is_single()) {
96 bke::copy_attributes(attributes,
97 AttrDomain::Point,
98 AttrDomain::Point,
99 attribute_filter,
100 curves.attributes_for_write());
101 return curves_id;
102 }
103 Array<int> indices(domain_size);
105 const VArraySpan<float> weights(weights_varray);
106 grouped_sort(OffsetIndices<int>({0, domain_size}), weights, indices);
107 bke::gather_attributes(attributes,
108 AttrDomain::Point,
109 AttrDomain::Point,
110 attribute_filter,
111 indices,
112 curves.attributes_for_write());
113 return curves_id;
114}
115
117 const Field<int> &group_id_field,
118 const Field<float> &weight_field,
119 const AttributeFilter &attribute_filter)
120{
121 const int domain_size = points.totpoint;
122 if (domain_size == 0) {
123 return nullptr;
124 }
125
126 const bke::PointCloudFieldContext context(points);
127 fn::FieldEvaluator evaluator(context, domain_size);
128 evaluator.add(group_id_field);
129 evaluator.add(weight_field);
130 evaluator.evaluate();
131
132 const VArray<int> group_ids_varray = evaluator.get_evaluated<int>(0);
133 const VArray<float> weights_varray = evaluator.get_evaluated<float>(1);
134
135 if (group_ids_varray.is_single()) {
136 return curve_from_points(points.attributes(), weights_varray, attribute_filter);
137 }
138
139 Array<int> group_ids(domain_size);
140 group_ids_varray.materialize(group_ids.as_mutable_span());
141 const int total_curves = identifiers_to_indices(group_ids);
142 if (total_curves == 1) {
143 return curve_from_points(points.attributes(), weights_varray, attribute_filter);
144 }
145
146 Curves *curves_id = bke::curves_new_nomain(domain_size, total_curves);
147 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
149 MutableSpan<int> offset = curves.offsets_for_write();
150 offset.fill(0);
151
152 Array<int> indices(domain_size);
153 find_points_by_group_index(group_ids, offset, indices.as_mutable_span());
154
155 if (!weights_varray.is_single()) {
156 const VArraySpan<float> weights(weights_varray);
157 grouped_sort(OffsetIndices<int>(offset), weights, indices);
158 }
160 AttrDomain::Point,
161 AttrDomain::Point,
162 attribute_filter,
163 indices,
164 curves.attributes_for_write());
165
167 return curves_id;
168}
169
171{
172 GeometrySet geometry_set = params.extract_input<GeometrySet>("Points");
173 const Field<int> group_id_field = params.extract_input<Field<int>>("Curve Group ID");
174 const Field<float> weight_field = params.extract_input<Field<float>>("Weight");
175
176 const NodeAttributeFilter attribute_filter = params.get_attribute_filter("Curves");
177 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
178 geometry_set.replace_curves(nullptr);
179 if (const PointCloud *points = geometry_set.get_pointcloud()) {
180 Curves *curves_id = curves_from_points(
181 *points, group_id_field, weight_field, attribute_filter);
182 geometry_set.replace_curves(curves_id);
183 }
184 geometry_set.keep_only_during_modify({GeometryComponent::Type::Curve});
185 });
186
187 params.set_output("Curves", std::move(geometry_set));
188}
189
190static void node_register()
191{
192 static blender::bke::bNodeType ntype;
193
194 geo_node_type_base(&ntype, "GeometryNodePointsToCurves", GEO_NODE_POINTS_TO_CURVES);
195 ntype.ui_name = "Points to Curves";
196 ntype.ui_description = "Split all points to curve by its group ID and reorder by weight";
197 ntype.enum_name_legacy = "POINTS_TO_CURVES";
200 ntype.declare = node_declare;
202}
203NOD_REGISTER_NODE(node_register)
204
205} // namespace blender::nodes::node_geo_points_to_curves_cc
Low-level operations for curves.
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define GEO_NODE_POINTS_TO_CURVES
#define UNLIKELY(x)
@ CURVE_TYPE_POLY
#define NOD_REGISTER_NODE(REGISTER_FUNC)
long long int int64_t
AttributeSet attributes
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr IndexRange index_range() const
Definition BLI_span.hh:670
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
void materialize(MutableSpan< T > r_span) const
MutableAttributeAccessor attributes_for_write()
void fill_curve_types(CurveType type)
MutableSpan< int > offsets_for_write()
int add(GField field, GVArray *varray_ptr)
Definition field.cc:751
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:448
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void fill_index_range(MutableSpan< T > span, const T start=0)
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void copy_attributes(const AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, MutableAttributeAccessor dst_attributes)
void gather_attributes(AttributeAccessor src_attributes, AttrDomain src_domain, AttrDomain dst_domain, const AttributeFilter &attribute_filter, const IndexMask &selection, MutableAttributeAccessor dst_attributes)
Curves * curves_new_nomain_single(int points_num, CurveType type)
Curves * curves_new_nomain(int points_num, int curves_num)
void debug_randomize_curve_order(bke::CurvesGeometry *curves)
Definition randomize.cc:193
static void node_geo_exec(GeoNodeExecParams params)
static Curves * curve_from_points(const AttributeAccessor attributes, const VArray< float > &weights_varray, const AttributeFilter &attribute_filter)
static void find_points_by_group_index(const Span< int > indices_of_curves, MutableSpan< int > r_offsets, MutableSpan< int > r_indices)
static void grouped_sort(const OffsetIndices< int > offsets, const Span< float > weights, MutableSpan< int > indices)
static void node_declare(NodeDeclarationBuilder &b)
static Curves * curves_from_points(const PointCloud &points, const Field< int > &group_id_field, const Field< float > &weight_field, const AttributeFilter &attribute_filter)
static int identifiers_to_indices(MutableSpan< int > r_identifiers_to_indices)
void build_reverse_offsets(Span< int > indices, MutableSpan< int > offsets)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
CurvesGeometry geometry
void keep_only_during_modify(Span< GeometryComponent::Type > component_types)
void replace_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
const PointCloud * get_pointcloud() const
void modify_geometry_sets(ForeachSubGeometryCallback callback)
Defines a node type.
Definition BKE_node.hh:226
std::string ui_description
Definition BKE_node.hh:232
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
NodeDeclareFunction declare
Definition BKE_node.hh:355