Blender V4.5
node_geo_curve_fill.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_array.hh"
6#include "BLI_array_utils.hh"
7#include "BLI_delaunay_2d.hh"
9
10#include "BKE_curves.hh"
11#include "BKE_grease_pencil.hh"
12#include "BKE_instances.hh"
13#include "BKE_mesh.hh"
14
15#include "BLI_task.hh"
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 b.add_input<decl::Int>("Group ID")
33 .field_on_all()
34 .hide_value()
35 .description(
36 "An index used to group curves together. Filling is done separately for each group");
37 b.add_output<decl::Geometry>("Mesh").propagate_all_instance_attributes();
38}
39
40static void node_layout(uiLayout *layout, bContext * /*C*/, PointerRNA *ptr)
41{
42 layout->prop(ptr, "mode", UI_ITEM_R_EXPAND, std::nullopt, ICON_NONE);
43}
44
45static void node_init(bNodeTree * /*tree*/, bNode *node)
46{
48
50 node->storage = data;
51}
52
55{
56 threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
57 for (const int i : range) {
58 faces[i].resize(offsets[i].size());
59 array_utils::fill_index_range<int>(faces[i], offsets[i].start());
60 }
61 });
62}
63
65 const CDT_output_type output_type)
66{
67 const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
68 const Span<float3> positions = curves.evaluated_positions();
69
70 Array<double2> positions_2d(positions.size());
71 threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
72 for (const int i : range) {
73 positions_2d[i] = double2(positions[i].x, positions[i].y);
74 }
75 });
76
77 Array<Vector<int>> faces(curves.curves_num());
78 fill_curve_vert_indices(points_by_curve, faces);
79
81 input.need_ids = false;
82 input.vert = std::move(positions_2d);
83 input.face = std::move(faces);
84
85 return delaunay_2d_calc(input, output_type);
86}
87
89 const CDT_output_type output_type,
90 const IndexMask &mask)
91{
92 const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
93 const Span<float3> positions = curves.evaluated_positions();
94
95 Array<int> offsets_data(mask.size() + 1);
96 const OffsetIndices points_by_curve_masked = offset_indices::gather_selected_offsets(
97 points_by_curve, mask, offsets_data);
98
99 Array<double2> positions_2d(points_by_curve_masked.total_size());
100 mask.foreach_index(GrainSize(1024), [&](const int src_curve, const int dst_curve) {
101 const IndexRange src_points = points_by_curve[src_curve];
102 const IndexRange dst_points = points_by_curve_masked[dst_curve];
103 for (const int i : src_points.index_range()) {
104 const int src = src_points[i];
105 const int dst = dst_points[i];
106 positions_2d[dst] = double2(positions[src].x, positions[src].y);
107 }
108 });
109
110 Array<Vector<int>> faces(points_by_curve_masked.size());
111 fill_curve_vert_indices(points_by_curve_masked, faces);
112
114 input.need_ids = false;
115 input.vert = std::move(positions_2d);
116 input.face = std::move(faces);
117
118 return delaunay_2d_calc(input, output_type);
119}
120
122 const bke::CurvesGeometry &curves,
123 const CDT_output_type output_type,
124 const Field<int> &group_index_field)
125{
126 const bke::GeometryFieldContext field_context{curves, AttrDomain::Curve};
127 fn::FieldEvaluator data_evaluator{field_context, curves.curves_num()};
128 data_evaluator.add(group_index_field);
129 data_evaluator.evaluate();
130 const VArray<int> curve_group_ids = data_evaluator.get_evaluated<int>(0);
131
132 if (curve_group_ids.is_single()) {
133 return {do_cdt(curves, output_type)};
134 }
135
136 VectorSet<int> group_indexing;
137 IndexMaskMemory mask_memory;
139 curve_group_ids, mask_memory, group_indexing);
140 const int groups_num = group_masks.size();
141
142 Array<meshintersect::CDT_result<double>> cdt_results(groups_num);
143
144 /* The grain size should be larger as each group gets smaller. */
145 const int domain_size = curve_group_ids.size();
146 const int avg_group_size = domain_size / groups_num;
147 const int grain_size = std::max(8192 / avg_group_size, 1);
148 threading::parallel_for(IndexRange(groups_num), grain_size, [&](const IndexRange range) {
149 for (const int group_index : range) {
150 const IndexMask &mask = group_masks[group_index];
151 cdt_results[group_index] = do_cdt_with_mask(curves, output_type, mask);
152 }
153 });
154
155 return cdt_results;
156}
157
158/* Converts multiple CDT results into a single Mesh. */
160{
161 /* Converting a single CDT result to a Mesh would be simple because the indices could be re-used.
162 * However, in the general case here we need to combine several CDT results into a single Mesh,
163 * which requires us to map the original indices to a new set of indices.
164 * In order to allow for parallelization when appropriate, this implementation starts by
165 * determining (for each domain) what range of indices in the final mesh data will be used for
166 * each CDT result. The index ranges are represented as offsets, which are referred to as "group
167 * offsets" to distinguish them from the other types of offsets we need to work with here.
168 * Since it's likely that most invocations will only have a single CDT result, it's important
169 * that case is made as optimal as feasible. */
170
171 Array<int> vert_groups_data(results.size() + 1);
172 Array<int> edge_groups_data(results.size() + 1);
173 Array<int> face_groups_data(results.size() + 1);
174 Array<int> loop_groups_data(results.size() + 1);
175 threading::parallel_for(results.index_range(), 1024, [&](const IndexRange results_range) {
176 for (const int i_result : results_range) {
177 const meshintersect::CDT_result<double> &result = results[i_result];
178 vert_groups_data[i_result] = result.vert.size();
179 edge_groups_data[i_result] = result.edge.size();
180 face_groups_data[i_result] = result.face.size();
181 int loop_len = 0;
182 for (const Vector<int> &face : result.face) {
183 loop_len += face.size();
184 }
185 loop_groups_data[i_result] = loop_len;
186 }
187 });
188
189 const OffsetIndices vert_groups = offset_indices::accumulate_counts_to_offsets(vert_groups_data);
190 const OffsetIndices edge_groups = offset_indices::accumulate_counts_to_offsets(edge_groups_data);
191 const OffsetIndices face_groups = offset_indices::accumulate_counts_to_offsets(face_groups_data);
192 const OffsetIndices loop_groups = offset_indices::accumulate_counts_to_offsets(loop_groups_data);
193
194 Mesh *mesh = BKE_mesh_new_nomain(vert_groups.total_size(),
195 edge_groups.total_size(),
196 face_groups.total_size(),
197 loop_groups.total_size());
198
199 MutableSpan<float3> all_positions = mesh->vert_positions_for_write();
200 MutableSpan<int2> all_edges = mesh->edges_for_write();
201 MutableSpan<int> all_face_offsets = mesh->face_offsets_for_write();
202 MutableSpan<int> all_corner_verts = mesh->corner_verts_for_write();
203
204 threading::parallel_for(results.index_range(), 1024, [&](const IndexRange results_range) {
205 for (const int i_result : results_range) {
206 const meshintersect::CDT_result<double> &result = results[i_result];
207 const IndexRange verts_range = vert_groups[i_result];
208 const IndexRange edges_range = edge_groups[i_result];
209 const IndexRange faces_range = face_groups[i_result];
210 const IndexRange loops_range = loop_groups[i_result];
211
212 MutableSpan<float3> positions = all_positions.slice(verts_range);
213 for (const int i : result.vert.index_range()) {
214 positions[i] = float3(float(result.vert[i].x), float(result.vert[i].y), 0.0f);
215 }
216
217 MutableSpan<int2> edges = all_edges.slice(edges_range);
218 for (const int i : result.edge.index_range()) {
219 edges[i] = int2(result.edge[i].first + verts_range.start(),
220 result.edge[i].second + verts_range.start());
221 }
222
223 MutableSpan<int> face_offsets = all_face_offsets.slice(faces_range);
224 MutableSpan<int> corner_verts = all_corner_verts.slice(loops_range);
225 int i_face_corner = 0;
226 for (const int i_face : result.face.index_range()) {
227 face_offsets[i_face] = i_face_corner + loops_range.start();
228 for (const int i_corner : result.face[i_face].index_range()) {
229 corner_verts[i_face_corner] = result.face[i_face][i_corner] + verts_range.start();
230 i_face_corner++;
231 }
232 }
233 }
234 });
235
236 /* The delaunay triangulation doesn't seem to return all of the necessary all_edges, even in
237 * triangulation mode. */
238 bke::mesh_calc_edges(*mesh, true, false);
239 bke::mesh_smooth_set(*mesh, false);
240
241 mesh->tag_overlapping_none();
242
243 return mesh;
244}
245
246static void curve_fill_calculate(GeometrySet &geometry_set,
247 const GeometryNodeCurveFillMode mode,
248 const Field<int> &group_index)
249{
250 const CDT_output_type output_type = (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) ?
253 if (geometry_set.has_curves()) {
254 const Curves &curves_id = *geometry_set.get_curves();
255 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
256 if (curves.curves_num() > 0) {
258 curves, output_type, group_index);
259 Mesh *mesh = cdts_to_mesh(results);
260 geometry_set.replace_mesh(mesh);
261 }
262 geometry_set.replace_curves(nullptr);
263 }
264
265 if (geometry_set.has_grease_pencil()) {
266 using namespace blender::bke::greasepencil;
267 const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
268 Vector<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
269 for (const int layer_index : grease_pencil.layers().index_range()) {
270 const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
271 if (drawing == nullptr) {
272 continue;
273 }
274 const bke::CurvesGeometry &src_curves = drawing->strokes();
275 if (src_curves.is_empty()) {
276 continue;
277 }
279 src_curves, output_type, group_index);
280 mesh_by_layer[layer_index] = cdts_to_mesh(results);
281 }
282 if (!mesh_by_layer.is_empty()) {
283 InstancesComponent &instances_component =
285 bke::Instances *instances = instances_component.get_for_write();
286 if (instances == nullptr) {
287 instances = new bke::Instances();
288 instances_component.replace(instances);
289 }
290 for (Mesh *mesh : mesh_by_layer) {
291 if (!mesh) {
292 /* Add an empty reference so the number of layers and instances match.
293 * This makes it easy to reconstruct the layers afterwards and keep their attributes.
294 * Although in this particular case we don't propagate the attributes. */
295 const int handle = instances->add_reference(bke::InstanceReference());
296 instances->add_instance(handle, float4x4::identity());
297 continue;
298 }
300 const int handle = instances->add_reference(bke::InstanceReference{temp_set});
301 instances->add_instance(handle, float4x4::identity());
302 }
303 }
304 geometry_set.replace_grease_pencil(nullptr);
305 }
306}
307
309{
310 GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
311 Field<int> group_index = params.extract_input<Field<int>>("Group ID");
312
313 const NodeGeometryCurveFill &storage = node_storage(params.node());
314 const GeometryNodeCurveFillMode mode = (GeometryNodeCurveFillMode)storage.mode;
315
316 geometry_set.modify_geometry_sets(
317 [&](GeometrySet &geometry_set) { curve_fill_calculate(geometry_set, mode, group_index); });
318
319 params.set_output("Mesh", std::move(geometry_set));
320}
321
322static void node_rna(StructRNA *srna)
323{
324 static const EnumPropertyItem mode_items[] = {
325 {GEO_NODE_CURVE_FILL_MODE_TRIANGULATED, "TRIANGLES", 0, "Triangles", ""},
326 {GEO_NODE_CURVE_FILL_MODE_NGONS, "NGONS", 0, "N-gons", ""},
327 {0, nullptr, 0, nullptr, nullptr},
328 };
329
331 "mode",
332 "Mode",
333 "",
334 mode_items,
337}
338
339static void node_register()
340{
341 static blender::bke::bNodeType ntype;
342 geo_node_type_base(&ntype, "GeometryNodeFillCurve", GEO_NODE_FILL_CURVE);
343 ntype.ui_name = "Fill Curve";
344 ntype.ui_description =
345 "Generate a mesh on the XY plane with faces on the inside of input curves";
346 ntype.enum_name_legacy = "FILL_CURVE";
348 ntype.initfunc = node_init;
350 ntype, "NodeGeometryCurveFill", node_free_standard_storage, node_copy_standard_storage);
351 ntype.declare = node_declare;
355
356 node_rna(ntype.rna_ext.srna);
357}
358NOD_REGISTER_NODE(node_register)
359
360} // namespace blender::nodes::node_geo_curve_fill_cc
Low-level operations for curves.
Low-level operations for grease pencil.
Mesh * BKE_mesh_new_nomain(int verts_num, int edges_num, int faces_num, int corners_num)
#define NODE_STORAGE_FUNCS(StorageT)
Definition BKE_node.hh:1215
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:447
#define GEO_NODE_FILL_CURVE
CDT_output_type
@ CDT_CONSTRAINTS_VALID_BMESH_WITH_HOLES
@ CDT_INSIDE_WITH_HOLES
GeometryNodeCurveFillMode
@ GEO_NODE_CURVE_FILL_MODE_TRIANGULATED
@ GEO_NODE_CURVE_FILL_MODE_NGONS
#define NOD_REGISTER_NODE(REGISTER_FUNC)
#define NOD_storage_enum_accessors(member)
@ UI_ITEM_R_EXPAND
BMesh const char void * data
static Vector< IndexMask, 4 > from_group_ids(const VArray< int > &group_ids, IndexMaskMemory &memory, VectorSet< int > &r_index_by_group_id)
constexpr IndexRange index_range() const
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
int64_t size() const
bool is_empty() const
OffsetIndices< int > evaluated_points_by_curve() const
Span< float3 > evaluated_positions() const
void replace(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
const bke::CurvesGeometry & strokes() const
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
#define input
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
ccl_device_inline float2 mask(const MaskType mask, const float2 a)
static char faces[256]
void node_register_type(bNodeType &ntype)
Definition node.cc:2748
void mesh_smooth_set(Mesh &mesh, bool use_smooth, bool keep_sharp_edges=false)
void mesh_calc_edges(Mesh &mesh, bool keep_existing_edges, bool select_new_edges)
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
static void node_init(bNodeTree *, bNode *node)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
static Array< meshintersect::CDT_result< double > > do_group_aware_cdt(const bke::CurvesGeometry &curves, const CDT_output_type output_type, const Field< int > &group_index_field)
static void curve_fill_calculate(GeometrySet &geometry_set, const GeometryNodeCurveFillMode mode, const Field< int > &group_index)
static void fill_curve_vert_indices(const OffsetIndices< int > offsets, MutableSpan< Vector< int > > faces)
static meshintersect::CDT_result< double > do_cdt_with_mask(const bke::CurvesGeometry &curves, const CDT_output_type output_type, const IndexMask &mask)
static Mesh * cdts_to_mesh(const Span< meshintersect::CDT_result< double > > results)
static void node_layout(uiLayout *layout, bContext *, PointerRNA *ptr)
static meshintersect::CDT_result< double > do_cdt(const bke::CurvesGeometry &curves, const CDT_output_type output_type)
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)
OffsetIndices< int > accumulate_counts_to_offsets(MutableSpan< int > counts_to_offsets, int start_offset=0)
OffsetIndices< int > gather_selected_offsets(OffsetIndices< int > src_offsets, const IndexMask &selection, int start_offset, MutableSpan< int > dst_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
VecBase< double, 2 > double2
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
CurvesGeometry geometry
StructRNA * srna
Definition RNA_types.hh:909
static GeometrySet from_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void * storage
GeometryComponent & get_component_for_write(GeometryComponent::Type component_type)
const GreasePencil * get_grease_pencil() const
const Curves * get_curves() const
void replace_curves(Curves *curves, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void modify_geometry_sets(ForeachSubGeometryCallback callback)
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void replace_grease_pencil(GreasePencil *grease_pencil, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
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)
i
Definition text_draw.cc:230
PointerRNA * ptr
Definition wm_files.cc:4227