Blender V5.0
geometry/intern/mesh_boolean.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <iostream>
6
7#include "BKE_attribute.hh"
8#include "BKE_customdata.hh"
9#include "BKE_lib_id.hh"
10#include "BKE_mesh.hh"
11
12#include "BLI_alloca.h"
13#include "BLI_array.hh"
14#include "BLI_math_geom.h"
15#include "BLI_math_matrix.h"
16#include "BLI_math_matrix.hh"
17#include "BLI_math_vector.h"
18#include "BLI_mesh_boolean.hh"
19#include "BLI_mesh_intersect.hh"
20#include "BLI_span.hh"
21#include "BLI_string.h"
22#include "BLI_task.hh"
23#include "BLI_threads.h"
24#include "BLI_virtual_array.hh"
25
26#include "DNA_node_types.h"
27
28#include "GEO_mesh_boolean.hh"
30
31#include "bmesh.hh"
33
34// #define BENCHMARK_TIME
35#ifdef BENCHMARK_TIME
36# include "BLI_timeit.hh"
37# include <filesystem>
38# include <fstream>
39# define BENCHMARK_FILE "/tmp/blender_benchmark.csv"
40#endif
41
43
44/* -------------------------------------------------------------------- */
47
48#ifdef WITH_GMP
49
50constexpr int estimated_max_facelen = 100; /* Used for initial size of some Vectors. */
51
52/* Snap entries that are near 0 or 1 or -1 to those values.
53 * Sometimes Blender's rotation matrices for multiples of 90 degrees have
54 * tiny numbers where there should be zeros. That messes makes some things
55 * every so slightly non-coplanar when users expect coplanarity,
56 * so this is a hack to clean up such matrices.
57 * Would be better to change the transformation code itself.
58 */
59static float4x4 clean_transform(const float4x4 &mat)
60{
61 float4x4 cleaned;
62 const float fuzz = 1e-6f;
63 for (int i = 0; i < 4; i++) {
64 for (int j = 0; j < 4; j++) {
65 float f = mat[i][j];
66 if (fabsf(f) <= fuzz) {
67 f = 0.0f;
68 }
69 else if (fabsf(f - 1.0f) <= fuzz) {
70 f = 1.0f;
71 }
72 else if (fabsf(f + 1.0f) <= fuzz) {
73 f = -1.0f;
74 }
75 cleaned[i][j] = f;
76 }
77 }
78 return cleaned;
79}
80
81static float3 clean_float3(const float3 &co)
82{
83 float3 cleaned = co;
84 if (UNLIKELY(!isfinite(co[0]))) {
85 cleaned[0] = 0.0f;
86 }
87 if (UNLIKELY(!isfinite(co[1]))) {
88 cleaned[1] = 0.0f;
89 }
90 if (UNLIKELY(!isfinite(co[2]))) {
91 cleaned[2] = 0.0f;
92 }
93 return cleaned;
94}
95
96/* `MeshesToIMeshInfo` keeps track of information used when combining a number
97 * of `Mesh`es into a single `IMesh` for doing boolean on.
98 * Mostly this means keeping track of the index offsets for various mesh elements. */
99class MeshesToIMeshInfo {
100 public:
101 /* The input meshes, */
102 Span<const Mesh *> meshes;
103 /* Numbering the vertices of the meshes in order of meshes,
104 * at what offset does the vertex range for mesh[i] start? */
105 Array<int> mesh_vert_offset;
106 /* Similarly for edges of meshes. */
107 Array<int> mesh_edge_offset;
108 /* Similarly for faces of meshes. */
109 Array<int> mesh_face_offset;
110 /* For each Mesh vertex in all the meshes (with concatenated indexing),
111 * what is the IMesh Vert* allocated for it in the input IMesh? */
112 Array<const meshintersect::Vert *> mesh_to_imesh_vert;
113 /* Similarly for each Mesh face. */
114 Array<meshintersect::Face *> mesh_to_imesh_face;
115 /* Transformation matrix to transform a coordinate in the corresponding
116 * Mesh to the local space of the first Mesh. */
117 Array<float4x4> to_target_transform;
118 /* For each input mesh, whether or not their transform is negative. */
119 Array<bool> has_negative_transform;
120 /* For each input mesh, how to remap the material slot numbers to
121 * the material slots in the first mesh. */
122 Span<Array<short>> material_remaps;
123 /* Total number of input mesh vertices. */
124 int tot_meshes_verts;
125 /* Total number of input mesh edges. */
126 int tot_meshes_edges;
127 /* Total number of input mesh polys. */
128 int tot_meshes_polys;
129
130 int input_mesh_for_imesh_vert(int imesh_v) const;
131 int input_mesh_for_imesh_edge(int imesh_e) const;
132 int input_mesh_for_imesh_face(int imesh_f) const;
133 IndexRange input_face_for_orig_index(int orig_index,
134 const Mesh **r_orig_mesh,
135 int *r_orig_mesh_index,
136 int *r_index_in_orig_mesh) const;
137 void input_mvert_for_orig_index(int orig_index,
138 const Mesh **r_orig_mesh,
139 int *r_index_in_orig_mesh) const;
140 void input_medge_for_orig_index(int orig_index,
141 const Mesh **r_orig_mesh,
142 int *r_index_in_orig_mesh) const;
143};
144
145/* Given an index `imesh_v` in the `IMesh`, return the index of the
146 * input `Mesh` that contained the vertex that it came from. */
147int MeshesToIMeshInfo::input_mesh_for_imesh_vert(int imesh_v) const
148{
149 int n = int(mesh_vert_offset.size());
150 for (int i = 0; i < n - 1; ++i) {
151 if (imesh_v < mesh_vert_offset[i + 1]) {
152 return i;
153 }
154 }
155 return n - 1;
156}
157
158/* Given an index `imesh_e` used as an original index in the `IMesh`,
159 * return the index of the input `Mesh` that contained the vertex that it came from. */
160int MeshesToIMeshInfo::input_mesh_for_imesh_edge(int imesh_e) const
161{
162 int n = int(mesh_edge_offset.size());
163 for (int i = 0; i < n - 1; ++i) {
164 if (imesh_e < mesh_edge_offset[i + 1]) {
165 return i;
166 }
167 }
168 return n - 1;
169}
170
171/* Given an index `imesh_f` in the `IMesh`, return the index of the
172 * input `Mesh` that contained the face that it came from. */
173int MeshesToIMeshInfo::input_mesh_for_imesh_face(int imesh_f) const
174{
175 int n = int(mesh_face_offset.size());
176 for (int i = 0; i < n - 1; ++i) {
177 if (imesh_f < mesh_face_offset[i + 1]) {
178 return i;
179 }
180 }
181 return n - 1;
182}
183
184/* Given an index of an original face in the `IMesh`, find out the input
185 * `Mesh` that it came from and return it in `*r_orig_mesh`,
186 * and also return the index of that `Mesh` in `*r_orig_mesh_index`.
187 * Finally, return the index of the corresponding face in that `Mesh`
188 * in `*r_index_in_orig_mesh`. */
189IndexRange MeshesToIMeshInfo::input_face_for_orig_index(int orig_index,
190 const Mesh **r_orig_mesh,
191 int *r_orig_mesh_index,
192 int *r_index_in_orig_mesh) const
193{
194 int orig_mesh_index = input_mesh_for_imesh_face(orig_index);
195 BLI_assert(0 <= orig_mesh_index && orig_mesh_index < meshes.size());
196 const Mesh *mesh = meshes[orig_mesh_index];
197 const OffsetIndices faces = mesh->faces();
198 int index_in_mesh = orig_index - mesh_face_offset[orig_mesh_index];
199 BLI_assert(0 <= index_in_mesh && index_in_mesh < mesh->faces_num);
200 const IndexRange face = faces[index_in_mesh];
201 if (r_orig_mesh) {
202 *r_orig_mesh = mesh;
203 }
204 if (r_orig_mesh_index) {
205 *r_orig_mesh_index = orig_mesh_index;
206 }
207 if (r_index_in_orig_mesh) {
208 *r_index_in_orig_mesh = index_in_mesh;
209 }
210 return face;
211}
212
213/* Given an index of an original vertex in the `IMesh`, find out the input
214 * `Mesh` that it came from and return it in `*r_orig_mesh`.
215 * Also find the index of the vertex in that `Mesh` and return it in
216 * `*r_index_in_orig_mesh`. */
217void MeshesToIMeshInfo::input_mvert_for_orig_index(int orig_index,
218 const Mesh **r_orig_mesh,
219 int *r_index_in_orig_mesh) const
220{
221 int orig_mesh_index = input_mesh_for_imesh_vert(orig_index);
222 BLI_assert(0 <= orig_mesh_index && orig_mesh_index < meshes.size());
223 const Mesh *mesh = meshes[orig_mesh_index];
224 int index_in_mesh = orig_index - mesh_vert_offset[orig_mesh_index];
225 BLI_assert(0 <= index_in_mesh && index_in_mesh < mesh->verts_num);
226 if (r_orig_mesh) {
227 *r_orig_mesh = mesh;
228 }
229 if (r_index_in_orig_mesh) {
230 *r_index_in_orig_mesh = index_in_mesh;
231 }
232}
233
234/* Similarly for edges. */
235void MeshesToIMeshInfo::input_medge_for_orig_index(int orig_index,
236 const Mesh **r_orig_mesh,
237 int *r_index_in_orig_mesh) const
238{
239 int orig_mesh_index = input_mesh_for_imesh_edge(orig_index);
240 BLI_assert(0 <= orig_mesh_index && orig_mesh_index < meshes.size());
241 const Mesh *mesh = meshes[orig_mesh_index];
242 int index_in_mesh = orig_index - mesh_edge_offset[orig_mesh_index];
243 BLI_assert(0 <= index_in_mesh && index_in_mesh < mesh->edges_num);
244 if (r_orig_mesh) {
245 *r_orig_mesh = mesh;
246 }
247 if (r_index_in_orig_mesh) {
248 *r_index_in_orig_mesh = index_in_mesh;
249 }
250}
251
264static meshintersect::IMesh meshes_to_imesh(Span<const Mesh *> meshes,
265 Span<float4x4> transforms,
266 Span<Array<short>> material_remaps,
267 meshintersect::IMeshArena &arena,
268 MeshesToIMeshInfo *r_info)
269{
270 int nmeshes = meshes.size();
271 BLI_assert(nmeshes > 0);
272 r_info->meshes = meshes;
273 r_info->tot_meshes_verts = 0;
274 r_info->tot_meshes_polys = 0;
275 int &totvert = r_info->tot_meshes_verts;
276 int &totedge = r_info->tot_meshes_edges;
277 int &faces_num = r_info->tot_meshes_polys;
278 for (const Mesh *mesh : meshes) {
279 totvert += mesh->verts_num;
280 totedge += mesh->edges_num;
281 faces_num += mesh->faces_num;
282 }
283
284 /* Estimate the number of vertices and faces in the boolean output,
285 * so that the memory arena can reserve some space. It is OK if these
286 * estimates are wrong. */
287 const int estimate_num_outv = 3 * totvert;
288 const int estimate_num_outf = 4 * faces_num;
289 arena.reserve(estimate_num_outv, estimate_num_outf);
290 r_info->mesh_to_imesh_vert.reinitialize(totvert);
291 r_info->mesh_to_imesh_face.reinitialize(faces_num);
292 r_info->mesh_vert_offset.reinitialize(nmeshes);
293 r_info->mesh_edge_offset.reinitialize(nmeshes);
294 r_info->mesh_face_offset.reinitialize(nmeshes);
295 r_info->to_target_transform.reinitialize(nmeshes);
296 r_info->has_negative_transform.reinitialize(nmeshes);
297 r_info->material_remaps = material_remaps;
298 int v = 0;
299 int e = 0;
300 int f = 0;
301
302 /* Put these Vectors here, with a size unlikely to need resizing,
303 * so that the loop to make new Faces will likely not need to allocate
304 * over and over. */
305 Vector<const meshintersect::Vert *, estimated_max_facelen> face_vert;
306 Vector<int, estimated_max_facelen> face_edge_orig;
307
308 /* To convert the coordinates of meshes 1, 2, etc. into the local space
309 * of the target, multiply each transform by the inverse of the
310 * target matrix. Exact Boolean works better if these matrices are 'cleaned'
311 * -- see the comment for the `clean_transform` function, above. */
312
313 /* For each input `Mesh`, make `Vert`s and `Face`s for the corresponding
314 * vertices and polygons, and keep track of the original indices (using the
315 * concatenating offset scheme) inside the `Vert`s and `Face`s.
316 * When making `Face`s, we also put in the original indices for edges that
317 * make up the polygons using the same scheme. */
318 for (int mi : meshes.index_range()) {
319 const Mesh *mesh = meshes[mi];
320 r_info->mesh_vert_offset[mi] = v;
321 r_info->mesh_edge_offset[mi] = e;
322 r_info->mesh_face_offset[mi] = f;
323 /* Get matrix that transforms a coordinate in meshes[mi]'s local space
324 * to the target space. */
325 r_info->to_target_transform[mi] = transforms.is_empty() ? float4x4::identity() :
326 clean_transform(transforms[mi]);
327 r_info->has_negative_transform[mi] = math::is_negative(r_info->to_target_transform[mi]);
328
329 /* All meshes 1 and up will be transformed into the local space of operand 0.
330 * Historical behavior of the modifier has been to flip the faces of any meshes
331 * that would have a negative transform if you do that. */
332 bool need_face_flip = r_info->has_negative_transform[mi] != r_info->has_negative_transform[0];
333
334 Vector<meshintersect::Vert *> verts(mesh->verts_num);
335 const Span<float3> vert_positions = mesh->vert_positions();
336 const OffsetIndices faces = mesh->faces();
337 const Span<int> corner_verts = mesh->corner_verts();
338 const Span<int> corner_edges = mesh->corner_edges();
339
340 /* Allocate verts
341 * Skip the matrix multiplication for each point when there is no transform for a mesh,
342 * for example when the first mesh is already in the target space. (Note the logic
343 * directly above, which uses an identity matrix with an empty input transform). */
344 if (transforms.is_empty() || r_info->to_target_transform[mi] == float4x4::identity()) {
345 threading::parallel_for(vert_positions.index_range(), 2048, [&](IndexRange range) {
346 for (int i : range) {
347 float3 co = clean_float3(vert_positions[i]);
348 mpq3 mco = mpq3(co.x, co.y, co.z);
349 double3 dco(mco[0].get_d(), mco[1].get_d(), mco[2].get_d());
350 verts[i] = new meshintersect::Vert(mco, dco, meshintersect::NO_INDEX, i);
351 }
352 });
353 }
354 else {
355 threading::parallel_for(vert_positions.index_range(), 2048, [&](IndexRange range) {
356 for (int i : range) {
357 float3 co = math::transform_point(r_info->to_target_transform[mi],
358 clean_float3(vert_positions[i]));
359 mpq3 mco = mpq3(co.x, co.y, co.z);
360 double3 dco(mco[0].get_d(), mco[1].get_d(), mco[2].get_d());
361 verts[i] = new meshintersect::Vert(mco, dco, meshintersect::NO_INDEX, i);
362 }
363 });
364 }
365 for (int i : vert_positions.index_range()) {
366 r_info->mesh_to_imesh_vert[v] = arena.add_or_find_vert(verts[i]);
367 ++v;
368 }
369
370 for (const int face_i : faces.index_range()) {
371 const IndexRange face = faces[face_i];
372 int flen = face.size();
373 face_vert.resize(flen);
374 face_edge_orig.resize(flen);
375 for (int i = 0; i < flen; ++i) {
376 const int corner_i = face[i];
377 int mverti = r_info->mesh_vert_offset[mi] + corner_verts[corner_i];
378 const meshintersect::Vert *fv = r_info->mesh_to_imesh_vert[mverti];
379 if (need_face_flip) {
380 face_vert[flen - i - 1] = fv;
381 int iedge = i < flen - 1 ? flen - i - 2 : flen - 1;
382 face_edge_orig[iedge] = e + corner_edges[corner_i];
383 }
384 else {
385 face_vert[i] = fv;
386 face_edge_orig[i] = e + corner_edges[corner_i];
387 }
388 }
389 r_info->mesh_to_imesh_face[f] = arena.add_face(face_vert, f, face_edge_orig);
390 ++f;
391 }
392 e += mesh->edges_num;
393 }
394 return meshintersect::IMesh(r_info->mesh_to_imesh_face);
395}
396
397/* Copy vertex attributes, including customdata, from `orig_mv` to `mv`.
398 * `mv` is in `dest_mesh` with index `mv_index`.
399 * The `orig_mv` vertex came from Mesh `orig_me` and had index `index_in_orig_me` there. */
400static void copy_vert_attributes(Mesh *dest_mesh,
401 const Mesh *orig_me,
402 int mv_index,
403 int index_in_orig_me)
404{
405 /* For all layers in the orig mesh, copy the layer information. */
406 CustomData *target_cd = &dest_mesh->vert_data;
407 const CustomData *source_cd = &orig_me->vert_data;
408 for (int source_layer_i = 0; source_layer_i < source_cd->totlayer; ++source_layer_i) {
409 const eCustomDataType ty = eCustomDataType(source_cd->layers[source_layer_i].type);
410 if (StringRef(source_cd->layers->name) == "position") {
411 continue;
412 }
413 const char *name = source_cd->layers[source_layer_i].name;
414 int target_layer_i = CustomData_get_named_layer_index(target_cd, ty, name);
415 /* Not all layers were merged in target: some are marked CD_FLAG_NOCOPY
416 * and some are not in the CD_MASK_MESH.vdata. */
417 if (target_layer_i != -1) {
419 source_cd, target_cd, source_layer_i, target_layer_i, index_in_orig_me, mv_index, 1);
420 }
421 }
422}
423
424/* Similar to copy_vert_attributes but for face attributes. */
425static void copy_face_attributes(Mesh *dest_mesh,
426 const Mesh *orig_me,
427 int face_index,
428 int index_in_orig_me,
429 Span<short> material_remap,
430 MutableSpan<int> dst_material_indices)
431{
432 CustomData *target_cd = &dest_mesh->face_data;
433 const CustomData *source_cd = &orig_me->face_data;
434 for (int source_layer_i = 0; source_layer_i < source_cd->totlayer; ++source_layer_i) {
435 const eCustomDataType ty = eCustomDataType(source_cd->layers[source_layer_i].type);
436 const char *name = source_cd->layers[source_layer_i].name;
437 int target_layer_i = CustomData_get_named_layer_index(target_cd, ty, name);
438 if (target_layer_i != -1) {
440 source_cd, target_cd, source_layer_i, target_layer_i, index_in_orig_me, face_index, 1);
441 }
442 }
443
444 /* Fix material indices after they have been transferred as a generic attribute. */
445 const VArray<int> src_material_indices = *orig_me->attributes().lookup_or_default<int>(
446 "material_index", bke::AttrDomain::Face, 0);
447 const int src_index = src_material_indices[index_in_orig_me];
448 if (material_remap.index_range().contains(src_index)) {
449 const int remapped_index = material_remap[src_index];
450 dst_material_indices[face_index] = remapped_index >= 0 ? remapped_index : src_index;
451 }
452 else {
453 dst_material_indices[face_index] = src_index;
454 }
455 BLI_assert(dst_material_indices[face_index] >= 0);
456}
457
458/* Similar to copy_vert_attributes but for edge attributes. */
459static void copy_edge_attributes(Mesh *dest_mesh,
460 const Mesh *orig_me,
461 int medge_index,
462 int index_in_orig_me)
463{
464 CustomData *target_cd = &dest_mesh->edge_data;
465 const CustomData *source_cd = &orig_me->edge_data;
466 for (int source_layer_i = 0; source_layer_i < source_cd->totlayer; ++source_layer_i) {
467 const eCustomDataType ty = eCustomDataType(source_cd->layers[source_layer_i].type);
468 if (ty == CD_PROP_INT32_2D) {
469 if (STREQ(source_cd->layers[source_layer_i].name, ".edge_verts")) {
470 continue;
471 }
472 }
473 const char *name = source_cd->layers[source_layer_i].name;
474 int target_layer_i = CustomData_get_named_layer_index(target_cd, ty, name);
475 if (target_layer_i != -1) {
477 source_cd, target_cd, source_layer_i, target_layer_i, index_in_orig_me, medge_index, 1);
478 }
479 }
480}
481
492static int fill_orig_loops(const meshintersect::Face *f,
493 const IndexRange orig_face,
494 const Mesh *orig_me,
495 int orig_me_index,
496 MeshesToIMeshInfo &mim,
497 MutableSpan<int> r_orig_loops)
498{
499 r_orig_loops.fill(-1);
500 const Span<int> orig_corner_verts = orig_me->corner_verts();
501
502 int orig_mplen = orig_face.size();
503 if (f->size() != orig_mplen) {
504 return 0;
505 }
506 BLI_assert(r_orig_loops.size() == orig_mplen);
507 /* We'll look for the case where the first vertex in f has an original vertex
508 * that is the same as one in orig_me (after correcting for offset in mim meshes).
509 * Then see that loop and any subsequent ones have the same start and end vertex.
510 * This may miss some cases of partial alignment, but that's OK since discovering
511 * aligned loops is only an optimization to avoid some re-interpolation.
512 */
513 int first_orig_v = f->vert[0]->orig;
514 if (first_orig_v == meshintersect::NO_INDEX) {
515 return 0;
516 }
517 /* It is possible that the original vert was merged with another in another mesh. */
518 if (orig_me_index != mim.input_mesh_for_imesh_vert(first_orig_v)) {
519 return 0;
520 }
521 int orig_me_vert_offset = mim.mesh_vert_offset[orig_me_index];
522 int first_orig_v_in_orig_me = first_orig_v - orig_me_vert_offset;
523 BLI_assert(0 <= first_orig_v_in_orig_me && first_orig_v_in_orig_me < orig_me->verts_num);
524 /* Assume all vertices in each face is unique. */
525 int offset = -1;
526 for (int i = 0; i < orig_mplen; ++i) {
527 int loop_i = i + orig_face.start();
528 if (orig_corner_verts[loop_i] == first_orig_v_in_orig_me) {
529 offset = i;
530 break;
531 }
532 }
533 if (offset == -1) {
534 return 0;
535 }
536 int num_orig_loops_found = 0;
537 for (int mp_loop_index = 0; mp_loop_index < orig_mplen; ++mp_loop_index) {
538 int orig_mp_loop_index = (mp_loop_index + offset) % orig_mplen;
539 const int vert_i = orig_corner_verts[orig_face.start() + orig_mp_loop_index];
540 int fv_orig = f->vert[mp_loop_index]->orig;
541 if (fv_orig != meshintersect::NO_INDEX) {
542 fv_orig -= orig_me_vert_offset;
543 if (fv_orig < 0 || fv_orig >= orig_me->verts_num) {
544 fv_orig = meshintersect::NO_INDEX;
545 }
546 }
547 if (vert_i == fv_orig) {
548 const int vert_next =
549 orig_corner_verts[orig_face.start() + ((orig_mp_loop_index + 1) % orig_mplen)];
550 int fvnext_orig = f->vert[(mp_loop_index + 1) % orig_mplen]->orig;
551 if (fvnext_orig != meshintersect::NO_INDEX) {
552 fvnext_orig -= orig_me_vert_offset;
553 if (fvnext_orig < 0 || fvnext_orig >= orig_me->verts_num) {
554 fvnext_orig = meshintersect::NO_INDEX;
555 }
556 }
557 if (vert_next == fvnext_orig) {
558 r_orig_loops[mp_loop_index] = orig_face.start() + orig_mp_loop_index;
559 ++num_orig_loops_found;
560 }
561 }
562 }
563 return num_orig_loops_found;
564}
565
566/* Fill `cos_2d` with the 2d coordinates found by projection face `face` along
567 * its normal. Also fill in r_axis_mat with the matrix that does that projection.
568 * But before projecting, also transform the 3d coordinate by multiplying by trans_mat.
569 * `cos_2d` should have room for `face.size()` entries. */
570static void get_poly2d_cos(const Mesh *mesh,
571 const IndexRange face,
572 float (*cos_2d)[2],
573 const float4x4 &trans_mat,
574 float r_axis_mat[3][3])
575{
576 const Span<float3> positions = mesh->vert_positions();
577 const Span<int> corner_verts = mesh->corner_verts();
578 const Span<int> face_verts = corner_verts.slice(face);
579
580 /* Project coordinates to 2d in cos_2d, using normal as projection axis. */
581 const float3 axis_dominant = bke::mesh::face_normal_calc(positions, face_verts);
582 axis_dominant_v3_to_m3(r_axis_mat, axis_dominant);
583 for (const int i : face_verts.index_range()) {
584 float3 co = positions[face_verts[i]];
585 co = math::transform_point(trans_mat, co);
586 *reinterpret_cast<float2 *>(&cos_2d[i]) = (float3x3(r_axis_mat) * co).xy();
587 }
588}
589
590/* For the loops of `face`, see if the face is unchanged from `orig_face`, and if so,
591 * copy the Loop attributes from corresponding loops to corresponding loops.
592 * Otherwise, interpolate the Loop attributes in the face `orig_face`. */
593static void copy_or_interp_loop_attributes(Mesh *dest_mesh,
594 const meshintersect::Face *f,
595 const IndexRange face,
596 const IndexRange orig_face,
597 const Mesh *orig_me,
598 int orig_me_index,
599 MeshesToIMeshInfo &mim)
600{
601 Array<int> orig_loops(face.size());
602 int norig = fill_orig_loops(f, orig_face, orig_me, orig_me_index, mim, orig_loops);
603 /* We may need these arrays if we have to interpolate Loop attributes rather than just copy.
604 * Right now, trying Array<float[2]> complains, so declare cos_2d a different way. */
605 float (*cos_2d)[2];
606 Array<float> weights;
607 Array<const void *> src_blocks_ofs;
608 float axis_mat[3][3];
609 if (norig != face.size()) {
610 /* We will need to interpolate. Make `cos_2d` hold 2d-projected coordinates of `orig_face`,
611 * which are transformed into object 0's local space before projecting.
612 * At this point we cannot yet calculate the interpolation weights, as they depend on
613 * the coordinate where interpolation is to happen, but we can allocate the needed arrays,
614 * so they don't have to be allocated per-layer. */
615 cos_2d = (float (*)[2])BLI_array_alloca(cos_2d, orig_face.size());
616 weights = Array<float>(orig_face.size());
617 src_blocks_ofs = Array<const void *>(orig_face.size());
618 get_poly2d_cos(orig_me, orig_face, cos_2d, mim.to_target_transform[orig_me_index], axis_mat);
619 }
620 CustomData *target_cd = &dest_mesh->corner_data;
621 const Span<float3> dst_positions = dest_mesh->vert_positions();
622 const Span<int> dst_corner_verts = dest_mesh->corner_verts();
623 for (int i = 0; i < face.size(); ++i) {
624 int loop_index = face[i];
625 int orig_loop_index = norig > 0 ? orig_loops[i] : -1;
626 const CustomData *source_cd = &orig_me->corner_data;
627 if (orig_loop_index == -1) {
628 /* Will need interpolation weights for this loop's vertex's coordinates.
629 * The coordinate needs to be projected into 2d, just like the interpolating face's
630 * coordinates were. The `dest_mesh` coordinates are already in object 0 local space. */
631 float co[2];
632 mul_v2_m3v3(co, axis_mat, dst_positions[dst_corner_verts[loop_index]]);
633 interp_weights_poly_v2(weights.data(), cos_2d, orig_face.size(), co);
634 }
635 for (int source_layer_i = 0; source_layer_i < source_cd->totlayer; ++source_layer_i) {
636 const eCustomDataType ty = eCustomDataType(source_cd->layers[source_layer_i].type);
637 if (STR_ELEM(source_cd->layers[source_layer_i].name, ".corner_vert", ".corner_edge")) {
638 continue;
639 }
640 const char *name = source_cd->layers[source_layer_i].name;
641 int target_layer_i = CustomData_get_named_layer_index(target_cd, ty, name);
642 if (target_layer_i == -1) {
643 continue;
644 }
645 if (orig_loop_index != -1) {
647 source_cd, target_cd, source_layer_i, target_layer_i, orig_loop_index, loop_index, 1);
648 }
649 else {
650 /* NOTE: although CustomData_bmesh_interp_n function has bmesh in its name, nothing about
651 * it is BMesh-specific. We can't use CustomData_interp because it assumes that
652 * all source layers exist in the dest.
653 * A non bmesh version could have the benefit of not copying data into src_blocks_ofs -
654 * using the contiguous data instead. TODO: add to the custom data API. */
655 int target_layer_type_index = CustomData_get_named_layer(target_cd, ty, name);
656 if (!CustomData_layer_has_interp(source_cd, source_layer_i)) {
657 continue;
658 }
659 int source_layer_type_index = source_layer_i - source_cd->typemap[ty];
660 BLI_assert(target_layer_type_index != -1 && source_layer_type_index >= 0);
661 const int size = CustomData_sizeof(ty);
662 for (int j = 0; j < orig_face.size(); ++j) {
663 const void *layer = CustomData_get_layer_n(source_cd, ty, source_layer_type_index);
664 src_blocks_ofs[j] = POINTER_OFFSET(layer, size * (orig_face[j]));
665 }
666 void *dst_layer = CustomData_get_layer_n_for_write(
667 target_cd, ty, target_layer_type_index, dest_mesh->corners_num);
668 void *dst_block_ofs = POINTER_OFFSET(dst_layer, size * loop_index);
670 src_blocks_ofs.data(),
671 weights.data(),
672 orig_face.size(),
673 dst_block_ofs,
674 target_layer_i);
675 }
676 }
677 }
678}
679
686static void merge_vertex_loop_face_customdata_layers(Mesh *target, MeshesToIMeshInfo &mim)
687{
688 for (int mesh_index = 1; mesh_index < mim.meshes.size(); ++mesh_index) {
689 const Mesh *mesh = mim.meshes[mesh_index];
690 if (mesh->verts_num) {
692 &target->vert_data,
693 CD_MASK_MESH.vmask,
695 target->verts_num);
696 }
697 if (mesh->corners_num) {
699 &target->corner_data,
700 CD_MASK_MESH.lmask,
702 target->corners_num);
703 }
704 if (mesh->faces_num) {
706 &target->face_data,
707 CD_MASK_MESH.pmask,
709 target->faces_num);
710 }
711 }
712}
713
714static void merge_edge_customdata_layers(Mesh *target, MeshesToIMeshInfo &mim)
715{
716 for (int mesh_index = 0; mesh_index < mim.meshes.size(); ++mesh_index) {
717 const Mesh *mesh = mim.meshes[mesh_index];
718 if (mesh->edges_num) {
720 &target->edge_data,
721 CD_MASK_MESH.emask,
723 target->edges_num);
724 }
725 }
726}
727
732static Mesh *imesh_to_mesh(meshintersect::IMesh *im, MeshesToIMeshInfo &mim)
733{
734 constexpr int dbg_level = 0;
735
736 im->populate_vert();
737 int out_totvert = im->vert_size();
738 int out_faces_num = im->face_size();
739 int out_totloop = 0;
740 for (const meshintersect::Face *f : im->faces()) {
741 out_totloop += f->size();
742 }
743 /* Will calculate edges later. */
745 mim.meshes[0], out_totvert, 0, out_faces_num, out_totloop);
746
747 merge_vertex_loop_face_customdata_layers(result, mim);
748 /* Set the vertex coordinate values and other data. */
749 MutableSpan<float3> positions = result->vert_positions_for_write();
750 for (int vi : im->vert_index_range()) {
751 const meshintersect::Vert *v = im->vert(vi);
752 if (v->orig != meshintersect::NO_INDEX) {
753 const Mesh *orig_me;
754 int index_in_orig_me;
755 mim.input_mvert_for_orig_index(v->orig, &orig_me, &index_in_orig_me);
756 copy_vert_attributes(result, orig_me, vi, index_in_orig_me);
757 }
758 copy_v3fl_v3db(positions[vi], v->co);
759 }
760
761 /* Set the loop-start and total-loops for each output face,
762 * and set the vertices in the appropriate loops. */
763 bke::SpanAttributeWriter<int> dst_material_indices =
764 result->attributes_for_write().lookup_or_add_for_write_only_span<int>("material_index",
765 bke::AttrDomain::Face);
766 int cur_loop_index = 0;
767 MutableSpan<int> dst_corner_verts = result->corner_verts_for_write();
768 MutableSpan<int> dst_face_offsets = result->face_offsets_for_write();
769 for (int fi : im->face_index_range()) {
770 const meshintersect::Face *f = im->face(fi);
771 const Mesh *orig_me;
772 int index_in_orig_me;
773 int orig_me_index;
774 const IndexRange orig_face = mim.input_face_for_orig_index(
775 f->orig, &orig_me, &orig_me_index, &index_in_orig_me);
776 dst_face_offsets[fi] = cur_loop_index;
777 for (int j : f->index_range()) {
778 const meshintersect::Vert *vf = f->vert[j];
779 const int vfi = im->lookup_vert(vf);
780 dst_corner_verts[cur_loop_index] = vfi;
781 ++cur_loop_index;
782 }
783
784 copy_face_attributes(result,
785 orig_me,
786 fi,
787 index_in_orig_me,
788 (mim.material_remaps.size() > 0) ?
789 mim.material_remaps[orig_me_index].as_span() :
790 Span<short>(),
791 dst_material_indices.span);
792 copy_or_interp_loop_attributes(result,
793 f,
794 IndexRange(dst_face_offsets[fi], f->size()),
795 orig_face,
796 orig_me,
797 orig_me_index,
798 mim);
799 }
800 dst_material_indices.finish();
801
802 bke::mesh_calc_edges(*result, false, false);
803 merge_edge_customdata_layers(result, mim);
804
805 /* Now that the MEdges are populated, we can copy over the required attributes and custom layers.
806 */
807 const OffsetIndices dst_polys = result->faces();
808 const Span<int> dst_corner_edges = result->corner_edges();
809 for (int fi : im->face_index_range()) {
810 const meshintersect::Face *f = im->face(fi);
811 const IndexRange face = dst_polys[fi];
812 for (int j : f->index_range()) {
813 if (f->edge_orig[j] != meshintersect::NO_INDEX) {
814 const Mesh *orig_me;
815 int index_in_orig_me;
816 mim.input_medge_for_orig_index(f->edge_orig[j], &orig_me, &index_in_orig_me);
817 int e_index = dst_corner_edges[face[j]];
818 copy_edge_attributes(result, orig_me, e_index, index_in_orig_me);
819 }
820 }
821 }
822
823 if (dbg_level > 0) {
824 BKE_mesh_validate(result, true, true);
825 }
826 return result;
827}
828
829static meshintersect::BoolOpType operation_to_mesh_arr_mode(const Operation operation)
830{
831 switch (operation) {
832 case Operation::Intersect:
833 return meshintersect::BoolOpType::Intersect;
834 case Operation::Union:
835 return meshintersect::BoolOpType::Union;
836 case Operation::Difference:
837 return meshintersect::BoolOpType::Difference;
838 }
840 return meshintersect::BoolOpType::None;
841}
842
843static Mesh *mesh_boolean_mesh_arr(Span<const Mesh *> meshes,
844 Span<float4x4> transforms,
845 Span<Array<short>> material_remaps,
846 const bool use_self,
847 const bool hole_tolerant,
848 const meshintersect::BoolOpType boolean_mode,
849 Vector<int> *r_intersecting_edges)
850{
851 BLI_assert(transforms.is_empty() || meshes.size() == transforms.size());
852 BLI_assert(material_remaps.is_empty() || material_remaps.size() == meshes.size());
853 if (meshes.size() <= 0) {
854 return nullptr;
855 }
856
857 const int dbg_level = 0;
858 if (dbg_level > 0) {
859 std::cout << "\nOLD_MESH_INTERSECT, nmeshes = " << meshes.size() << "\n";
860 }
861 MeshesToIMeshInfo mim;
862 meshintersect::IMeshArena arena;
863 meshintersect::IMesh m_in = meshes_to_imesh(meshes, transforms, material_remaps, arena, &mim);
864 std::function<int(int)> shape_fn = [&mim](int f) {
865 for (int mi = 0; mi < mim.mesh_face_offset.size() - 1; ++mi) {
866 if (f < mim.mesh_face_offset[mi + 1]) {
867 return mi;
868 }
869 }
870 return int(mim.mesh_face_offset.size()) - 1;
871 };
872 meshintersect::IMesh m_out = boolean_mesh(
873 m_in, boolean_mode, meshes.size(), shape_fn, use_self, hole_tolerant, nullptr, &arena);
874 if (dbg_level > 0) {
875 std::cout << m_out;
876 write_obj_mesh(m_out, "m_out");
877 }
878
879 Mesh *result = imesh_to_mesh(&m_out, mim);
880
881 /* Store intersecting edge indices. */
882 if (r_intersecting_edges != nullptr) {
883 const OffsetIndices faces = result->faces();
884 const Span<int> corner_edges = result->corner_edges();
885 for (int fi : m_out.face_index_range()) {
886 const meshintersect::Face &face = *m_out.face(fi);
887 const IndexRange mesh_face = faces[fi];
888 for (int i : face.index_range()) {
889 if (face.is_intersect[i]) {
890 int e_index = corner_edges[mesh_face[i]];
891 r_intersecting_edges->append(e_index);
892 }
893 }
894 }
895 }
896
897 return result;
898}
899
900#endif // WITH_GMP
901
903
904/* -------------------------------------------------------------------- */
907
908/* has no meaning for faces, do this so we can tell which face is which */
909#define BM_FACE_TAG BM_ELEM_SELECT_UV
910
915static int face_boolean_operand(BMFace *f, void * /*user_data*/)
916{
917 return BM_elem_flag_test(f, BM_FACE_TAG) ? 0 : 1;
918}
919
920/* Create a BMesh that is the concatenation of the given meshes.
921 * The corresponding mesh-to-world transformations are also given,
922 * as well as a target_tranform.
923 * A triangulation is also calculated and returned in the last two
924 * parameters.
925 * The faces of the first mesh are tagged with BM_FACE_TAG so that the
926 * face_boolean_operand() function can distinguish those faces from the
927 * rest.
928 * The caller is responsible for using `BM_mesh_free` on the returned
929 * BMesh, and calling `MEM_freeN` on the returned looptris.
930 *
931 * TODO: maybe figure out how to use the join_geometries() function
932 * to join all the meshes into one mesh first, and then convert
933 * that single mesh to BMesh. Issues with that include needing
934 * to apply the transforms and material remaps.
935 */
937 Span<float4x4> transforms,
938 Span<Array<short>> material_remaps,
939 Array<std::array<BMLoop *, 3>> &r_looptris)
940{
941 const int meshes_num = meshes.size();
942 BLI_assert(meshes_num >= 1);
943 Array<bool> is_negative_transform(meshes_num);
944 Array<bool> is_flip(meshes_num);
945 const int tsize = transforms.size();
946 for (const int i : IndexRange(meshes_num)) {
947 if (tsize > i) {
948 is_negative_transform[i] = math::is_negative(transforms[i]);
949 is_flip[i] = is_negative_transform[i] != is_negative_transform[0];
950 }
951 else {
952 is_negative_transform[i] = false;
953 is_flip[i] = false;
954 }
955 }
956
957 /* Make a BMesh that will be a concatenation of the elements of all the meshes */
958 BMAllocTemplate allocsize;
959 allocsize.totvert = 0;
960 allocsize.totedge = 0;
961 allocsize.totloop = 0;
962 allocsize.totface = 0;
963 for (const int i : meshes.index_range()) {
964 allocsize.totvert += meshes[i]->verts_num;
965 allocsize.totedge += meshes[i]->edges_num;
966 allocsize.totloop += meshes[i]->corners_num;
967 allocsize.totface += meshes[i]->faces_num;
968 }
969
970 BMeshCreateParams bmesh_create_params{};
971 BMesh *bm = BM_mesh_create(&allocsize, &bmesh_create_params);
972
974 bm, const_cast<const Mesh **>(meshes.begin()), meshes_num, &allocsize);
975
976 BMeshFromMeshParams bmesh_from_mesh_params{};
977 bmesh_from_mesh_params.calc_face_normal = true;
978 bmesh_from_mesh_params.calc_vert_normal = true;
979
980 Array<int> verts_end(meshes_num);
981 Array<int> faces_end(meshes_num);
982 verts_end[0] = meshes[0]->verts_num;
983 faces_end[0] = meshes[0]->faces_num;
984 for (const int i : meshes.index_range()) {
985 /* Append meshes[i] elements and data to bm. */
986 BM_mesh_bm_from_me(bm, meshes[i], &bmesh_from_mesh_params);
987 if (i > 0) {
988 verts_end[i] = verts_end[i - 1] + meshes[i]->verts_num;
989 faces_end[i] = faces_end[i - 1] + meshes[i]->faces_num;
990 if (is_flip[i]) {
991 /* Need to flip face normals to match that of mesh[0]. */
992 const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS);
994 for (int j = faces_end[i - 1]; j < faces_end[i]; j++) {
995 BMFace *efa = bm->ftable[j];
996 BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true);
997 }
998 }
999 }
1000 }
1001
1002 /* Make a triangulation of all polys before transforming vertices
1003 * so we can use the original normals. */
1004 const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop);
1005 r_looptris.reinitialize(looptris_tot);
1007
1008 /* Transform the vertices that into the desired target_transform space. */
1009 BMIter iter;
1010 BMVert *eve;
1011 int i = 0;
1012 int mesh_index = 0;
1013 BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) {
1014 copy_v3_v3(eve->co, math::transform_point(transforms[mesh_index], float3(eve->co)));
1015 ++i;
1016 if (i == verts_end[mesh_index]) {
1017 mesh_index++;
1018 }
1019 }
1020
1021 /* Transform face normals and tag the first-operand faces.
1022 * Also, apply material remaps. */
1023 BMFace *efa;
1024 i = 0;
1025 mesh_index = 0;
1026 BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) {
1027 copy_v3_v3(efa->no, math::transform_direction(transforms[mesh_index], float3(efa->no)));
1028 if (is_negative_transform[mesh_index]) {
1029 negate_v3(efa->no);
1030 }
1031 normalize_v3(efa->no);
1032
1033 /* Temp tag used in `face_boolean_operand()` to test for operand 0. */
1034 if (i < faces_end[0]) {
1036 }
1037
1038 /* Remap material. */
1039 int cur_mat = efa->mat_nr;
1040 if (cur_mat < material_remaps[mesh_index].size()) {
1041 int new_mat = material_remaps[mesh_index][cur_mat];
1042 if (new_mat >= 0) {
1043 efa->mat_nr = material_remaps[mesh_index][cur_mat];
1044 }
1045 }
1046
1047 ++i;
1048 if (i == faces_end[mesh_index]) {
1049 mesh_index++;
1050 }
1051 }
1052
1053 return bm;
1054}
1055
1056static int operation_to_float_mode(const Operation operation)
1057{
1058 switch (operation) {
1061 case Operation::Union:
1065 }
1068}
1069
1071 Span<float4x4> transforms,
1072 Span<Array<short>> material_remaps,
1073 const int boolean_mode,
1074 Vector<int> * /*r_intersecting_edges*/)
1075{
1076 BLI_assert(meshes.size() == transforms.size() || transforms.size() == 0);
1077 BLI_assert(material_remaps.size() == 0 || material_remaps.size() == meshes.size());
1078 if (meshes.is_empty()) {
1079 return nullptr;
1080 }
1081
1082 if (meshes.size() == 1) {
1083 /* The float solver doesn't do self union. Just return nullptr, which will
1084 * cause geometry nodes to leave the input as is. */
1085 return BKE_mesh_copy_for_eval(*meshes[0]);
1086 }
1087
1089 if (meshes.size() == 2) {
1090 BMesh *bm = mesh_bm_concat(meshes, transforms, material_remaps, looptris);
1092 looptris,
1094 nullptr,
1095 false,
1096 false,
1097 true,
1098 true,
1099 false,
1100 false,
1101 boolean_mode,
1102 1e-6f);
1103 Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, meshes[0]);
1105 return result;
1106 }
1107
1108 /* Iteratively operate with each operand. */
1109 Array<const Mesh *> two_meshes = {meshes[0], meshes[1]};
1110 Array<float4x4> two_transforms = {transforms[0], transforms[1]};
1111 Array<Array<short>> two_remaps = {material_remaps[0], material_remaps[1]};
1112 Mesh *prev_result_mesh = nullptr;
1113 for (const int i : meshes.index_range().drop_back(1)) {
1114 BMesh *bm = mesh_bm_concat(two_meshes, two_transforms, two_remaps, looptris);
1116 looptris,
1118 nullptr,
1119 false,
1120 false,
1121 true,
1122 true,
1123 false,
1124 false,
1125 boolean_mode,
1126 1e-6f);
1127 Mesh *result_i_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, meshes[0]);
1129 if (prev_result_mesh != nullptr) {
1130 /* Except in the first iteration, two_meshes[0] holds the intermediate
1131 * mesh result from the previous iteration. */
1132 BKE_id_free(nullptr, prev_result_mesh);
1133 }
1134 if (i < meshes.size() - 2) {
1135 two_meshes[0] = result_i_mesh;
1136 two_meshes[1] = meshes[i + 2];
1137 two_transforms[0] = float4x4::identity();
1138 two_transforms[1] = transforms[i + 2];
1139 two_remaps[0] = {};
1140 two_remaps[1] = material_remaps[i + 2];
1141 prev_result_mesh = result_i_mesh;
1142 }
1143 else {
1144 return result_i_mesh;
1145 }
1146 }
1147
1149 return nullptr;
1150}
1151
1152#ifdef BENCHMARK_TIME
1153/* Append benchmark time and other information for boolean operations to a /tmp file. */
1154static void write_boolean_benchmark_time(
1155 StringRef solver, StringRef op, const Mesh *mesh1, const Mesh *mesh2, float time_ms)
1156{
1157 StringRef mesh1_name = mesh1 ? mesh1->id.name + 2 : "";
1158 StringRef mesh2_name = mesh2 ? mesh2->id.name + 2 : "";
1159 const int num_faces_1 = mesh1 ? mesh1->faces_num : 0;
1160 const int num_faces_2 = mesh2 ? mesh2->faces_num : 0;
1161 const int num_tris_1 = mesh1 ? mesh1->corner_tris().size() : 0;
1162 const int num_tris_2 = mesh2 ? mesh2->corner_tris().size() : 0;
1163 const int threads = BLI_system_num_threads_override_get();
1164
1165 /* Add header line if file doesn't exist yet. */
1166 bool first_time = false;
1167 if (!std::filesystem::exists(BENCHMARK_FILE)) {
1168 first_time = true;
1169 }
1170 /* Open the benchmark file in append mode. */
1171 std::ofstream outfile(BENCHMARK_FILE, std::ios_base::app);
1172
1173 if (outfile.is_open()) {
1174 if (first_time) {
1175 outfile << "solver,op,mesh1,mesh2,face1,face2,tris1,tris2,time_in_ms,threads" << std::endl;
1176 }
1177 outfile << solver << "," << op << ",\"" << mesh1_name << "\",\"" << mesh2_name << "\","
1178 << num_faces_1 << "," << num_faces_2 << "," << num_tris_1 << "," << num_tris_2 << ","
1179 << time_ms << "," << threads << std::endl;
1180 outfile.close();
1181 }
1182 else {
1183 std::cerr << "Unable to open benchmark file: " << BENCHMARK_FILE << std::endl;
1184 }
1185}
1186#endif
1187
1189
1191 Span<float4x4> transforms,
1192 Span<Array<short>> material_remaps,
1193 BooleanOpParameters op_params,
1194 Solver solver,
1195 Vector<int> *r_intersecting_edges,
1196 BooleanError *r_error)
1197{
1198 Mesh *ans = nullptr;
1199#ifdef BENCHMARK_TIME
1200 const timeit::TimePoint start_time = timeit::Clock::now();
1201#endif
1202 switch (solver) {
1203 case Solver::Float:
1204 *r_error = BooleanError::NoError;
1205 ans = mesh_boolean_float(meshes,
1206 transforms,
1207 material_remaps,
1209 r_intersecting_edges);
1210 break;
1211 case Solver::MeshArr:
1212#ifdef WITH_GMP
1213 *r_error = BooleanError::NoError;
1214 ans = mesh_boolean_mesh_arr(meshes,
1215 transforms,
1216 material_remaps,
1217 !op_params.no_self_intersections,
1218 !op_params.watertight,
1219 operation_to_mesh_arr_mode(op_params.boolean_mode),
1220 r_intersecting_edges);
1221#else
1223#endif
1224 break;
1225 case Solver::Manifold:
1226#ifdef WITH_MANIFOLD
1228 meshes, transforms, material_remaps, op_params, r_intersecting_edges, r_error);
1229#else
1231#endif
1232 break;
1233 default:
1235 }
1236#ifdef BENCHMARK_TIME
1237 const timeit::TimePoint end_time = timeit::Clock::now();
1238 const timeit::Nanoseconds duration = end_time - start_time;
1239 float time_ms = duration.count() / 1.0e6f;
1240 Operation op = op_params.boolean_mode;
1241 const char *opstr = op == Operation::Intersect ?
1242 "intersect" :
1243 (op == Operation::Union ? "union" : "difference");
1244 const Mesh *mesh1 = meshes.size() > 0 ? meshes[0] : nullptr;
1245 const Mesh *mesh2 = meshes.size() > 0 ? meshes[1] : nullptr;
1246 const char *solverstr = solver == Solver::Float ? "float" :
1247 solver == Solver::MeshArr ? "mesharr" :
1248 "manifold";
1249 write_boolean_benchmark_time(solverstr, opstr, mesh1, mesh2, time_ms);
1250#endif
1251 return ans;
1252}
1253
1254} // namespace blender::geometry::boolean
CustomData interface, see also DNA_customdata_types.h.
int CustomData_sizeof(eCustomDataType type)
int CustomData_get_offset(const CustomData *data, eCustomDataType type)
const void * CustomData_get_layer_n(const CustomData *data, eCustomDataType type, int n)
int CustomData_get_named_layer(const CustomData *data, eCustomDataType type, blender::StringRef name)
bool CustomData_layer_has_interp(const CustomData *data, int layer_n)
@ CD_SET_DEFAULT
int CustomData_get_named_layer_index(const CustomData *data, eCustomDataType type, blender::StringRef name)
void CustomData_copy_data_layer(const CustomData *source, CustomData *dest, int src_layer_index, int dst_layer_index, int src_index, int dst_index, int count)
void CustomData_bmesh_interp_n(CustomData *data, const void **src_blocks, const float *weights, int count, void *dst_block_ofs, int n)
bool CustomData_merge_layout(const CustomData *source, CustomData *dest, eCustomDataMask mask, eCDAllocType alloctype, int totelem)
void * CustomData_get_layer_n_for_write(CustomData *data, eCustomDataType type, int n, int totelem)
const CustomData_MeshMasks CD_MASK_MESH
void BKE_id_free(Main *bmain, void *idv)
Mesh * BKE_mesh_new_nomain_from_template(const Mesh *me_src, int verts_num, int edges_num, int faces_num, int corners_num)
bool BKE_mesh_validate(Mesh *mesh, bool do_verbose, bool cddata_check_mask)
Mesh * BKE_mesh_copy_for_eval(const Mesh &source)
Mesh * BKE_mesh_from_bmesh_for_eval_nomain(BMesh *bm, const CustomData_MeshMasks *cd_mask_extra, const Mesh *me_settings)
#define BLI_array_alloca(arr, realsize)
Definition BLI_alloca.h:18
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
void axis_dominant_v3_to_m3(float r_mat[3][3], const float normal[3])
Normal to x,y matrix.
MINLINE int poly_to_tri_count(int poly_count, int corner_count)
void interp_weights_poly_v2(float w[], float v[][2], int n, const float co[2])
void mul_v2_m3v3(float r[2], const float M[3][3], const float a[3])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void negate_v3(float r[3])
MINLINE void copy_v3fl_v3db(float r[3], const double a[3])
MINLINE float normalize_v3(float n[3])
#define STR_ELEM(...)
Definition BLI_string.h:661
int BLI_system_num_threads_override_get(void)
Definition threads.cc:294
#define UNLIKELY(x)
#define POINTER_OFFSET(v, ofs)
#define STREQ(a, b)
@ CD_PROP_INT32_2D
struct Mesh Mesh
void BM_mesh_copy_init_customdata_from_mesh_array(BMesh *bm_dst, const Mesh *me_src_array[], const int me_src_array_len, const BMAllocTemplate *allocsize)
#define BM_elem_flag_test(ele, hflag)
#define BM_elem_flag_enable(ele, hflag)
bool BM_mesh_intersect(BMesh *bm, const blender::Span< std::array< BMLoop *, 3 > > looptris, int(*test_fn)(BMFace *f, void *user_data), void *user_data, const bool use_self, const bool use_separate, const bool use_dissolve, const bool use_island_connect, const bool use_partial_connect, const bool use_edge_tag, const int boolean_mode, const float eps)
@ BMESH_ISECT_BOOLEAN_DIFFERENCE
@ BMESH_ISECT_BOOLEAN_NONE
@ BMESH_ISECT_BOOLEAN_UNION
@ BMESH_ISECT_BOOLEAN_ISECT
#define BM_ITER_MESH(ele, iter, bm, itype)
@ BM_VERTS_OF_MESH
@ BM_FACES_OF_MESH
BMesh * bm
void BM_mesh_free(BMesh *bm)
BMesh Free Mesh.
void BM_mesh_elem_table_ensure(BMesh *bm, const char htype)
BMesh * BM_mesh_create(const BMAllocTemplate *allocsize, const BMeshCreateParams *params)
BMesh Make Mesh.
void BM_mesh_bm_from_me(BMesh *bm, const Mesh *mesh, const BMeshFromMeshParams *params)
void BM_mesh_calc_tessellation_beauty(BMesh *bm, MutableSpan< std::array< BMLoop *, 3 > > looptris)
#define BM_FACE
void BM_face_normal_flip_ex(BMesh *bm, BMFace *f, const int cd_loop_mdisp_offset, const bool use_loop_mdisp_flip)
Face Flip Normal.
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
ATTR_WARN_UNUSED_RESULT const BMVert * v
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
const T * data() const
Definition BLI_array.hh:312
AttributeSet attributes
constexpr int64_t size() const
constexpr int64_t start() const
constexpr int64_t size() const
Definition BLI_span.hh:493
constexpr void fill(const T &value) const
Definition BLI_span.hh:517
constexpr Span slice(int64_t start, int64_t size) const
Definition BLI_span.hh:137
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr bool is_empty() const
Definition BLI_span.hh:260
void append(const T &value)
void resize(const int64_t new_size)
constexpr IndexRange drop_back(int64_t n) const
constexpr bool contains(int64_t value) const
constexpr int64_t size() const
Definition BLI_span.hh:252
constexpr IndexRange index_range() const
Definition BLI_span.hh:401
constexpr const T * begin() const
Definition BLI_span.hh:220
constexpr bool is_empty() const
Definition BLI_span.hh:260
nullptr float
static float verts[][3]
MatBase< 4, 4 > float4x4
MatBase< 3, 3 > float3x3
static char faces[256]
static int operation_to_float_mode(const Operation operation)
Mesh * mesh_boolean_manifold(Span< const Mesh * > meshes, Span< float4x4 > transforms, Span< Array< short > > material_remaps, BooleanOpParameters op_params, Vector< int > *r_intersecting_edges, BooleanError *r_error)
static Mesh * mesh_boolean_float(Span< const Mesh * > meshes, Span< float4x4 > transforms, Span< Array< short > > material_remaps, const int boolean_mode, Vector< int > *)
static BMesh * mesh_bm_concat(Span< const Mesh * > meshes, Span< float4x4 > transforms, Span< Array< short > > material_remaps, Array< std::array< BMLoop *, 3 > > &r_looptris)
Mesh * mesh_boolean(Span< const Mesh * > meshes, Span< float4x4 > transforms, Span< Array< short > > material_remaps, BooleanOpParameters op_params, Solver solver, Vector< int > *r_intersecting_edges, BooleanError *r_error)
static int face_boolean_operand(BMFace *f, void *)
bool is_negative(const MatBase< T, 3, 3 > &mat)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
VecBase< T, 3 > transform_point(const CartesianBasis &basis, const VecBase< T, 3 > &v)
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
std::chrono::nanoseconds Nanoseconds
Definition BLI_timeit.hh:21
Clock::time_point TimePoint
Definition BLI_timeit.hh:20
MatBase< float, 4, 4 > float4x4
VecBase< float, 3 > float3
const char * name
#define fabsf
short mat_nr
float no[3]
float co[3]
CustomDataLayer * layers
char name[258]
Definition DNA_ID.h:432
int corners_num
CustomData edge_data
int edges_num
CustomData corner_data
CustomData face_data
CustomData vert_data
int faces_num
int verts_num
i
Definition text_draw.cc:230