Blender V4.3
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"
9#include "BKE_curves.hh"
10
11#include "BLI_array_utils.hh"
12
14
15#include "BLI_sort.hh"
16#include "BLI_task.hh"
17
18#include "GEO_randomize.hh"
19
20#include "BKE_geometry_set.hh"
21
23
25{
26 b.add_input<decl::Geometry>("Points")
28 .description("Points to generate curves from");
29 b.add_input<decl::Int>("Curve Group ID")
30 .field_on_all()
31 .hide_value()
32 .description(
33 "A curve is created for every distinct group ID. All points with the same ID are put "
34 "into the same curve");
35 b.add_input<decl::Float>("Weight").field_on_all().hide_value().description(
36 "Determines the order of points in each curve");
37
38 b.add_output<decl::Geometry>("Curves").propagate_all();
39}
40
41static void grouped_sort(const OffsetIndices<int> offsets,
42 const Span<float> weights,
43 MutableSpan<int> indices)
44{
45 const auto comparator = [&](const int index_a, const int index_b) {
46 const float weight_a = weights[index_a];
47 const float weight_b = weights[index_b];
48 if (UNLIKELY(weight_a == weight_b)) {
49 /* Approach to make it stable. */
50 return index_a < index_b;
51 }
52 return weight_a < weight_b;
53 };
54
55 threading::parallel_for(offsets.index_range(), 250, [&](const IndexRange range) {
56 for (const int group_index : range) {
57 MutableSpan<int> group = indices.slice(offsets[group_index]);
58 parallel_sort(group.begin(), group.end(), comparator);
59 }
60 });
61}
62
63static void find_points_by_group_index(const Span<int> indices_of_curves,
64 MutableSpan<int> r_offsets,
65 MutableSpan<int> r_indices)
66{
67 offset_indices::build_reverse_offsets(indices_of_curves, r_offsets);
68 Array<int> counts(r_offsets.size(), 0);
69
70 for (const int64_t index : indices_of_curves.index_range()) {
71 const int curve_index = indices_of_curves[index];
72 r_indices[r_offsets[curve_index] + counts[curve_index]] = int(index);
73 counts[curve_index]++;
74 }
75}
76
77static int identifiers_to_indices(MutableSpan<int> r_identifiers_to_indices)
78{
79 const VectorSet<int> deduplicated_groups(r_identifiers_to_indices);
80 threading::parallel_for(
81 r_identifiers_to_indices.index_range(), 2048, [&](const IndexRange range) {
82 for (int &value : r_identifiers_to_indices.slice(range)) {
83 value = deduplicated_groups.index_of(value);
84 }
85 });
86 return deduplicated_groups.size();
87}
88
90 const VArray<float> &weights_varray,
91 const AttributeFilter &attribute_filter)
92{
93 const int domain_size = weights_varray.size();
94 Curves *curves_id = bke::curves_new_nomain_single(domain_size, CURVE_TYPE_POLY);
95 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
96 if (weights_varray.is_single()) {
97 bke::copy_attributes(attributes,
98 AttrDomain::Point,
99 AttrDomain::Point,
100 attribute_filter,
101 curves.attributes_for_write());
102 return curves_id;
103 }
104 Array<int> indices(domain_size);
105 array_utils::fill_index_range<int>(indices);
106 const VArraySpan<float> weights(weights_varray);
107 grouped_sort(OffsetIndices<int>({0, domain_size}), weights, indices);
108 bke::gather_attributes(attributes,
109 AttrDomain::Point,
110 AttrDomain::Point,
111 attribute_filter,
112 indices,
113 curves.attributes_for_write());
114 return curves_id;
115}
116
118 const Field<int> &group_id_field,
119 const Field<float> &weight_field,
120 const AttributeFilter &attribute_filter)
121{
122 const int domain_size = points.totpoint;
123 if (domain_size == 0) {
124 return nullptr;
125 }
126
127 const bke::PointCloudFieldContext context(points);
128 fn::FieldEvaluator evaluator(context, domain_size);
129 evaluator.add(group_id_field);
130 evaluator.add(weight_field);
131 evaluator.evaluate();
132
133 const VArray<int> group_ids_varray = evaluator.get_evaluated<int>(0);
134 const VArray<float> weights_varray = evaluator.get_evaluated<float>(1);
135
136 if (group_ids_varray.is_single()) {
137 return curve_from_points(points.attributes(), weights_varray, attribute_filter);
138 }
139
140 Array<int> group_ids(domain_size);
141 group_ids_varray.materialize(group_ids.as_mutable_span());
142 const int total_curves = identifiers_to_indices(group_ids);
143 if (total_curves == 1) {
144 return curve_from_points(points.attributes(), weights_varray, attribute_filter);
145 }
146
147 Curves *curves_id = bke::curves_new_nomain(domain_size, total_curves);
148 bke::CurvesGeometry &curves = curves_id->geometry.wrap();
149 curves.fill_curve_types(CURVE_TYPE_POLY);
150 MutableSpan<int> offset = curves.offsets_for_write();
151 offset.fill(0);
152
153 Array<int> indices(domain_size);
154 find_points_by_group_index(group_ids, offset, indices.as_mutable_span());
155
156 if (!weights_varray.is_single()) {
157 const VArraySpan<float> weights(weights_varray);
158 grouped_sort(OffsetIndices<int>(offset), weights, indices);
159 }
160 bke::gather_attributes(points.attributes(),
161 AttrDomain::Point,
162 AttrDomain::Point,
163 attribute_filter,
164 indices,
165 curves.attributes_for_write());
166
167 geometry::debug_randomize_curve_order(&curves);
168 return curves_id;
169}
170
172{
173 GeometrySet geometry_set = params.extract_input<GeometrySet>("Points");
174 const Field<int> group_id_field = params.extract_input<Field<int>>("Curve Group ID");
175 const Field<float> weight_field = params.extract_input<Field<float>>("Weight");
176
177 const NodeAttributeFilter attribute_filter = params.get_attribute_filter("Curves");
178 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
179 geometry_set.replace_curves(nullptr);
180 if (const PointCloud *points = geometry_set.get_pointcloud()) {
181 Curves *curves_id = curves_from_points(
182 *points, group_id_field, weight_field, attribute_filter);
183 geometry_set.replace_curves(curves_id);
184 }
185 geometry_set.keep_only_during_modify({GeometryComponent::Type::Curve});
186 });
187
188 params.set_output("Curves", std::move(geometry_set));
189}
190
191static void node_register()
192{
193 static blender::bke::bNodeType ntype;
194
195 geo_node_type_base(&ntype, GEO_NODE_POINTS_TO_CURVES, "Points to Curves", NODE_CLASS_GEOMETRY);
197 ntype.declare = node_declare;
199}
200NOD_REGISTER_NODE(node_register)
201
202} // namespace blender::nodes::node_geo_points_to_curves_cc
Low-level operations for curves.
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define UNLIKELY(x)
@ CURVE_TYPE_POLY
#define NOD_REGISTER_NODE(REGISTER_FUNC)
MutableSpan< T > as_mutable_span()
Definition BLI_array.hh:237
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr void fill(const T &value) const
Definition BLI_span.hh:518
constexpr IndexRange index_range() const
Definition BLI_span.hh:671
constexpr IndexRange index_range() const
Definition BLI_span.hh:402
void materialize(MutableSpan< T > r_span) const
int add(GField field, GVArray *varray_ptr)
Definition field.cc:756
const GVArray & get_evaluated(const int field_index) const
Definition FN_field.hh:450
local_group_size(16, 16) .push_constant(Type b
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
static ushort indices[]
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
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 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:95
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
__int64 int64_t
Definition stdint.h:89
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:218
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:339
NodeDeclareFunction declare
Definition BKE_node.hh:347