bmo_hull.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. /*
  2. * ***** BEGIN GPL LICENSE BLOCK *****
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License
  6. * as published by the Free Software Foundation; either version 2
  7. * of the License, or (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software Foundation,
  16. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. *
  18. * Contributor(s): Nicholas Bishop
  19. *
  20. * ***** END GPL LICENSE BLOCK *****
  21. */
  22. /** \file blender/bmesh/operators/bmo_hull.c
  23. * \ingroup bmesh
  24. *
  25. * Create a convex hull using bullet physics library.
  26. */
  27. #ifdef WITH_BULLET
  28. #include "MEM_guardedalloc.h"
  29. #include "BLI_array.h"
  30. #include "BLI_listbase.h"
  31. #include "BLI_math.h"
  32. #include "Bullet-C-Api.h"
  33. /* XXX: using 128 for totelem and pchunk of mempool, no idea what good
  34. * values would be though */
  35. #include "bmesh.h"
  36. #include "intern/bmesh_operators_private.h" /* own include */
  37. /* Internal operator flags */
  38. typedef enum {
  39. HULL_FLAG_INPUT = (1 << 0),
  40. HULL_FLAG_INTERIOR_ELE = (1 << 1),
  41. HULL_FLAG_OUTPUT_GEOM = (1 << 2),
  42. HULL_FLAG_DEL = (1 << 3),
  43. HULL_FLAG_HOLE = (1 << 4)
  44. } HullFlags;
  45. /* Store hull triangles separate from BMesh faces until the end; this
  46. * way we don't have to worry about cleaning up extraneous edges or
  47. * incorrectly deleting existing geometry. */
  48. typedef struct HullTriangle {
  49. BMVert *v[3];
  50. float no[3];
  51. int skip;
  52. } HullTriangle;
  53. /*************************** Hull Triangles ***************************/
  54. static void hull_add_triangle(
  55. BMesh *bm, GSet *hull_triangles, BLI_mempool *pool,
  56. BMVert *v1, BMVert *v2, BMVert *v3)
  57. {
  58. HullTriangle *t;
  59. int i;
  60. t = BLI_mempool_calloc(pool);
  61. t->v[0] = v1;
  62. t->v[1] = v2;
  63. t->v[2] = v3;
  64. /* Mark triangles vertices as not interior */
  65. for (i = 0; i < 3; i++)
  66. BMO_vert_flag_disable(bm, t->v[i], HULL_FLAG_INTERIOR_ELE);
  67. BLI_gset_insert(hull_triangles, t);
  68. normal_tri_v3(t->no, v1->co, v2->co, v3->co);
  69. }
  70. static BMFace *hull_find_example_face(BMesh *bm, BMEdge *e)
  71. {
  72. BMIter iter;
  73. BMFace *f;
  74. BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
  75. if (BMO_face_flag_test(bm, f, HULL_FLAG_INPUT) ||
  76. BMO_face_flag_test(bm, f, HULL_FLAG_OUTPUT_GEOM) == false)
  77. {
  78. return f;
  79. }
  80. }
  81. return NULL;
  82. }
  83. static void hull_output_triangles(BMesh *bm, GSet *hull_triangles)
  84. {
  85. GSetIterator iter;
  86. GSET_ITER (iter, hull_triangles) {
  87. HullTriangle *t = BLI_gsetIterator_getKey(&iter);
  88. int i;
  89. if (!t->skip) {
  90. BMEdge *edges[3] = {
  91. BM_edge_create(bm, t->v[0], t->v[1], NULL, BM_CREATE_NO_DOUBLE),
  92. BM_edge_create(bm, t->v[1], t->v[2], NULL, BM_CREATE_NO_DOUBLE),
  93. BM_edge_create(bm, t->v[2], t->v[0], NULL, BM_CREATE_NO_DOUBLE)
  94. };
  95. BMFace *f, *example = NULL;
  96. f = BM_face_exists(t->v, 3);
  97. if (f != NULL) {
  98. /* If the operator is run with "use_existing_faces"
  99. * disabled, but an output face in the hull is the
  100. * same as a face in the existing mesh, it should not
  101. * be marked as unused or interior. */
  102. BMO_face_flag_enable(bm, f, HULL_FLAG_OUTPUT_GEOM);
  103. BMO_face_flag_disable(bm, f, HULL_FLAG_HOLE);
  104. BMO_face_flag_disable(bm, f, HULL_FLAG_INTERIOR_ELE);
  105. }
  106. else {
  107. /* Look for an adjacent face that existed before the hull */
  108. for (i = 0; i < 3; i++) {
  109. if (!example)
  110. example = hull_find_example_face(bm, edges[i]);
  111. }
  112. /* Create new hull face */
  113. f = BM_face_create_verts(bm, t->v, 3, example, BM_CREATE_NO_DOUBLE, true);
  114. BM_face_copy_shared(bm, f, NULL, NULL);
  115. }
  116. /* Mark face for 'geom.out' slot and select */
  117. BMO_face_flag_enable(bm, f, HULL_FLAG_OUTPUT_GEOM);
  118. BM_face_select_set(bm, f, true);
  119. /* Mark edges for 'geom.out' slot */
  120. for (i = 0; i < 3; i++) {
  121. BMO_edge_flag_enable(bm, edges[i], HULL_FLAG_OUTPUT_GEOM);
  122. }
  123. }
  124. else {
  125. /* Mark input edges for 'geom.out' slot */
  126. for (i = 0; i < 3; i++) {
  127. const int next = (i == 2 ? 0 : i + 1);
  128. BMEdge *e = BM_edge_exists(t->v[i], t->v[next]);
  129. if (e &&
  130. BMO_edge_flag_test(bm, e, HULL_FLAG_INPUT) &&
  131. !BMO_edge_flag_test(bm, e, HULL_FLAG_HOLE))
  132. {
  133. BMO_edge_flag_enable(bm, e, HULL_FLAG_OUTPUT_GEOM);
  134. }
  135. }
  136. }
  137. /* Mark verts for 'geom.out' slot */
  138. for (i = 0; i < 3; i++) {
  139. BMO_vert_flag_enable(bm, t->v[i], HULL_FLAG_OUTPUT_GEOM);
  140. }
  141. }
  142. }
  143. /***************************** Final Edges ****************************/
  144. typedef struct {
  145. GHash *edges;
  146. BLI_mempool *base_pool, *link_pool;
  147. } HullFinalEdges;
  148. static LinkData *final_edges_find_link(ListBase *adj, BMVert *v)
  149. {
  150. LinkData *link;
  151. for (link = adj->first; link; link = link->next) {
  152. if (link->data == v)
  153. return link;
  154. }
  155. return NULL;
  156. }
  157. static int hull_final_edges_lookup(
  158. HullFinalEdges *final_edges,
  159. BMVert *v1, BMVert *v2)
  160. {
  161. ListBase *adj;
  162. /* Use lower vertex pointer for hash key */
  163. if (v1 > v2)
  164. SWAP(BMVert *, v1, v2);
  165. adj = BLI_ghash_lookup(final_edges->edges, v1);
  166. if (!adj)
  167. return false;
  168. return !!final_edges_find_link(adj, v2);
  169. }
  170. /* Used for checking whether a pre-existing edge lies on the hull */
  171. static HullFinalEdges *hull_final_edges(GSet *hull_triangles)
  172. {
  173. HullFinalEdges *final_edges;
  174. GSetIterator iter;
  175. final_edges = MEM_callocN(sizeof(HullFinalEdges), "HullFinalEdges");
  176. final_edges->edges = BLI_ghash_ptr_new("final edges ghash");
  177. final_edges->base_pool = BLI_mempool_create(sizeof(ListBase), 0, 128, BLI_MEMPOOL_NOP);
  178. final_edges->link_pool = BLI_mempool_create(sizeof(LinkData), 0, 128, BLI_MEMPOOL_NOP);
  179. GSET_ITER (iter, hull_triangles) {
  180. HullTriangle *t = BLI_gsetIterator_getKey(&iter);
  181. LinkData *link;
  182. int i;
  183. for (i = 0; i < 3; i++) {
  184. BMVert *v1 = t->v[i];
  185. BMVert *v2 = t->v[(i + 1) % 3];
  186. ListBase *adj;
  187. /* Use lower vertex pointer for hash key */
  188. if (v1 > v2)
  189. SWAP(BMVert *, v1, v2);
  190. adj = BLI_ghash_lookup(final_edges->edges, v1);
  191. if (!adj) {
  192. adj = BLI_mempool_calloc(final_edges->base_pool);
  193. BLI_ghash_insert(final_edges->edges, v1, adj);
  194. }
  195. if (!final_edges_find_link(adj, v2)) {
  196. link = BLI_mempool_calloc(final_edges->link_pool);
  197. link->data = v2;
  198. BLI_addtail(adj, link);
  199. }
  200. }
  201. }
  202. return final_edges;
  203. }
  204. static void hull_final_edges_free(HullFinalEdges *final_edges)
  205. {
  206. BLI_ghash_free(final_edges->edges, NULL, NULL);
  207. BLI_mempool_destroy(final_edges->base_pool);
  208. BLI_mempool_destroy(final_edges->link_pool);
  209. MEM_freeN(final_edges);
  210. }
  211. /**************************** Final Output ****************************/
  212. static void hull_remove_overlapping(
  213. BMesh *bm, GSet *hull_triangles,
  214. HullFinalEdges *final_edges)
  215. {
  216. GSetIterator hull_iter;
  217. GSET_ITER (hull_iter, hull_triangles) {
  218. HullTriangle *t = BLI_gsetIterator_getKey(&hull_iter);
  219. BMIter bm_iter1, bm_iter2;
  220. BMFace *f;
  221. bool f_on_hull;
  222. BM_ITER_ELEM (f, &bm_iter1, t->v[0], BM_FACES_OF_VERT) {
  223. BMEdge *e;
  224. /* Check that all the face's edges are on the hull,
  225. * otherwise can't reuse it */
  226. f_on_hull = true;
  227. BM_ITER_ELEM (e, &bm_iter2, f, BM_EDGES_OF_FACE) {
  228. if (!hull_final_edges_lookup(final_edges, e->v1, e->v2)) {
  229. f_on_hull = false;
  230. break;
  231. }
  232. }
  233. /* Note: can't change ghash while iterating, so mark
  234. * with 'skip' flag rather than deleting triangles */
  235. if (BM_vert_in_face(t->v[1], f) &&
  236. BM_vert_in_face(t->v[2], f) && f_on_hull)
  237. {
  238. t->skip = true;
  239. BMO_face_flag_disable(bm, f, HULL_FLAG_INTERIOR_ELE);
  240. BMO_face_flag_enable(bm, f, HULL_FLAG_HOLE);
  241. }
  242. }
  243. }
  244. }
  245. static void hull_mark_interior_elements(
  246. BMesh *bm, BMOperator *op,
  247. HullFinalEdges *final_edges)
  248. {
  249. BMEdge *e;
  250. BMFace *f;
  251. BMOIter oiter;
  252. /* Check for interior edges too */
  253. BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
  254. if (!hull_final_edges_lookup(final_edges, e->v1, e->v2))
  255. BMO_edge_flag_enable(bm, e, HULL_FLAG_INTERIOR_ELE);
  256. }
  257. /* Mark all input faces as interior, some may be unmarked in
  258. * hull_remove_overlapping() */
  259. BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
  260. BMO_face_flag_enable(bm, f, HULL_FLAG_INTERIOR_ELE);
  261. }
  262. }
  263. static void hull_tag_unused(BMesh *bm, BMOperator *op)
  264. {
  265. BMIter iter;
  266. BMOIter oiter;
  267. BMVert *v;
  268. BMEdge *e;
  269. BMFace *f;
  270. /* Mark vertices, edges, and faces that are already marked
  271. * interior (i.e. were already part of the input, but not part of
  272. * the hull), but that aren't also used by elements outside the
  273. * input set */
  274. BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
  275. if (BMO_vert_flag_test(bm, v, HULL_FLAG_INTERIOR_ELE)) {
  276. bool del = true;
  277. BM_ITER_ELEM (e, &iter, v, BM_EDGES_OF_VERT) {
  278. if (!BMO_edge_flag_test(bm, e, HULL_FLAG_INPUT)) {
  279. del = false;
  280. break;
  281. }
  282. }
  283. BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) {
  284. if (!BMO_face_flag_test(bm, f, HULL_FLAG_INPUT)) {
  285. del = false;
  286. break;
  287. }
  288. }
  289. if (del) {
  290. BMO_vert_flag_enable(bm, v, HULL_FLAG_DEL);
  291. }
  292. }
  293. }
  294. BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
  295. if (BMO_edge_flag_test(bm, e, HULL_FLAG_INTERIOR_ELE)) {
  296. bool del = true;
  297. BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
  298. if (!BMO_face_flag_test(bm, f, HULL_FLAG_INPUT)) {
  299. del = false;
  300. break;
  301. }
  302. }
  303. if (del) {
  304. BMO_edge_flag_enable(bm, e, HULL_FLAG_DEL);
  305. }
  306. }
  307. }
  308. BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
  309. if (BMO_face_flag_test(bm, f, HULL_FLAG_INTERIOR_ELE)) {
  310. BMO_face_flag_enable(bm, f, HULL_FLAG_DEL);
  311. }
  312. }
  313. }
  314. static void hull_tag_holes(BMesh *bm, BMOperator *op)
  315. {
  316. BMIter iter;
  317. BMOIter oiter;
  318. BMFace *f;
  319. BMEdge *e;
  320. /* Unmark any hole faces if they are isolated or part of a
  321. * border */
  322. BMO_ITER (f, &oiter, op->slots_in, "input", BM_FACE) {
  323. if (BMO_face_flag_test(bm, f, HULL_FLAG_HOLE)) {
  324. BM_ITER_ELEM (e, &iter, f, BM_EDGES_OF_FACE) {
  325. if (BM_edge_is_boundary(e)) {
  326. BMO_face_flag_disable(bm, f, HULL_FLAG_HOLE);
  327. break;
  328. }
  329. }
  330. }
  331. }
  332. /* Mark edges too if all adjacent faces are holes and the edge is
  333. * not already isolated */
  334. BMO_ITER (e, &oiter, op->slots_in, "input", BM_EDGE) {
  335. bool hole = true;
  336. bool any_faces = false;
  337. BM_ITER_ELEM (f, &iter, e, BM_FACES_OF_EDGE) {
  338. any_faces = true;
  339. if (!BMO_face_flag_test(bm, f, HULL_FLAG_HOLE)) {
  340. hole = false;
  341. break;
  342. }
  343. }
  344. if (hole && any_faces)
  345. BMO_edge_flag_enable(bm, e, HULL_FLAG_HOLE);
  346. }
  347. }
  348. static int hull_input_vert_count(BMOperator *op)
  349. {
  350. BMOIter oiter;
  351. BMVert *v;
  352. int count = 0;
  353. BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
  354. count++;
  355. }
  356. return count;
  357. }
  358. static BMVert **hull_input_verts_copy(
  359. BMOperator *op,
  360. const int num_input_verts)
  361. {
  362. BMOIter oiter;
  363. BMVert *v;
  364. BMVert **input_verts = MEM_callocN(sizeof(*input_verts) *
  365. num_input_verts, AT);
  366. int i = 0;
  367. BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
  368. input_verts[i++] = v;
  369. }
  370. return input_verts;
  371. }
  372. static float (*hull_verts_for_bullet(
  373. BMVert **input_verts,
  374. const int num_input_verts))[3]
  375. {
  376. float (*coords)[3] = MEM_callocN(sizeof(*coords) * num_input_verts, __func__);
  377. int i;
  378. for (i = 0; i < num_input_verts; i++) {
  379. copy_v3_v3(coords[i], input_verts[i]->co);
  380. }
  381. return coords;
  382. }
  383. static BMVert **hull_verts_from_bullet(
  384. plConvexHull hull,
  385. BMVert **input_verts,
  386. const int num_input_verts)
  387. {
  388. const int num_verts = plConvexHullNumVertices(hull);
  389. BMVert **hull_verts = MEM_mallocN(sizeof(*hull_verts) *
  390. num_verts, AT);
  391. int i;
  392. for (i = 0; i < num_verts; i++) {
  393. float co[3];
  394. int original_index;
  395. plConvexHullGetVertex(hull, i, co, &original_index);
  396. if (original_index >= 0 && original_index < num_input_verts) {
  397. hull_verts[i] = input_verts[original_index];
  398. }
  399. else
  400. BLI_assert(!"Unexpected new vertex in hull output");
  401. }
  402. return hull_verts;
  403. }
  404. static void hull_from_bullet(
  405. BMesh *bm, BMOperator *op,
  406. GSet *hull_triangles,
  407. BLI_mempool *pool)
  408. {
  409. int *fvi = NULL;
  410. BLI_array_declare(fvi);
  411. BMVert **input_verts;
  412. float (*coords)[3];
  413. BMVert **hull_verts;
  414. plConvexHull hull;
  415. int i, count = 0;
  416. const int num_input_verts = hull_input_vert_count(op);
  417. input_verts = hull_input_verts_copy(op, num_input_verts);
  418. coords = hull_verts_for_bullet(input_verts, num_input_verts);
  419. hull = plConvexHullCompute(coords, num_input_verts);
  420. hull_verts = hull_verts_from_bullet(hull, input_verts, num_input_verts);
  421. count = plConvexHullNumFaces(hull);
  422. for (i = 0; i < count; i++) {
  423. const int len = plConvexHullGetFaceSize(hull, i);
  424. if (len > 2) {
  425. BMVert *fv[3];
  426. int j;
  427. /* Get face vertex indices */
  428. BLI_array_empty(fvi);
  429. BLI_array_grow_items(fvi, len);
  430. plConvexHullGetFaceVertices(hull, i, fvi);
  431. /* Note: here we throw away any NGons from Bullet and turn
  432. * them into triangle fans. Would be nice to use these
  433. * directly, but will have to wait until HullTriangle goes
  434. * away (TODO) */
  435. fv[0] = hull_verts[fvi[0]];
  436. for (j = 2; j < len; j++) {
  437. fv[1] = hull_verts[fvi[j - 1]];
  438. fv[2] = hull_verts[fvi[j]];
  439. hull_add_triangle(bm, hull_triangles, pool,
  440. fv[0], fv[1], fv[2]);
  441. }
  442. }
  443. }
  444. BLI_array_free(fvi);
  445. plConvexHullDelete(hull);
  446. MEM_freeN(hull_verts);
  447. MEM_freeN(coords);
  448. MEM_freeN(input_verts);
  449. }
  450. /* Check that there are at least three vertices in the input */
  451. static bool hull_num_input_verts_is_ok(BMOperator *op)
  452. {
  453. BMOIter oiter;
  454. BMVert *v;
  455. int partial_num_verts = 0;
  456. BMO_ITER (v, &oiter, op->slots_in, "input", BM_VERT) {
  457. partial_num_verts++;
  458. if (partial_num_verts >= 3)
  459. break;
  460. }
  461. return (partial_num_verts >= 3);
  462. }
  463. void bmo_convex_hull_exec(BMesh *bm, BMOperator *op)
  464. {
  465. HullFinalEdges *final_edges;
  466. BLI_mempool *hull_pool;
  467. BMElemF *ele;
  468. BMOIter oiter;
  469. GSet *hull_triangles;
  470. /* Verify that at least three verts in the input */
  471. if (!hull_num_input_verts_is_ok(op)) {
  472. BMO_error_raise(bm, op, BMERR_CONVEX_HULL_FAILED,
  473. "Requires at least three vertices");
  474. return;
  475. }
  476. /* Tag input elements */
  477. BMO_ITER (ele, &oiter, op->slots_in, "input", BM_ALL) {
  478. /* Mark all vertices as interior to begin with */
  479. if (ele->head.htype == BM_VERT) {
  480. BMO_vert_flag_enable(bm, (BMVert *)ele, HULL_FLAG_INPUT | HULL_FLAG_INTERIOR_ELE);
  481. }
  482. else if (ele->head.htype == BM_EDGE) {
  483. BMO_edge_flag_enable(bm, (BMEdge *)ele, HULL_FLAG_INPUT);
  484. }
  485. else {
  486. BMO_face_flag_enable(bm, (BMFace *)ele, HULL_FLAG_INPUT);
  487. }
  488. }
  489. hull_pool = BLI_mempool_create(sizeof(HullTriangle), 0, 128, BLI_MEMPOOL_NOP);
  490. hull_triangles = BLI_gset_ptr_new("hull_triangles");
  491. hull_from_bullet(bm, op, hull_triangles, hull_pool);
  492. final_edges = hull_final_edges(hull_triangles);
  493. hull_mark_interior_elements(bm, op, final_edges);
  494. /* Remove hull triangles covered by an existing face */
  495. if (BMO_slot_bool_get(op->slots_in, "use_existing_faces")) {
  496. hull_remove_overlapping(bm, hull_triangles, final_edges);
  497. hull_tag_holes(bm, op);
  498. }
  499. /* Done with edges */
  500. hull_final_edges_free(final_edges);
  501. /* Convert hull triangles to BMesh faces */
  502. hull_output_triangles(bm, hull_triangles);
  503. BLI_mempool_destroy(hull_pool);
  504. BLI_gset_free(hull_triangles, NULL);
  505. hull_tag_unused(bm, op);
  506. /* Output slot of input elements that ended up inside the hull
  507. * rather than part of it */
  508. BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom_interior.out",
  509. BM_ALL_NOLOOP, HULL_FLAG_INTERIOR_ELE);
  510. /* Output slot of input elements that ended up inside the hull and
  511. * are are unused by other geometry. */
  512. BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom_unused.out",
  513. BM_ALL_NOLOOP, HULL_FLAG_DEL);
  514. /* Output slot of faces and edges that were in the input and on
  515. * the hull (useful for cases like bridging where you want to
  516. * delete some input geometry) */
  517. BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom_holes.out",
  518. BM_ALL_NOLOOP, HULL_FLAG_HOLE);
  519. /* Output slot of all hull vertices, faces, and edges */
  520. BMO_slot_buffer_from_enabled_flag(bm, op, op->slots_out, "geom.out",
  521. BM_ALL_NOLOOP, HULL_FLAG_OUTPUT_GEOM);
  522. }
  523. #endif /* WITH_BULLET */