Blender V4.5
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
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").propagate_all_instance_attributes();
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 }
51
52 /* Copy vertices. */
53 MutableSpan<float3> dst_positions = result->vert_positions_for_write();
54 for (const int i : IndexRange(verts_num)) {
55 float3 dummy_co;
56 int original_index;
57 plConvexHullGetVertex(hull, i, dummy_co, &original_index);
58 if (UNLIKELY(!coords.index_range().contains(original_index))) {
60 dst_positions[i] = float3(0);
61 continue;
62 }
63 dst_positions[i] = coords[original_index];
64 }
65
66 /* Copy edges and loops. */
67
68 /* NOTE: ConvexHull from Bullet uses a half-edge data structure
69 * for its mesh. To convert that, each half-edge needs to be converted
70 * to a loop and edges need to be created from that. */
71 Array<int> corner_verts(loops_num);
72 Array<int> corner_edges(loops_num);
73 uint edge_index = 0;
74 MutableSpan<int2> edges = result->edges_for_write();
75
76 for (const int i : IndexRange(loops_num)) {
77 int v_from;
78 int v_to;
79 plConvexHullGetLoop(hull, i, &v_from, &v_to);
80
81 corner_verts[i] = v_from;
82 /* Add edges for ascending order loops only. */
83 if (v_from < v_to) {
84 edges[edge_index] = int2(v_from, v_to);
85
86 /* Write edge index into both loops that have it. */
87 int reverse_index = plConvexHullGetReversedLoopIndex(hull, i);
88 corner_edges[i] = edge_index;
89 corner_edges[reverse_index] = edge_index;
90 edge_index++;
91 }
92 }
93 if (edges_num == 1) {
94 /* In this case there are no loops. */
95 edges[0] = int2(0, 1);
96 edge_index++;
97 }
98 BLI_assert(edge_index == edges_num);
99
100 /* Copy faces. */
101 Array<int> loops;
102 int j = 0;
103 MutableSpan<int> face_offsets = result->face_offsets_for_write();
104 MutableSpan<int> mesh_corner_verts = result->corner_verts_for_write();
105 MutableSpan<int> mesh_corner_edges = result->corner_edges_for_write();
106 int dst_corner = 0;
107
108 for (const int i : IndexRange(faces_num)) {
109 const int len = plConvexHullGetFaceSize(hull, i);
110
111 BLI_assert(len > 2);
112
113 /* Get face loop indices. */
114 loops.reinitialize(len);
115 plConvexHullGetFaceLoops(hull, i, loops.data());
116
117 face_offsets[i] = j;
118 for (const int k : IndexRange(len)) {
119 mesh_corner_verts[dst_corner] = corner_verts[loops[k]];
120 mesh_corner_edges[dst_corner] = corner_edges[loops[k]];
121 dst_corner++;
122 }
123 j += len;
124 }
125
126 plConvexHullDelete(hull);
127 return result;
128}
129
130static Mesh *compute_hull(const GeometrySet &geometry_set)
131{
132 int span_count = 0;
133 int count = 0;
134 int total_num = 0;
135
136 Span<float3> positions_span;
137
138 if (const Mesh *mesh = geometry_set.get_mesh()) {
139 count++;
140 if (const VArray positions = *mesh->attributes().lookup<float3>("position")) {
141 if (positions.is_span()) {
142 span_count++;
143 positions_span = positions.get_internal_span();
144 }
145 total_num += positions.size();
146 }
147 }
148
149 if (const PointCloud *points = geometry_set.get_pointcloud()) {
150 count++;
151 if (const VArray positions = *points->attributes().lookup<float3>("position")) {
152 if (positions.is_span()) {
153 span_count++;
154 positions_span = positions.get_internal_span();
155 }
156 total_num += positions.size();
157 }
158 }
159
160 if (const Curves *curves_id = geometry_set.get_curves()) {
161 count++;
162 span_count++;
163 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
164 positions_span = curves.evaluated_positions();
165 total_num += positions_span.size();
166 }
167
168 if (count == 0) {
169 return nullptr;
170 }
171
172 /* If there is only one positions virtual array and it is already contiguous, avoid copying
173 * all of the positions and instead pass the span directly to the convex hull function. */
174 if (span_count == 1 && count == 1) {
175 return hull_from_bullet(geometry_set.get_mesh(), positions_span);
176 }
177
178 Array<float3> positions(total_num);
179 int offset = 0;
180
181 if (const Mesh *mesh = geometry_set.get_mesh()) {
182 if (const VArray varray = *mesh->attributes().lookup<float3>("position")) {
183 varray.materialize(positions.as_mutable_span().slice(offset, varray.size()));
184 offset += varray.size();
185 }
186 }
187
188 if (const PointCloud *points = geometry_set.get_pointcloud()) {
189 if (const VArray varray = *points->attributes().lookup<float3>("position")) {
190 varray.materialize(positions.as_mutable_span().slice(offset, varray.size()));
191 offset += varray.size();
192 }
193 }
194
195 if (const Curves *curves_id = geometry_set.get_curves()) {
196 const bke::CurvesGeometry &curves = curves_id->geometry.wrap();
197 Span<float3> array = curves.evaluated_positions();
198 positions.as_mutable_span().slice(offset, array.size()).copy_from(array);
199 offset += array.size();
200 }
201
202 return hull_from_bullet(geometry_set.get_mesh(), positions);
203}
204
205static void convex_hull_grease_pencil(GeometrySet &geometry_set)
206{
207 using namespace blender::bke::greasepencil;
208
209 const GreasePencil &grease_pencil = *geometry_set.get_grease_pencil();
210 Array<Mesh *> mesh_by_layer(grease_pencil.layers().size(), nullptr);
211
212 for (const int layer_index : grease_pencil.layers().index_range()) {
213 const Drawing *drawing = grease_pencil.get_eval_drawing(grease_pencil.layer(layer_index));
214 if (drawing == nullptr) {
215 continue;
216 }
217 const bke::CurvesGeometry &curves = drawing->strokes();
218 const Span<float3> positions_span = curves.evaluated_positions();
219 if (positions_span.is_empty()) {
220 continue;
221 }
222 mesh_by_layer[layer_index] = hull_from_bullet(nullptr, positions_span);
223 }
224
225 if (mesh_by_layer.is_empty()) {
226 return;
227 }
228
229 InstancesComponent &instances_component =
230 geometry_set.get_component_for_write<InstancesComponent>();
231 bke::Instances *instances = instances_component.get_for_write();
232 if (instances == nullptr) {
233 instances = new bke::Instances();
234 instances_component.replace(instances);
235 }
236 for (Mesh *mesh : mesh_by_layer) {
237 if (!mesh) {
238 /* Add an empty reference so the number of layers and instances match.
239 * This makes it easy to reconstruct the layers afterwards and keep their attributes.
240 * Although in this particular case we don't propagate the attributes. */
241 const int handle = instances->add_reference(bke::InstanceReference());
242 instances->add_instance(handle, float4x4::identity());
243 continue;
244 }
245 GeometrySet temp_set = GeometrySet::from_mesh(mesh);
246 const int handle = instances->add_reference(bke::InstanceReference{temp_set});
247 instances->add_instance(handle, float4x4::identity());
248 }
249 geometry_set.replace_grease_pencil(nullptr);
250}
251
252#endif /* WITH_BULLET */
253
255{
256 GeometrySet geometry_set = params.extract_input<GeometrySet>("Geometry");
257
258#ifdef WITH_BULLET
259
260 geometry_set.modify_geometry_sets([&](GeometrySet &geometry_set) {
261 Mesh *mesh = compute_hull(geometry_set);
262 if (mesh) {
264 }
265 geometry_set.replace_mesh(mesh);
266 if (geometry_set.has_grease_pencil()) {
267 convex_hull_grease_pencil(geometry_set);
268 }
270 });
271
272 params.set_output("Convex Hull", std::move(geometry_set));
273#else
274 params.error_message_add(NodeWarningType::Error,
275 TIP_("Disabled, Blender was compiled without Bullet"));
276 params.set_default_remaining_outputs();
277#endif /* WITH_BULLET */
278}
279
280static void node_register()
281{
282 static blender::bke::bNodeType ntype;
283 geo_node_type_base(&ntype, "GeometryNodeConvexHull", GEO_NODE_CONVEX_HULL);
284 ntype.ui_name = "Convex Hull";
285 ntype.ui_description =
286 "Create a mesh that encloses all points in the input geometry with the smallest number of "
287 "points";
288 ntype.enum_name_legacy = "CONVEX_HULL";
290 ntype.declare = node_declare;
293}
295
296} // 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:447
#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:301
void reinitialize(const int64_t new_size)
Definition BLI_array.hh:398
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:2748
void mesh_smooth_set(Mesh &mesh, bool use_smooth, bool keep_sharp_edges=false)
void debug_randomize_mesh_order(Mesh *mesh)
Definition randomize.cc:217
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_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:226
std::string ui_description
Definition BKE_node.hh:232
NodeGeometryExecFunction geometry_node_execute
Definition BKE_node.hh:347
const char * enum_name_legacy
Definition BKE_node.hh:235
NodeDeclareFunction declare
Definition BKE_node.hh:355
static GeometrySet from_mesh(Mesh *mesh, GeometryOwnershipType ownership=GeometryOwnershipType::Owned)
i
Definition text_draw.cc:230
uint len