Blender V4.3
node_geo_convex_hull.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_curves.hh"
9#include "BKE_instances.hh"
10#include "BKE_material.h"
11#include "BKE_mesh.hh"
12
13#include "GEO_randomize.hh"
14
15#include "node_geometry_util.hh"
16
17#ifdef WITH_BULLET
18# include "RBI_hull_api.h"
19#endif
20
22
24{
25 b.add_input<decl::Geometry>("Geometry");
26 b.add_output<decl::Geometry>("Convex Hull");
27}
28
29#ifdef WITH_BULLET
30
31static Mesh *hull_from_bullet(const Mesh *mesh, Span<float3> coords)
32{
33 plConvexHull hull = plConvexHullCompute((float(*)[3])coords.data(), coords.size());
34
35 const int verts_num = plConvexHullNumVertices(hull);
36 const int faces_num = verts_num <= 2 ? 0 : plConvexHullNumFaces(hull);
37 const int loops_num = verts_num <= 2 ? 0 : plConvexHullNumLoops(hull);
38 /* Half as many edges as loops, because the mesh is manifold. */
39 const int edges_num = verts_num == 2 ? 1 : verts_num < 2 ? 0 : loops_num / 2;
40
41 /* Create Mesh *result with proper capacity. */
42 Mesh *result;
43 if (mesh) {
44 result = BKE_mesh_new_nomain_from_template(mesh, verts_num, edges_num, faces_num, loops_num);
45 }
46 else {
47 result = BKE_mesh_new_nomain(verts_num, edges_num, faces_num, loops_num);
49 }
50 bke::mesh_smooth_set(*result, false);
51
52 /* Copy vertices. */
53 MutableSpan<float3> dst_positions = result->vert_positions_for_write();
54 for (const int i : IndexRange(verts_num)) {
55 int original_index;
56 plConvexHullGetVertex(hull, i, dst_positions[i], &original_index);
57
58 if (original_index >= 0 && original_index < coords.size()) {
59# if 0 /* Disabled because it only works for meshes, not predictable enough. */
60 /* Copy custom data on vertices, like vertex groups etc. */
61 if (mesh && original_index < mesh->verts_num) {
62 CustomData_copy_data(&mesh->vert_data, &result->vert_data, int(original_index), int(i), 1);
63 }
64# endif
65 }
66 else {
67 BLI_assert_msg(0, "Unexpected new vertex in hull output");
68 }
69 }
70
71 /* Copy edges and loops. */
72
73 /* NOTE: ConvexHull from Bullet uses a half-edge data structure
74 * for its mesh. To convert that, each half-edge needs to be converted
75 * to a loop and edges need to be created from that. */
76 Array<int> corner_verts(loops_num);
77 Array<int> corner_edges(loops_num);
78 uint edge_index = 0;
79 MutableSpan<int2> edges = result->edges_for_write();
80
81 for (const int i : IndexRange(loops_num)) {
82 int v_from;
83 int v_to;
84 plConvexHullGetLoop(hull, i, &v_from, &v_to);
85
86 corner_verts[i] = v_from;
87 /* Add edges for ascending order loops only. */
88 if (v_from < v_to) {
89 edges[edge_index] = int2(v_from, v_to);
90
91 /* Write edge index into both loops that have it. */
92 int reverse_index = plConvexHullGetReversedLoopIndex(hull, i);
93 corner_edges[i] = edge_index;
94 corner_edges[reverse_index] = edge_index;
95 edge_index++;
96 }
97 }
98 if (edges_num == 1) {
99 /* In this case there are no loops. */
100 edges[0] = int2(0, 1);
101 edge_index++;
102 }
103 BLI_assert(edge_index == edges_num);
104
105 /* Copy faces. */
106 Array<int> loops;
107 int j = 0;
108 MutableSpan<int> face_offsets = result->face_offsets_for_write();
109 MutableSpan<int> mesh_corner_verts = result->corner_verts_for_write();
110 MutableSpan<int> mesh_corner_edges = result->corner_edges_for_write();
111 int dst_corner = 0;
112
113 for (const int i : IndexRange(faces_num)) {
114 const int len = plConvexHullGetFaceSize(hull, i);
115
116 BLI_assert(len > 2);
117
118 /* Get face loop indices. */
119 loops.reinitialize(len);
120 plConvexHullGetFaceLoops(hull, i, loops.data());
121
122 face_offsets[i] = j;
123 for (const int k : IndexRange(len)) {
124 mesh_corner_verts[dst_corner] = corner_verts[loops[k]];
125 mesh_corner_edges[dst_corner] = corner_edges[loops[k]];
126 dst_corner++;
127 }
128 j += len;
129 }
130
131 plConvexHullDelete(hull);
132 return result;
133}
134
135static Mesh *compute_hull(const GeometrySet &geometry_set)
136{
137 int span_count = 0;
138 int count = 0;
139 int total_num = 0;
140
141 Span<float3> positions_span;
142
143 if (const Mesh *mesh = geometry_set.get_mesh()) {
144 count++;
145 if (const VArray positions = *mesh->attributes().lookup<float3>("position")) {
146 if (positions.is_span()) {
147 span_count++;
148 positions_span = positions.get_internal_span();
149 }
150 total_num += positions.size();
151 }
152 }
153
154 if (const PointCloud *points = geometry_set.get_pointcloud()) {
155 count++;
156 if (const VArray positions = *points->attributes().lookup<float3>("position")) {
157 if (positions.is_span()) {
158 span_count++;
159 positions_span = positions.get_internal_span();
160 }
161 total_num += positions.size();
162 }
163 }
164
165 if (const Curves *curves_id = geometry_set.get_curves()) {
166 count++;
167 span_count++;
168 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
169 positions_span = curves.evaluated_positions();
170 total_num += positions_span.size();
171 }
172
173 if (count == 0) {
174 return nullptr;
175 }
176
177 /* If there is only one positions virtual array and it is already contiguous, avoid copying
178 * all of the positions and instead pass the span directly to the convex hull function. */
179 if (span_count == 1 && count == 1) {
180 return hull_from_bullet(geometry_set.get_mesh(), positions_span);
181 }
182
183 Array<float3> positions(total_num);
184 int offset = 0;
185
186 if (const Mesh *mesh = geometry_set.get_mesh()) {
187 if (const VArray varray = *mesh->attributes().lookup<float3>("position")) {
188 varray.materialize(positions.as_mutable_span().slice(offset, varray.size()));
189 offset += varray.size();
190 }
191 }
192
193 if (const PointCloud *points = geometry_set.get_pointcloud()) {
194 if (const VArray varray = *points->attributes().lookup<float3>("position")) {
195 varray.materialize(positions.as_mutable_span().slice(offset, varray.size()));
196 offset += varray.size();
197 }
198 }
199
200 if (const Curves *curves_id = geometry_set.get_curves()) {
201 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
202 Span<float3> array = curves.evaluated_positions();
203 positions.as_mutable_span().slice(offset, array.size()).copy_from(array);
204 offset += array.size();
205 }
206
207 return hull_from_bullet(geometry_set.get_mesh(), positions);
208}
209
210static void convex_hull_grease_pencil(GeometrySet &geometry_set)
211{
212 using namespace blender::bke::greasepencil;
213
214 const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
215 Array<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
216
217 for (const int layer_index : grease_pencil.layers().index_range()) {
218 const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
219 if (drawing == nullptr) {
220 continue;
221 }
222 const bke::CurvesGeometry &curves = drawing->strokes();
223 const Span<float3> positions_span = curves.evaluated_positions();
224 if (positions_span.is_empty()) {
225 continue;
226 }
227 mesh_by_layer[layer_index] = hull_from_bullet(nullptr, positions_span);
228 }
229
230 if (mesh_by_layer.is_empty()) {
231 return;
232 }
233
234 InstancesComponent &instances_component =
235 geometry_set.get_component_for_write<InstancesComponent>();
236 bke::Instances *instances = instances_component.get_for_write();
237 if (instances == nullptr) {
238 instances = new bke::Instances();
239 instances_component.replace(instances);
240 }
241 for (Mesh *mesh : mesh_by_layer) {
242 if (!mesh) {
243 /* Add an empty reference so the number of layers and instances match.
244 * This makes it easy to reconstruct the layers afterwards and keep their attributes.
245 * Although in this particular case we don't propagate the attributes. */
246 const int handle = instances->add_reference(bke::InstanceReference());
247 instances->add_instance(handle, float4x4::identity());
248 continue;
249 }
250 GeometrySet temp_set = GeometrySet::from_mesh(mesh);
251 const int handle = instances->add_reference(bke::InstanceReference{temp_set});
252 instances->add_instance(handle, float4x4::identity());
253 }
254 geometry_set.replace_grease_pencil(nullptr);
255}
256
257#endif /* WITH_BULLET */
258
260{
261 GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
262
263#ifdef WITH_BULLET
264
265 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
266 Mesh *mesh = compute_hull(geometry_set);
267 if (mesh) {
269 }
270 geometry_set.replace_mesh(mesh);
271 if (geometry_set.has_grease_pencil()) {
272 convex_hull_grease_pencil(geometry_set);
273 }
275 });
276
277 params.set_output("Convex Hull", std::move(geometry_set));
278#else
279 params.error_message_add(NodeWarningType::Error,
280 TIP_("Disabled, Blender was compiled without Bullet"));
281 params.set_default_remaining_outputs();
282#endif /* WITH_BULLET */
283}
284
285static void node_register()
286{
287 static blender::bke::bNodeType ntype;
288
289 geo_node_type_base(&ntype, GEO_NODE_CONVEX_HULL, "Convex Hull", NODE_CLASS_GEOMETRY);
290 ntype.declare = node_declare;
293}
295
296} // namespace blender::nodes::node_geo_convex_hull_cc
Low-level operations for curves.
void CustomData_copy_data(const CustomData *source, CustomData *dest, int source_index, int dest_index, int count)
Low-level operations for grease pencil.
General operations, lookup, etc. for materials.
void BKE_id_material_eval_ensure_default_slot(struct ID *id)
Mesh * BKE_mesh_new_nomain_from_template(const Mesh *me_src, int verts_num, int edges_num, int faces_num, int corners_num)
Mesh * BKE_mesh_new_nomain(int verts_num, int edges_num, int faces_num, int corners_num)
#define NODE_CLASS_GEOMETRY
Definition BKE_node.hh:418
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
unsigned int uint
#define TIP_(msgid)
#define NOD_REGISTER_NODE(REGISTER_FUNC)
size_t size() const
constexpr const T * data() const
Definition BLI_span.hh:216
constexpr int64_t size() const
Definition BLI_span.hh:253
constexpr bool is_empty() const
Definition BLI_span.hh:261
void replace(Instances *instances, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
const bke::CurvesGeometry & strokes() const
local_group_size(16, 16) .push_constant(Type b
int len
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int count
void mesh_smooth_set(Mesh &mesh, bool use_smooth, bool keep_sharp_edges=false)
void node_register_type(bNodeType *ntype)
Definition node.cc:1708
void debug_randomize_mesh_order(Mesh *mesh)
Definition randomize.cc:220
static void node_declare(NodeDeclarationBuilder &b)
static void node_geo_exec(GeoNodeExecParams params)
VecBase< int32_t, 2 > int2
void geo_node_type_base(blender::bke::bNodeType *ntype, int type, const char *name, short nclass)
int plConvexHullGetFaceSize(plConvexHull hull, int n)
int plConvexHullNumLoops(plConvexHull hull)
void plConvexHullGetVertex(plConvexHull hull, int n, float coords[3], int *original_index)
void plConvexHullGetLoop(plConvexHull hull, int n, int *v_from, int *v_to)
int plConvexHullNumVertices(plConvexHull hull)
int plConvexHullGetReversedLoopIndex(plConvexHull hull, int n)
plConvexHull plConvexHullCompute(float(*coords)[3], int count)
void plConvexHullDelete(plConvexHull hull)
void plConvexHullGetFaceLoops(plConvexHull hull, int n, int *loops)
int plConvexHullNumFaces(plConvexHull hull)
static GeometrySet from_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
void keep_only_during_modify(Span< GeometryComponent::Type > component_types)
void modify_geometry_sets(ForeachSubGeometryCallback callback)
void replace_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
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