Blender V5.0
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
18
19#include "node_geometry_util.hh"
20
22
24
25static const EnumPropertyItem mode_items[] = {
26 {GEO_NODE_CURVE_FILL_MODE_TRIANGULATED, "TRIANGLES", 0, N_("Triangles"), ""},
27 {GEO_NODE_CURVE_FILL_MODE_NGONS, "NGONS", 0, N_("N-gons"), ""},
28 {0, nullptr, 0, nullptr, nullptr},
29};
30
32{
33 b.add_input<decl::Geometry>("Curve")
34 .supported_type({GeometryComponent::Type::Curve, GeometryComponent::Type::GreasePencil})
35 .description(
36 "Curves to fill. All curves are treated as cyclic and projected to the XY plane");
37 b.add_input<decl::Int>("Group ID")
38 .field_on_all()
39 .hide_value()
40 .description(
41 "An index used to group curves together. Filling is done separately for each group");
42 b.add_input<decl::Menu>("Mode")
43 .static_items(mode_items)
45 .optional_label();
46 b.add_output<decl::Geometry>("Mesh").propagate_all_instance_attributes();
47}
48
49static void node_init(bNodeTree * /*tree*/, bNode *node)
50{
51 /* Still used for forward compatibility. */
53}
54
57{
58 threading::parallel_for(faces.index_range(), 1024, [&](const IndexRange range) {
59 for (const int i : range) {
60 faces[i].resize(offsets[i].size());
61 array_utils::fill_index_range<int>(faces[i], offsets[i].start());
62 }
63 });
64}
65
67 const CDT_output_type output_type)
68{
69 const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
70 const Span<float3> positions = curves.evaluated_positions();
71
72 Array<double2> positions_2d(positions.size());
73 threading::parallel_for(positions.index_range(), 2048, [&](const IndexRange range) {
74 for (const int i : range) {
75 positions_2d[i] = double2(positions[i].x, positions[i].y);
76 }
77 });
78
79 Array<Vector<int>> faces(curves.curves_num());
80 fill_curve_vert_indices(points_by_curve, faces);
81
83 input.need_ids = false;
84 input.vert = std::move(positions_2d);
85 input.face = std::move(faces);
86
87 return delaunay_2d_calc(input, output_type);
88}
89
91 const CDT_output_type output_type,
92 const IndexMask &mask)
93{
94 const OffsetIndices points_by_curve = curves.evaluated_points_by_curve();
95 const Span<float3> positions = curves.evaluated_positions();
96
97 Array<int> offsets_data(mask.size() + 1);
98 const OffsetIndices points_by_curve_masked = offset_indices::gather_selected_offsets(
99 points_by_curve, mask, offsets_data);
100
101 Array<double2> positions_2d(points_by_curve_masked.total_size());
102 mask.foreach_index(GrainSize(1024), [&](const int src_curve, const int dst_curve) {
103 const IndexRange src_points = points_by_curve[src_curve];
104 const IndexRange dst_points = points_by_curve_masked[dst_curve];
105 for (const int i : src_points.index_range()) {
106 const int src = src_points[i];
107 const int dst = dst_points[i];
108 positions_2d[dst] = double2(positions[src].x, positions[src].y);
109 }
110 });
111
112 Array<Vector<int>> faces(points_by_curve_masked.size());
113 fill_curve_vert_indices(points_by_curve_masked, faces);
114
116 input.need_ids = false;
117 input.vert = std::move(positions_2d);
118 input.face = std::move(faces);
119
120 return delaunay_2d_calc(input, output_type);
121}
122
124 const bke::CurvesGeometry &curves,
125 const CDT_output_type output_type,
126 const Field<int> &group_index_field)
127{
128 const bke::GeometryFieldContext field_context{curves, AttrDomain::Curve};
129 fn::FieldEvaluator data_evaluator{field_context, curves.curves_num()};
130 data_evaluator.add(group_index_field);
131 data_evaluator.evaluate();
132 const VArray<int> curve_group_ids = data_evaluator.get_evaluated<int>(0);
133
134 if (curve_group_ids.is_single()) {
135 return {do_cdt(curves, output_type)};
136 }
137
138 VectorSet<int> group_indexing;
139 IndexMaskMemory mask_memory;
141 curve_group_ids, mask_memory, group_indexing);
142 const int groups_num = group_masks.size();
143
144 Array<meshintersect::CDT_result<double>> cdt_results(groups_num);
145
146 /* The grain size should be larger as each group gets smaller. */
147 const int domain_size = curve_group_ids.size();
148 const int avg_group_size = domain_size / groups_num;
149 const int grain_size = std::max(8192 / avg_group_size, 1);
150 threading::parallel_for(IndexRange(groups_num), grain_size, [&](const IndexRange range) {
151 for (const int group_index : range) {
152 const IndexMask &mask = group_masks[group_index];
153 cdt_results[group_index] = do_cdt_with_mask(curves, output_type, mask);
154 }
155 });
156
157 return cdt_results;
158}
159
160/* Converts multiple CDT results into a single Mesh. */
162{
163 /* Converting a single CDT result to a Mesh would be simple because the indices could be re-used.
164 * However, in the general case here we need to combine several CDT results into a single Mesh,
165 * which requires us to map the original indices to a new set of indices.
166 * In order to allow for parallelization when appropriate, this implementation starts by
167 * determining (for each domain) what range of indices in the final mesh data will be used for
168 * each CDT result. The index ranges are represented as offsets, which are referred to as "group
169 * offsets" to distinguish them from the other types of offsets we need to work with here.
170 * Since it's likely that most invocations will only have a single CDT result, it's important
171 * that case is made as optimal as feasible. */
172
173 Array<int> vert_groups_data(results.size() + 1);
174 Array<int> edge_groups_data(results.size() + 1);
175 Array<int> face_groups_data(results.size() + 1);
176 Array<int> loop_groups_data(results.size() + 1);
177 threading::parallel_for(results.index_range(), 1024, [&](const IndexRange results_range) {
178 for (const int i_result : results_range) {
179 const meshintersect::CDT_result<double> &result = results[i_result];
180 vert_groups_data[i_result] = result.vert.size();
181 edge_groups_data[i_result] = result.edge.size();
182 face_groups_data[i_result] = result.face.size();
183 int loop_len = 0;
184 for (const Vector<int> &face : result.face) {
185 loop_len += face.size();
186 }
187 loop_groups_data[i_result] = loop_len;
188 }
189 });
190
191 const OffsetIndices vert_groups = offset_indices::accumulate_counts_to_offsets(vert_groups_data);
192 const OffsetIndices edge_groups = offset_indices::accumulate_counts_to_offsets(edge_groups_data);
193 const OffsetIndices face_groups = offset_indices::accumulate_counts_to_offsets(face_groups_data);
194 const OffsetIndices loop_groups = offset_indices::accumulate_counts_to_offsets(loop_groups_data);
195
196 Mesh *mesh = BKE_mesh_new_nomain(vert_groups.total_size(),
197 edge_groups.total_size(),
198 face_groups.total_size(),
199 loop_groups.total_size());
200
201 MutableSpan<float3> all_positions = mesh->vert_positions_for_write();
202 MutableSpan<int2> all_edges = mesh->edges_for_write();
203 MutableSpan<int> all_face_offsets = mesh->face_offsets_for_write();
204 MutableSpan<int> all_corner_verts = mesh->corner_verts_for_write();
205
206 threading::parallel_for(results.index_range(), 1024, [&](const IndexRange results_range) {
207 for (const int i_result : results_range) {
208 const meshintersect::CDT_result<double> &result = results[i_result];
209 const IndexRange verts_range = vert_groups[i_result];
210 const IndexRange edges_range = edge_groups[i_result];
211 const IndexRange faces_range = face_groups[i_result];
212 const IndexRange loops_range = loop_groups[i_result];
213
214 MutableSpan<float3> positions = all_positions.slice(verts_range);
215 for (const int i : result.vert.index_range()) {
216 positions[i] = float3(float(result.vert[i].x), float(result.vert[i].y), 0.0f);
217 }
218
219 MutableSpan<int2> edges = all_edges.slice(edges_range);
220 for (const int i : result.edge.index_range()) {
221 edges[i] = int2(result.edge[i].first + verts_range.start(),
222 result.edge[i].second + verts_range.start());
223 }
224
225 MutableSpan<int> face_offsets = all_face_offsets.slice(faces_range);
226 MutableSpan<int> corner_verts = all_corner_verts.slice(loops_range);
227 int i_face_corner = 0;
228 for (const int i_face : result.face.index_range()) {
229 face_offsets[i_face] = i_face_corner + loops_range.start();
230 for (const int i_corner : result.face[i_face].index_range()) {
231 corner_verts[i_face_corner] = result.face[i_face][i_corner] + verts_range.start();
232 i_face_corner++;
233 }
234 }
235 }
236 });
237
238 /* The delaunay triangulation doesn't seem to return all of the necessary all_edges, even in
239 * triangulation mode. */
240 bke::mesh_calc_edges(*mesh, true, false);
241 bke::mesh_smooth_set(*mesh, false);
242
243 mesh->tag_overlapping_none();
244
245 return mesh;
246}
247
248static void curve_fill_calculate(GeometrySet &geometry_set,
249 const GeometryNodeCurveFillMode mode,
250 const Field<int> &group_index)
251{
252 const CDT_output_type output_type = (mode == GEO_NODE_CURVE_FILL_MODE_NGONS) ?
255 if (geometry_set.has_curves()) {
256 const Curves &curves_id = *geometry_set.get_curves();
257 const bke::CurvesGeometry &curves = curves_id.geometry.wrap();
258 if (curves.curves_num() > 0) {
260 curves, output_type, group_index);
261 Mesh *mesh = cdts_to_mesh(results);
262 geometry_set.replace_mesh(mesh);
263 }
264 geometry_set.replace_curves(nullptr);
265 }
266
267 if (geometry_set.has_grease_pencil()) {
268 using namespace blender::bke::greasepencil;
269 const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
270 Vector<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
271 for (const int layer_index : grease_pencil.layers().index_range()) {
272 const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
273 if (drawing == nullptr) {
274 continue;
275 }
276 const bke::CurvesGeometry &src_curves = drawing->strokes();
277 if (src_curves.is_empty()) {
278 continue;
279 }
281 src_curves, output_type, group_index);
282 mesh_by_layer[layer_index] = cdts_to_mesh(results);
283 }
284 if (!mesh_by_layer.is_empty()) {
285 InstancesComponent &instances_component =
287 bke::Instances *instances = instances_component.get_for_write();
288 if (instances == nullptr) {
289 instances = new bke::Instances();
290 instances_component.replace(instances);
291 }
292 for (Mesh *mesh : mesh_by_layer) {
293 if (!mesh) {
294 /* Add an empty reference so the number of layers and instances match.
295 * This makes it easy to reconstruct the layers afterwards and keep their attributes.
296 * Although in this particular case we don't propagate the attributes. */
297 const int handle = instances->add_reference(bke::InstanceReference());
298 instances->add_instance(handle, float4x4::identity());
299 continue;
300 }
302 const int handle = instances->add_reference(bke::InstanceReference{temp_set});
303 instances->add_instance(handle, float4x4::identity());
304 }
305 }
306 geometry_set.replace_grease_pencil(nullptr);
307 }
308}
309
311{
312 GeometrySet geometry_set = params.extract_input<GeometrySet>("Curve");
313 Field<int> group_index = params.extract_input<Field<int>>("Group ID");
314 const GeometryNodeCurveFillMode mode = params.extract_input<GeometryNodeCurveFillMode>("Mode");
315
317 curve_fill_calculate(geometry, mode, group_index);
318 });
319
320 params.set_output("Mesh", std::move(geometry_set));
321}
322
323static void node_register()
324{
325 static blender::bke::bNodeType ntype;
326 geo_node_type_base(&ntype, "GeometryNodeFillCurve", GEO_NODE_FILL_CURVE);
327 ntype.ui_name = "Fill Curve";
328 ntype.ui_description =
329 "Generate a mesh on the XY plane with faces on the inside of input curves";
330 ntype.enum_name_legacy = "FILL_CURVE";
332 ntype.initfunc = node_init;
334 ntype, "NodeGeometryCurveFill", node_free_standard_storage, node_copy_standard_storage);
335 ntype.declare = node_declare;
338}
339NOD_REGISTER_NODE(node_register)
340
341} // 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:1240
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:461
#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)
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:2416
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:5414
void foreach_real_geometry(bke::GeometrySet &geometry, FunctionRef< void(bke::GeometrySet &geometry_set)> fn)
static void node_init(bNodeTree *, bNode *node)
static void node_geo_exec(GeoNodeExecParams params)
static void node_declare(NodeDeclarationBuilder &b)
static const EnumPropertyItem mode_items[]
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 meshintersect::CDT_result< double > do_cdt(const bke::CurvesGeometry &curves, const CDT_output_type output_type)
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
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 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: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
NodeDeclareFunction declare
Definition BKE_node.hh:362
i
Definition text_draw.cc:230
#define N_(msgid)