Blender V5.0
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
17
18#include "NOD_rna_define.hh"
19
21#include "UI_resources.hh"
22
23#include "node_geometry_util.hh"
24
26
28
30{
31 b.add_input<decl::Geometry>("Curve")
32 .supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
33 .description("Curves to convert to points");
34 auto &count = b.add_input<decl::Int>("Count")
35 .default_value(10)
36 .min(2)
37 .max(100000)
38 .field_on_all()
39 .make_available([](bNode &node) {
40 node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_COUNT;
41 });
42 auto &length = b.add_input<decl::Float>("Length")
43 .default_value(0.1f)
44 .min(0.001f)
45 .subtype(PROP_DISTANCE)
46 .field_on_all()
47 .make_available([](bNode &node) {
48 node_storage(node).mode = GEO_NODE_CURVE_RESAMPLE_LENGTH;
49 });
50 b.add_output<decl::Geometry>("Points").propagate_all();
51 b.add_output<decl::Vector>("Tangent").field_on_all();
52 b.add_output<decl::Vector>("Normal").field_on_all();
53 b.add_output<decl::Rotation>("Rotation").field_on_all();
54
55 const bNode *node = b.node_or_null();
56 if (node != nullptr) {
57 const NodeGeometryCurveToPoints &storage = node_storage(*node);
59
60 count.available(mode == GEO_NODE_CURVE_RESAMPLE_COUNT);
61 length.available(mode == GEO_NODE_CURVE_RESAMPLE_LENGTH);
62 }
63}
64
65static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
66{
67 layout->prop(ptr, "mode", UI_ITEM_NONE, "", ICON_NONE);
68}
69
70static void node_init(bNodeTree * /*tree*/, bNode *node)
71{
73
75 node->storage = data;
76}
77
78static void fill_rotation_attribute(const Span<float3> tangents,
81{
82 threading::parallel_for(IndexRange(rotations.size()), 512, [&](IndexRange range) {
83 for (const int i : range) {
84 rotations[i] = math::to_quaternion(
85 math::from_orthonormal_axes<float4x4>(normals[i], tangents[i]));
86 }
87 });
88}
89
90static void copy_curve_domain_attributes(const AttributeAccessor curve_attributes,
91 const AttributeFilter &attribute_filter,
92 MutableAttributeAccessor point_attributes)
93{
94 curve_attributes.foreach_attribute([&](const bke::AttributeIter &iter) {
95 if (iter.is_builtin) {
96 return;
97 }
98 if (iter.domain != AttrDomain::Curve) {
99 return;
100 }
101 if (attribute_filter.allow_skip(iter.name)) {
102 return;
103 }
104 if (iter.data_type == bke::AttrType::String) {
105 return;
106 }
107 point_attributes.add(iter.name,
108 AttrDomain::Point,
109 iter.data_type,
110 bke::AttributeInitVArray(*iter.get(AttrDomain::Point)));
111 });
112}
113
115 const bke::CurvesGeometry &curves,
116 const AttributeFilter &attribute_filter,
117 const geometry::ResampleCurvesOutputAttributeIDs &resample_attributes,
118 const std::optional<StringRef> &rotation_id)
119{
120 const AttributeAccessor curve_attributes = curves.attributes();
121
123 MutableAttributeAccessor point_attributes = pointcloud->attributes_for_write();
124
126 if (ELEM(name, resample_attributes.tangent_id, resample_attributes.normal_id, rotation_id)) {
128 }
129 if (attribute_filter.allow_skip(name)) {
131 }
132 if (curve_attributes.is_builtin(name) && !point_attributes.is_builtin(name)) {
134 }
136 };
137
141 filter,
142 pointcloud->attributes_for_write());
143
144 copy_curve_domain_attributes(curve_attributes, filter, point_attributes);
145
146 if (rotation_id) {
147 const VArraySpan tangents = *curve_attributes.lookup<float3>(*resample_attributes.tangent_id,
148 AttrDomain::Point);
149 const VArraySpan normals = *curve_attributes.lookup<float3>(*resample_attributes.normal_id,
150 AttrDomain::Point);
151 SpanAttributeWriter rotations =
153 AttrDomain::Point);
154 fill_rotation_attribute(tangents, normals, rotations.span);
155 rotations.finish();
156 }
157
158 return pointcloud;
159}
160
161static void layer_pointclouds_to_instances(const Span<PointCloud *> pointcloud_by_layer,
162 const AttributeFilter &attribute_filter,
164{
165 if (!pointcloud_by_layer.is_empty()) {
166 bke::Instances *instances = new bke::Instances();
167 for (PointCloud *pointcloud : pointcloud_by_layer) {
168 if (!pointcloud) {
169 /* Add an empty reference so the number of layers and instances match.
170 * This makes it easy to reconstruct the layers afterwards and keep their
171 * attributes. */
172 const int handle = instances->add_reference(bke::InstanceReference());
173 instances->add_instance(handle, float4x4::identity());
174 continue;
175 }
176 GeometrySet temp_set = GeometrySet::from_pointcloud(pointcloud);
177 const int handle = instances->add_reference(bke::InstanceReference{temp_set});
178 instances->add_instance(handle, float4x4::identity());
179 }
180
181 bke::copy_attributes(geometry.get_grease_pencil()->attributes(),
184 attribute_filter,
185 instances->attributes_for_write());
186 InstancesComponent &dst_component = geometry.get_component_for_write<InstancesComponent>();
188 {GeometrySet::from_instances(dst_component.release()),
189 GeometrySet::from_instances(instances)},
190 attribute_filter);
191 dst_component.replace(new_instances.get_component_for_write<InstancesComponent>().release());
192 }
193}
194
196{
197 const NodeGeometryCurveToPoints &storage = node_storage(params.node());
199 GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
200
202
203 std::optional<std::string> rotation_anonymous_id =
204 params.get_output_anonymous_attribute_id_if_needed("Rotation");
205 const bool need_tangent_and_normal = bool(rotation_anonymous_id);
206 std::optional<std::string> tangent_anonymous_id =
207 params.get_output_anonymous_attribute_id_if_needed("Tangent", need_tangent_and_normal);
208 std::optional<std::string> normal_anonymous_id =
209 params.get_output_anonymous_attribute_id_if_needed("Normal", need_tangent_and_normal);
210
212 resample_attributes.tangent_id = tangent_anonymous_id;
213 resample_attributes.normal_id = normal_anonymous_id;
214 const NodeAttributeFilter &attribute_filter = params.get_attribute_filter("Points");
215
216 switch (mode) {
218 const Field<int> count = params.extract_input<Field<int>>("Count");
220 if (const Curves *src_curves_id = geometry.get_curves()) {
222 src_curves_id->geometry.wrap(),
223 bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
225 count,
226 resample_attributes);
227 PointCloud *pointcloud = curves_to_points(
228 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
229 geometry.replace_pointcloud(pointcloud);
230 }
231 if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
232 Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
233 for (const int layer_index : grease_pencil->layers().index_range()) {
234 const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
235 grease_pencil->layer(layer_index));
236 if (drawing == nullptr) {
237 continue;
238 }
240 drawing->strokes(),
241 bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
243 count,
244 resample_attributes);
245 pointcloud_by_layer[layer_index] = curves_to_points(
246 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
247 }
248 layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
249 }
253 });
254 break;
255 }
257 const Field<float> length = params.extract_input<Field<float>>("Length");
259 if (const Curves *src_curves_id = geometry.get_curves()) {
261 src_curves_id->geometry.wrap(),
262 bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
264 length,
265 resample_attributes);
266 PointCloud *pointcloud = curves_to_points(
267 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
268 geometry.replace_pointcloud(pointcloud);
269 }
270 if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
271 Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
272 for (const int layer_index : grease_pencil->layers().index_range()) {
273 const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
274 grease_pencil->layer(layer_index));
275 if (drawing == nullptr) {
276 continue;
277 }
279 drawing->strokes(),
280 bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
282 length,
283 resample_attributes);
284 pointcloud_by_layer[layer_index] = curves_to_points(
285 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
286 }
287 layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
288 }
292 });
293 break;
294 }
297 if (const Curves *src_curves_id = geometry.get_curves()) {
299 src_curves_id->geometry.wrap(),
300 bke::CurvesFieldContext(*src_curves_id, AttrDomain::Curve),
302 resample_attributes);
303 PointCloud *pointcloud = curves_to_points(
304 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
305 geometry.replace_pointcloud(pointcloud);
306 geometry.replace_curves(nullptr);
307 }
308 if (const GreasePencil *grease_pencil = geometry.get_grease_pencil()) {
309 Array<PointCloud *> pointcloud_by_layer(grease_pencil->layers().size(), nullptr);
310 for (const int layer_index : grease_pencil->layers().index_range()) {
311 const bke::greasepencil::Drawing *drawing = grease_pencil->get_eval_drawing(
312 grease_pencil->layer(layer_index));
313 if (drawing == nullptr) {
314 continue;
315 }
317 drawing->strokes(),
318 bke::GreasePencilLayerFieldContext(*grease_pencil, AttrDomain::Curve, layer_index),
320 resample_attributes);
321 pointcloud_by_layer[layer_index] = curves_to_points(
322 dst_curves, attribute_filter, resample_attributes, rotation_anonymous_id);
323 }
324 layer_pointclouds_to_instances(pointcloud_by_layer, attribute_filter, geometry);
325 }
329 });
330 break;
331 }
332 }
333
334 params.set_output("Points", std::move(geometry_set));
335}
336
337static void node_rna(StructRNA *srna)
338{
339 static EnumPropertyItem mode_items[] = {
341 "EVALUATED",
342 0,
343 "Evaluated",
344 "Create points from the curve's evaluated points, based on the resolution attribute for "
345 "NURBS and Bézier splines"},
347 "COUNT",
348 0,
349 "Count",
350 "Sample each spline by evenly distributing the specified number of points"},
352 "LENGTH",
353 0,
354 "Length",
355 "Sample each spline by splitting it into segments with the specified length"},
356 {0, nullptr, 0, nullptr, nullptr},
357 };
358
360 "mode",
361 "Mode",
362 "How to generate points from the input curve",
363 mode_items,
366}
367
368static void node_register()
369{
370 static blender::bke::bNodeType ntype;
371
372 geo_node_type_base(&ntype, "GeometryNodeCurveToPoints", GEO_NODE_CURVE_TO_POINTS);
373 ntype.ui_name = "Curve to Points";
374 ntype.ui_description = "Generate a point cloud by sampling positions along curves";
375 ntype.enum_name_legacy = "CURVE_TO_POINTS";
377 ntype.declare = node_declare;
381 ntype, "NodeGeometryCurveToPoints", node_free_standard_storage, node_copy_standard_storage);
382 ntype.initfunc = node_init;
384
385 node_rna(ntype.rna_ext.srna);
386}
387NOD_REGISTER_NODE(node_register)
388
389} // 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:1240
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#define GEO_NODE_CURVE_TO_POINTS
General operations for point clouds.
#define ELEM(...)
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:256
#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:261
void add_instance(int instance_handle, const float4x4 &transform)
Definition instances.cc:204
bke::MutableAttributeAccessor attributes_for_write()
Definition instances.cc:69
bool add(const StringRef attribute_id, const AttrDomain domain, const AttrType data_type, const AttributeInit &initializer)
GSpanAttributeWriter lookup_or_add_for_write_only_span(StringRef attribute_id, AttrDomain domain, AttrType 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:2416
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:5414
GField make_constant_field(const CPPType &type, const void *value)
Definition field.cc:528
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, bool allow_merging_instance_references=true)
void foreach_real_geometry(bke::GeometrySet &geometry, FunctionRef< void(bke::GeometrySet &geometry_set)> fn)
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={})
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
const char * name
#define min(a, b)
Definition sort.cc:36
StructRNA * srna
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
Defines a node type.
Definition BKE_node.hh:238
std::string ui_description
Definition BKE_node.hh:244
void(* initfunc)(bNodeTree *ntree, bNode *node)
Definition BKE_node.hh:289
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:354
const char * enum_name_legacy
Definition BKE_node.hh:247
void(* draw_buttons)(uiLayout *, bContext *C, PointerRNA *ptr)
Definition BKE_node.hh:259
NodeDeclareFunction declare
Definition BKE_node.hh:362
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:4238