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