Blender V4.5
node_geo_curve_to_points.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
5#include "BLI_math_matrix.hh"
6
8
9#include "BKE_customdata.hh"
10#include "BKE_grease_pencil.hh"
11#include "BKE_instances.hh"
12#include "BKE_pointcloud.hh"
13
16
17#include "NOD_rna_define.hh"
18
19#include "UI_interface.hh"
20#include "UI_resources.hh"
21
22#include "node_geometry_util.hh"
23
25
27
29{
30 b.add_input<decl::Geometry>("Curve").supported_type(
31 {GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil});
32 auto &count = b.add_input<decl::Int>("Count")
33 .default_value(10)
34 .min(2)
35 .max(100000)
36 .field_on_all()
37 .make_available([](bNode &node) {
38 node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT;
39 });
40 auto &length = b.add_input<decl::Float>("Length")
41 .default_value(0.1f)
42 .min(0.001f)
43 .subtype(PROP_DISTANCE)
44 .field_on_all()
45 .make_available([](bNode &node) {
46 node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_LENGTH;
47 });
48 b.add_output<decl::Geometry>("Points").propagate_all();
49 b.add_output<decl::Vector>("Tangent").field_on_all();
50 b.add_output<decl::Vector>("Normal").field_on_all();
51 b.add_output<decl::Rotation>("Rotation").field_on_all();
52
53 const bNode *node = b.node_or_null();
54 if (node != nullptr) {
55 const NodeGeometryCurveToPoints &storage = node_storage(*node);
57
58 count.available(mode == GEO_NODE_CURVE_RESAMPLE_COUNT);
59 length.available(mode == GEO_NODE_CURVE_RESAMPLE_LENGTH);
60 }
61}
62
63static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
64{
65 layout->prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
66}
67
68static void node_init(bNodeTree * /*tree*/, bNode *node)
69{
71
73 node->storage = data;
74}
75
76static void fill_rotation_attribute(const Span<float3> tangents,
79{
80 threading::parallel_for(IndexRange(rotations.size()), 512, [&](IndexRange range) {
81 for (const int i : range) {
82 rotations[i] = math::to_quaternion(
83 math::from_orthonormal_axes<float4x4>(normals[i], tangents[i]));
84 }
85 });
86}
87
88static void copy_curve_domain_attributes(const AttributeAccessor curve_attributes,
89 const AttributeFilter &attribute_filter,
90 MutableAttributeAccessor point_attributes)
91{
92 curve_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
93 if (iter.is_builtin) {
94 return;
95 }
96 if (iter.domain != AttrDomain::Curve) {
97 return;
98 }
99 if (attribute_filter.allow_skip(iter.name)) {
100 return;
101 }
102 if (iter.data_type == CD_PROP_STRING) {
103 return;
104 }
105 point_attributes.add(iter.name,
106 AttrDomain::Point,
107 iter.data_type,
108 bke::AttributeInitVArray(*iter.get(AttrDomain::Point)));
109 });
110}
111
113 const bke::CurvesGeometry &curves,
114 const AttributeFilter &attribute_filter,
115 const geometry::ResampleCurvesOutputAttributeIDs &resample_attributes,
116 const std::optional<StringRef> &rotation_id)
117{
118 const AttributeAccessor curve_attributes = curves.attributes();
119
121 MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
122
123 const bke::AttributeFilterFromFunc filter = [&](const StringRef name) {
124 if (ELEM(name, resample_attributes.tangent_id, resample_attributes.normal_id, rotation_id)) {
126 }
127 if (attribute_filter.allow_skip(name)) {
129 }
130 if (curve_attributes.is_builtin(name) && !point_attributes.is_builtin(name)) {
132 }
134 };
135
139 filter,
140 pointcloud->attributes_for_write());
141
142 copy_curve_domain_attributes(curve_attributes, filter, point_attributes);
143
144 if (rotation_id) {
145 const VArraySpan tangents = *curve_attributes.lookup<float3>(*resample_attributes.tangent_id,
146 AttrDomain::Point);
147 const VArraySpan normals = *curve_attributes.lookup<float3>(*resample_attributes.normal_id,
148 AttrDomain::Point);
149 SpanAttributeWriter rotations =
151 AttrDomain::Point);
152 fill_rotation_attribute(tangents, normals, rotations.span);
153 rotations.finish();
154 }
155
156 return pointcloud;
157}
158
159static void layer_pointclouds_to_instances(const Span<PointCloud *> pointcloud_by_layer,
160 const AttributeFilter &attribute_filter,
162{
163 if (!pointcloud_by_layer.is_empty()) {
164 bke::Instances *instances = new bke::Instances();
165 for (PointCloud *pointcloud : pointcloud_by_layer) {
166 if (!pointcloud) {
167 /* Add an empty reference so the number of layers and instances match.
168 * This makes it easy to reconstruct the layers afterwards and keep their
169 * attributes. */
170 const int handle = instances->add_reference(bke::InstanceReference());
171 instances->add_instance(handle, float4x4::identity());
172 continue;
173 }
174 GeometrySet temp_set = GeometrySet::from_pointcloud(pointcloud);
175 const int handle = instances->add_reference(bke::InstanceReference{temp_set});
176 instances->add_instance(handle, float4x4::identity());
177 }
178
179 bke::copy_attributes(geometry.get_grease_pencil()->attributes(),
182 attribute_filter,
183 instances->attributes_for_write());
184 InstancesComponent &dst_component = geometry.get_component_for_write<InstancesComponent>();
186 {GeometrySet::from_instances(dst_component.release()),
187 GeometrySet::from_instances(instances)},
188 attribute_filter);
189 dst_component.replace(new_instances.get_component_for_write<InstancesComponent>().release());
190 }
191}
192
194{
195 const NodeGeometryCurveToPoints &storage = node_storage(params.node());
197 GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
198
200
201 std::optional<std::string> rotation_anonymous_id =
202 params.get_output_anonymous_attribute_id_if_needed("Rotation");
203 const bool need_tangent_and_normal = bool(rotation_anonymous_id);
204 std::optional<std::string> tangent_anonymous_id =
205 params.get_output_anonymous_attribute_id_if_needed("Tangent", need_tangent_and_normal);
206 std::optional<std::string> normal_anonymous_id =
207 params.get_output_anonymous_attribute_id_if_needed("Normal", need_tangent_and_normal);
208
210 resample_attributes.tangent_id = tangent_anonymous_id;
211 resample_attributes.normal_id = normal_anonymous_id;
212 const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Points");
213
214 switch (mode) {
216 const Field<int> count = params.extract_input<Field<int>>("Count");
217 geometry_set.modify_geometry_sets([&](GeometrySet &geometry) {
218 if (const Curves *src_curves_id = geometry.get_curves()) {
220 src_curves_id->geometry.wrap(),
221 bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
223 count,
224 resample_attributes);
225 PointCloud *pointcloud = curves_to_points(
226 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
227 geometry.replace_pointcloud(pointcloud);
228 }
229 if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
230 Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
231 for (const int layer_index : grease_pencil->layers().index_range()) {
232 const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
233 grease_pencil->layer(layer_index));
234 if (drawing == nullptr) {
235 continue;
236 }
238 drawing->strokes(),
239 bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
241 count,
242 resample_attributes);
243 pointcloud_by_layer[layer_index] = curves_to_points(
244 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
245 }
246 layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
247 }
248 geometry.keep_only_during_modify({bke::GeometryComponent::Type::PointCloud});
249 });
250 break;
251 }
253 const Field<float> length = params.extract_input<Field<float>>("Length");
254 geometry_set.modify_geometry_sets([&](GeometrySet &geometry) {
255 if (const Curves *src_curves_id = geometry.get_curves()) {
257 src_curves_id->geometry.wrap(),
258 bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
260 length,
261 resample_attributes);
262 PointCloud *pointcloud = curves_to_points(
263 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
264 geometry.replace_pointcloud(pointcloud);
265 }
266 if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
267 Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
268 for (const int layer_index : grease_pencil->layers().index_range()) {
269 const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
270 grease_pencil->layer(layer_index));
271 if (drawing == nullptr) {
272 continue;
273 }
275 drawing->strokes(),
276 bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
278 length,
279 resample_attributes);
280 pointcloud_by_layer[layer_index] = curves_to_points(
281 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
282 }
283 layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
284 }
285 geometry.keep_only_during_modify({bke::GeometryComponent::Type::PointCloud});
286 });
287 break;
288 }
290 geometry_set.modify_geometry_sets([&](GeometrySet &geometry) {
291 if (const Curves *src_curves_id = geometry.get_curves()) {
293 src_curves_id->geometry.wrap(),
294 bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
296 resample_attributes);
297 PointCloud *pointcloud = curves_to_points(
298 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
299 geometry.replace_pointcloud(pointcloud);
300 geometry.replace_curves(nullptr);
301 }
302 if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
303 Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
304 for (const int layer_index : grease_pencil->layers().index_range()) {
305 const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
306 grease_pencil->layer(layer_index));
307 if (drawing == nullptr) {
308 continue;
309 }
311 drawing->strokes(),
312 bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
314 resample_attributes);
315 pointcloud_by_layer[layer_index] = curves_to_points(
316 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
317 }
318 layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
319 }
320 geometry.keep_only_during_modify({bke::GeometryComponent::Type::PointCloud});
321 });
322 break;
323 }
324 }
325
326 params.set_output("Points", std::move(geometry_set));
327}
328
329static void node_rna(StructRNA *srna)
330{
331 static EnumPropertyItem mode_items[] = {
333 "EVALUATED",
334 0,
335 "Evaluated",
336 "Create points from the curve's evaluated points, based on the resolution attribute for "
337 "NURBS and Bézier splines"},
339 "COUNT",
340 0,
341 "Count",
342 "Sample each spline by evenly distributing the specified number of points"},
344 "LENGTH",
345 0,
346 "Length",
347 "Sample each spline by splitting it into segments with the specified length"},
348 {0, nullptr, 0, nullptr, nullptr},
349 };
350
352 "mode",
353 "Mode",
354 "How to generate points from the input curve",
355 mode_items,
358}
359
360static void node_register()
361{
362 static blender::bke::bNodeType ntype;
363
364 geo_node_type_base(&ntype, "GeometryNodeCurveToPoints", GEO_NODE_CURVE_TO_POINTS);
365 ntype.ui_name = "Curve to Points";
366 ntype.ui_description = "Generate a point cloud by sampling positions along curves";
367 ntype.enum_name_legacy = "CURVE_TO_POINTS";
369 ntype.declare = node_declare;
373 ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage);
374 ntype.initfunc = node_init;
376
377 node_rna(ntype.rna_ext.srna);
378}
379NOD_REGISTER_NODE(node_register)
380
381} // namespace blender::nodes::node_geo_curve_to_points_cc
CustomData interface, see also DNA_customdata_types.h.
Low-level operations for grease pencil.
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define GEO_NODE_CURVE_TO_POINTS
General operations for point clouds.
#define ELEM(...)
@ CD_PROP_STRING
GeometryNodeCurveResampleMode
@ GEO_NODE_CURVE_RESAMPLE_LENGTH
@ GEO_NODE_CURVE_RESAMPLE_EVALUATED
@ GEO_NODE_CURVE_RESAMPLE_COUNT
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
@ PROP_DISTANCE
Definition RNA_types.hh:244
#define UI_ITEM_NONE
BMesh const char void * data
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr bool is_empty() const
Definition BLI_span.hh:260
bool is_builtin(const StringRef attribute_id) const
void foreach_attribute(const FunctionRef< void(const AttributeIter &)> fn) const
GAttributeReader lookup(const StringRef attribute_id) const
GAttributeReader get() const
AttributeAccessor attributes() const
void replace(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
int add_reference(const InstanceReference &reference)
Definition instances.cc:271
void add_instance(int instance_handle, const float4x4 &transform)
Definition instances.cc:203
bke::MutableAttributeAccessor attributes_for_write()
Definition instances.cc:68
bool add(const StringRef attribute_id, const AttrDomain domain, const eCustomDataType data_type, const AttributeInit &initializer)
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, eCustomDataType data_type)
const bke::CurvesGeometry & strokes() const
static void remember_deformed_positions_if_necessary(GeometrySet &geometry)
static float normals[][3]
#define filter
float length(VecOp< float, D >) RET
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int count
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
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)
PointCloud * pointcloud_new_no_attributes(int totpoint)
void node_type_storage(bNodeType &ntype, std::optional< StringRefNull > storagename, void(*freefunc)(bNode *node), void(*copyfunc)(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node))
Definition node.cc:5603
GField make_constant_field(const CPPType &type, const void *value)
Definition field.cc:528
CurvesGeometry resample_to_count(const CurvesGeometry &src_curves, const IndexMask &selection, const VArray< int > &counts, const ResampleCurvesOutputAttributeIDs &output_ids={})
CurvesGeometry resample_to_evaluated(const CurvesGeometry &src_curves, const IndexMask &selection, const ResampleCurvesOutputAttributeIDs &output_ids={})
bke::GeometrySet join_geometries(Span< bke::GeometrySet > geometries, const bke::AttributeFilter &attribute_filter, const std::optional< Span< bke::GeometryComponent::Type > > &component_types_to_join=std::nullopt)
CurvesGeometry resample_to_length(const CurvesGeometry &src_curves, const IndexMask &selection, const VArray< float > &sample_lengths, const ResampleCurvesOutputAttributeIDs &output_ids={}, bool keep_last_segment=false)
QuaternionBase< float > Quaternion
static PointCloud * curves_to_points(const bke::CurvesGeometry &curves, const AttributeFilter &attribute_filter, const geometry::ResampleCurvesOutputAttributeIDs &resample_attributes, const std::optional< StringRef > &rotation_id)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
static void copy_curve_domain_attributes(const AttributeAccessor curve_attributes, const AttributeFilter &attribute_filter, MutableAttributeAccessor point_attributes)
static void node_init(bNodeTree *, bNode *node)
static void fill_rotation_attribute(const Span< float3 > tangents, const Span< float3 > normals, MutableSpan< math::Quaternion > rotations)
static void layer_pointclouds_to_instances(const Span< PointCloud * > pointcloud_by_layer, const AttributeFilter &attribute_filter, GeometrySet &geometry)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
PropertyRNA * RNA_def_node_enum(StructRNA *srna, const char *identifier, const char *ui_name, const char *ui_description, const EnumPropertyItem *static_items, const EnumRNAAccessors accessors, std::optional< int > default_value, const EnumPropertyItemFunc item_func, const bool allow_animation)
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
VecBase< float, 3 > float3
std::optional< std::string > rotation_id
void geo_node_type_base(blender::bke::bNodeType *ntype, std::string idname, const std::optional< int16_t > legacy_type)
void node_free_standard_storage(bNode *node)
Definition node_util.cc:42
void node_copy_standard_storage(bNodeTree *, bNode *dest_node, const bNode *src_node)
Definition node_util.cc:54
#define min(a, b)
Definition sort.cc:36
StructRNA * srna
Definition RNA_types.hh:909
static GeometrySet from_instances(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
static GeometrySet from_pointcloud(PointCloud *pointcloud, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void * storage
bool allow_skip(const StringRef name) 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
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:277
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:247
NodeDeclareFunction declare
Definition BKE_node.hh:355
void prop(PointerRNA *ptr, PropertyRNA *prop, int index, int value, eUI_Item_Flag flag, std::optional< blender::StringRef > name_opt, int icon, std::optional< blender::StringRef > placeholder=std::nullopt)
max
Definition text_draw.cc:251
PointerRNA * ptr
Definition wm_files.cc:4227