/* * ***** BEGIN GPL LICENSE BLOCK ***** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) Blender Foundation * All rights reserved. * * Contributor(s): Sergey Sharybin. * * ***** END GPL LICENSE BLOCK ***** */ /** \file blender/modifiers/intern/MOD_boolean_util.c * \ingroup modifiers */ #include "MEM_guardedalloc.h" #include "DNA_material_types.h" #include "DNA_meshdata_types.h" #include "DNA_object_types.h" #include "BLI_utildefines.h" #include "BLI_alloca.h" #include "BLI_ghash.h" #include "BLI_math.h" #include "BKE_cdderivedmesh.h" #include "BKE_material.h" #include "MOD_boolean_util.h" #include "carve-capi.h" /* Adopted from BM_loop_interp_from_face(), * * Transform matrix is used in cases when target coordinate needs * to be converted to source space (namely when interpolating * boolean result loops from second operand). * * TODO(sergey): Consider making it a generic function in DerivedMesh.c. */ static void DM_loop_interp_from_poly(DerivedMesh *source_dm, MVert *source_mverts, MLoop *source_mloops, MPoly *source_poly, DerivedMesh *target_dm, MVert *target_mverts, MLoop *target_mloop, float transform[4][4], int target_loop_index) { float (*cos_3d)[3] = BLI_array_alloca(cos_3d, source_poly->totloop); int *source_indices = BLI_array_alloca(source_indices, source_poly->totloop); float *weights = BLI_array_alloca(weights, source_poly->totloop); int i; int target_vert_index = target_mloop[target_loop_index].v; float coord[3]; for (i = 0; i < source_poly->totloop; ++i) { MLoop *mloop = &source_mloops[source_poly->loopstart + i]; source_indices[i] = source_poly->loopstart + i; copy_v3_v3(cos_3d[i], source_mverts[mloop->v].co); } if (transform) { mul_v3_m4v3(coord, transform, target_mverts[target_vert_index].co); } else { copy_v3_v3(coord, target_mverts[target_vert_index].co); } interp_weights_poly_v3(weights, cos_3d, source_poly->totloop, coord); DM_interp_loop_data(source_dm, target_dm, source_indices, weights, source_poly->totloop, target_loop_index); } typedef struct DMArrays { MVert *mvert; MEdge *medge; MLoop *mloop; MPoly *mpoly; bool mvert_allocated; bool medge_allocated; bool mloop_allocated; bool mpoly_allocated; } DMArrays; static void dm_arrays_get(DerivedMesh *dm, DMArrays *arrays) { arrays->mvert = DM_get_vert_array(dm, &arrays->mvert_allocated); arrays->medge = DM_get_edge_array(dm, &arrays->medge_allocated); arrays->mloop = DM_get_loop_array(dm, &arrays->mloop_allocated); arrays->mpoly = DM_get_poly_array(dm, &arrays->mpoly_allocated); } static void dm_arrays_free(DMArrays *arrays) { if (arrays->mvert_allocated) { MEM_freeN(arrays->mvert); } if (arrays->medge_allocated) { MEM_freeN(arrays->medge); } if (arrays->mloop_allocated) { MEM_freeN(arrays->mloop); } if (arrays->mpoly_allocated) { MEM_freeN(arrays->mpoly); } } /* **** Importer from derived mesh to Carve **** */ typedef struct ImportMeshData { DerivedMesh *dm; float obmat[4][4]; MVert *mvert; MEdge *medge; MLoop *mloop; MPoly *mpoly; } ImportMeshData; /* Get number of vertices. */ static int importer_GetNumVerts(ImportMeshData *import_data) { DerivedMesh *dm = import_data->dm; return dm->getNumVerts(dm); } /* Get number of edges. */ static int importer_GetNumEdges(ImportMeshData *import_data) { DerivedMesh *dm = import_data->dm; return dm->getNumEdges(dm); } /* Get number of loops. */ static int importer_GetNumLoops(ImportMeshData *import_data) { DerivedMesh *dm = import_data->dm; return dm->getNumLoops(dm); } /* Get number of polys. */ static int importer_GetNumPolys(ImportMeshData *import_data) { DerivedMesh *dm = import_data->dm; return dm->getNumPolys(dm); } /* Get 3D coordinate of vertex with given index. */ static void importer_GetVertCoord(ImportMeshData *import_data, int vert_index, float coord[3]) { MVert *mvert = import_data->mvert; BLI_assert(vert_index >= 0 && vert_index < import_data->dm->getNumVerts(import_data->dm)); mul_v3_m4v3(coord, import_data->obmat, mvert[vert_index].co); } /* Get index of vertices which are adjucent to edge specified by it's index. */ static void importer_GetEdgeVerts(ImportMeshData *import_data, int edge_index, int *v1, int *v2) { MEdge *medge = &import_data->medge[edge_index]; BLI_assert(edge_index >= 0 && edge_index < import_data->dm->getNumEdges(import_data->dm)); *v1 = medge->v1; *v2 = medge->v2; } /* Get number of adjucent vertices to the poly specified by it's index. */ static int importer_GetPolyNumVerts(ImportMeshData *import_data, int poly_index) { MPoly *mpoly = import_data->mpoly; BLI_assert(poly_index >= 0 && poly_index < import_data->dm->getNumPolys(import_data->dm)); return mpoly[poly_index].totloop; } /* Get list of adjucent vertices to the poly specified by it's index. */ static void importer_GetPolyVerts(ImportMeshData *import_data, int poly_index, int *verts) { MPoly *mpoly = &import_data->mpoly[poly_index]; MLoop *mloop = import_data->mloop + mpoly->loopstart; int i; BLI_assert(poly_index >= 0 && poly_index < import_data->dm->getNumPolys(import_data->dm)); for (i = 0; i < mpoly->totloop; i++, mloop++) { verts[i] = mloop->v; } } // Triangulate 2D polygon. #if 0 static int importer_triangulate2DPoly(ImportMeshData *UNUSED(import_data), const float (*vertices)[2], int num_vertices, unsigned int (*triangles)[3]) { // TODO(sergey): Currently import_data is unused but in the future we could // put memory arena there which will reduce amount of allocations happening // over the triangulation period. // // However that's not so much straighforward to do it right now because we // also are tu consider threaded import/export. BLI_assert(num_vertices > 3); BLI_polyfill_calc(vertices, num_vertices, triangles); return num_vertices - 2; } #endif static CarveMeshImporter MeshImporter = { importer_GetNumVerts, importer_GetNumEdges, importer_GetNumLoops, importer_GetNumPolys, importer_GetVertCoord, importer_GetEdgeVerts, importer_GetPolyNumVerts, importer_GetPolyVerts, /* TODO(sergey): We don't use BLI_polyfill_calc() because it tends * to generate degenerated geometry which is fatal for booleans. * * For now we stick to Carve's triangulation. */ NULL, /* importer_triangulate2DPoly */ }; /* **** Exporter from Carve to derived mesh **** */ typedef struct ExportMeshData { DerivedMesh *dm; float obimat[4][4]; MVert *mvert; MEdge *medge; MLoop *mloop; MPoly *mpoly; int *vert_origindex; int *edge_origindex; int *poly_origindex; int *loop_origindex; /* Objects and derived meshes of left and right operands. * Used for custom data merge and interpolation. */ Object *ob_left; Object *ob_right; DerivedMesh *dm_left; DerivedMesh *dm_right; MVert *mvert_left; MEdge *medge_left; MLoop *mloop_left; MPoly *mpoly_left; MVert *mvert_right; MEdge *medge_right; MLoop *mloop_right; MPoly *mpoly_right; float left_to_right_mat[4][4]; /* Hash to map materials from right object to result. */ GHash *material_hash; } ExportMeshData; BLI_INLINE Object *which_object(ExportMeshData *export_data, int which_mesh) { Object *object = NULL; switch (which_mesh) { case CARVE_MESH_LEFT: object = export_data->ob_left; break; case CARVE_MESH_RIGHT: object = export_data->ob_right; break; } return object; } BLI_INLINE DerivedMesh *which_dm(ExportMeshData *export_data, int which_mesh) { DerivedMesh *dm = NULL; switch (which_mesh) { case CARVE_MESH_LEFT: dm = export_data->dm_left; break; case CARVE_MESH_RIGHT: dm = export_data->dm_right; break; } return dm; } BLI_INLINE MVert *which_mvert(ExportMeshData *export_data, int which_mesh) { MVert *mvert = NULL; switch (which_mesh) { case CARVE_MESH_LEFT: mvert = export_data->mvert_left; break; case CARVE_MESH_RIGHT: mvert = export_data->mvert_right; break; } return mvert; } BLI_INLINE MEdge *which_medge(ExportMeshData *export_data, int which_mesh) { MEdge *medge = NULL; switch (which_mesh) { case CARVE_MESH_LEFT: medge = export_data->medge_left; break; case CARVE_MESH_RIGHT: medge = export_data->medge_right; break; } return medge; } BLI_INLINE MLoop *which_mloop(ExportMeshData *export_data, int which_mesh) { MLoop *mloop = NULL; switch (which_mesh) { case CARVE_MESH_LEFT: mloop = export_data->mloop_left; break; case CARVE_MESH_RIGHT: mloop = export_data->mloop_right; break; } return mloop; } BLI_INLINE MPoly *which_mpoly(ExportMeshData *export_data, int which_mesh) { MPoly *mpoly = NULL; switch (which_mesh) { case CARVE_MESH_LEFT: mpoly = export_data->mpoly_left; break; case CARVE_MESH_RIGHT: mpoly = export_data->mpoly_right; break; } return mpoly; } /* Create new external mesh */ static void exporter_InitGeomArrays(ExportMeshData *export_data, int num_verts, int num_edges, int num_loops, int num_polys) { DerivedMesh *dm = CDDM_new(num_verts, num_edges, 0, num_loops, num_polys); DerivedMesh *dm_left = export_data->dm_left, *dm_right = export_data->dm_right; /* Mask for custom data layers to be merged from operands. */ CustomDataMask merge_mask = CD_MASK_DERIVEDMESH & ~CD_MASK_ORIGINDEX; export_data->dm = dm; export_data->mvert = dm->getVertArray(dm); export_data->medge = dm->getEdgeArray(dm); export_data->mloop = dm->getLoopArray(dm); export_data->mpoly = dm->getPolyArray(dm); /* Merge custom data layers from operands. * * Will only create custom data layers for all the layers which appears in * the operand. Data for those layers will not be allocated or initialized. */ CustomData_merge(&dm_left->loopData, &dm->loopData, merge_mask, CD_DEFAULT, num_loops); CustomData_merge(&dm_right->loopData, &dm->loopData, merge_mask, CD_DEFAULT, num_loops); CustomData_merge(&dm_left->polyData, &dm->polyData, merge_mask, CD_DEFAULT, num_polys); CustomData_merge(&dm_right->polyData, &dm->polyData, merge_mask, CD_DEFAULT, num_polys); CustomData_merge(&dm_left->edgeData, &dm->edgeData, merge_mask, CD_DEFAULT, num_edges); CustomData_merge(&dm_right->edgeData, &dm->edgeData, merge_mask, CD_DEFAULT, num_edges); export_data->vert_origindex = dm->getVertDataArray(dm, CD_ORIGINDEX); export_data->edge_origindex = dm->getEdgeDataArray(dm, CD_ORIGINDEX); export_data->poly_origindex = dm->getPolyDataArray(dm, CD_ORIGINDEX); export_data->loop_origindex = dm->getLoopDataArray(dm, CD_ORIGINDEX); } /* Set coordinate of vertex with given index. */ static void exporter_SetVert(ExportMeshData *export_data, int vert_index, float coord[3], int which_orig_mesh, int orig_vert_index) { DerivedMesh *dm = export_data->dm; DerivedMesh *dm_orig; MVert *mvert = export_data->mvert; BLI_assert(vert_index >= 0 && vert_index <= dm->getNumVerts(dm)); dm_orig = which_dm(export_data, which_orig_mesh); if (dm_orig) { BLI_assert(orig_vert_index >= 0 && orig_vert_index < dm_orig->getNumVerts(dm_orig)); mvert[vert_index] = which_mvert(export_data, which_orig_mesh)[orig_vert_index]; CustomData_copy_data(&dm_orig->vertData, &dm->vertData, orig_vert_index, vert_index, 1); } /* Set original index of the vertex. */ if (export_data->vert_origindex) { if (which_orig_mesh == CARVE_MESH_LEFT) { export_data->vert_origindex[vert_index] = orig_vert_index; } else { export_data->vert_origindex[vert_index] = ORIGINDEX_NONE; } } mul_v3_m4v3(mvert[vert_index].co, export_data->obimat, coord); } /* Set vertices which are adjucent to the edge specified by it's index. */ static void exporter_SetEdge(ExportMeshData *export_data, int edge_index, int v1, int v2, int which_orig_mesh, int orig_edge_index) { DerivedMesh *dm = export_data->dm; MEdge *medge = &export_data->medge[edge_index]; DerivedMesh *dm_orig; BLI_assert(edge_index >= 0 && edge_index < dm->getNumEdges(dm)); BLI_assert(v1 >= 0 && v1 < dm->getNumVerts(dm)); BLI_assert(v2 >= 0 && v2 < dm->getNumVerts(dm)); dm_orig = which_dm(export_data, which_orig_mesh); if (dm_orig) { BLI_assert(orig_edge_index >= 0 && orig_edge_index < dm_orig->getNumEdges(dm_orig)); *medge = which_medge(export_data, which_orig_mesh)[orig_edge_index]; /* Copy all edge layers, including medge. */ CustomData_copy_data(&dm_orig->edgeData, &dm->edgeData, orig_edge_index, edge_index, 1); } /* Set original index of the edge. */ if (export_data->edge_origindex) { if (which_orig_mesh == CARVE_MESH_LEFT) { export_data->edge_origindex[edge_index] = orig_edge_index; } else { export_data->edge_origindex[edge_index] = ORIGINDEX_NONE; } } medge->v1 = v1; medge->v2 = v2; medge->flag |= ME_EDGEDRAW | ME_EDGERENDER; } static void setMPolyMaterial(ExportMeshData *export_data, MPoly *mpoly, int which_orig_mesh) { Object *orig_object; GHash *material_hash; Material *orig_mat; if (which_orig_mesh == CARVE_MESH_LEFT) { /* No need to change materian index for faces from left operand */ return; } material_hash = export_data->material_hash; orig_object = which_object(export_data, which_orig_mesh); /* Set material, based on lookup in hash table. */ orig_mat = give_current_material(orig_object, mpoly->mat_nr + 1); if (orig_mat) { /* For faces from right operand check if there's requested material * in the left operand. And if it is, use index of that material, * otherwise fallback to first material (material with index=0). */ if (!BLI_ghash_haskey(material_hash, orig_mat)) { int a, mat_nr; mat_nr = 0; for (a = 0; a < export_data->ob_left->totcol; a++) { if (give_current_material(export_data->ob_left, a + 1) == orig_mat) { mat_nr = a; break; } } BLI_ghash_insert(material_hash, orig_mat, SET_INT_IN_POINTER(mat_nr)); mpoly->mat_nr = mat_nr; } else mpoly->mat_nr = GET_INT_FROM_POINTER(BLI_ghash_lookup(material_hash, orig_mat)); } else { mpoly->mat_nr = 0; } } /* Set list of adjucent loops to the poly specified by it's index. */ static void exporter_SetPoly(ExportMeshData *export_data, int poly_index, int start_loop, int num_loops, int which_orig_mesh, int orig_poly_index) { DerivedMesh *dm = export_data->dm; MPoly *mpoly = &export_data->mpoly[poly_index]; DerivedMesh *dm_orig; int i; /* Poly is always to be either from left or right operand. */ dm_orig = which_dm(export_data, which_orig_mesh); BLI_assert(poly_index >= 0 && poly_index < dm->getNumPolys(dm)); BLI_assert(start_loop >= 0 && start_loop <= dm->getNumLoops(dm) - num_loops); BLI_assert(num_loops >= 3); BLI_assert(dm_orig != NULL); BLI_assert(orig_poly_index >= 0 && orig_poly_index < dm_orig->getNumPolys(dm_orig)); /* Copy all poly layers, including mpoly. */ *mpoly = which_mpoly(export_data, which_orig_mesh)[orig_poly_index]; CustomData_copy_data(&dm_orig->polyData, &dm->polyData, orig_poly_index, poly_index, 1); /* Set material of the curren poly. * This would re-map materials from right operand to materials from the * left one as well. */ setMPolyMaterial(export_data, mpoly, which_orig_mesh); /* Set original index of the poly. */ if (export_data->poly_origindex) { if (which_orig_mesh == CARVE_MESH_LEFT) { export_data->poly_origindex[poly_index] = orig_poly_index; } else { export_data->poly_origindex[poly_index] = ORIGINDEX_NONE; } } /* Set poly data itself. */ mpoly->loopstart = start_loop; mpoly->totloop = num_loops; /* Interpolate data for poly loops. */ { MVert *source_mverts = which_mvert(export_data, which_orig_mesh); MLoop *source_mloops = which_mloop(export_data, which_orig_mesh); MPoly *source_mpolys = which_mpoly(export_data, which_orig_mesh); MPoly *source_poly = &source_mpolys[orig_poly_index]; MVert *target_mverts = export_data->mvert; MLoop *target_mloops = export_data->mloop; float (*transform)[4] = NULL; if (which_orig_mesh == CARVE_MESH_RIGHT) { transform = export_data->left_to_right_mat; } for (i = 0; i < mpoly->totloop; i++) { DM_loop_interp_from_poly(dm_orig, source_mverts, source_mloops, source_poly, dm, target_mverts, target_mloops, transform, i + mpoly->loopstart); } } } /* Set list vertex and edge which are adjucent to loop with given index. */ static void exporter_SetLoop(ExportMeshData *export_data, int loop_index, int vertex, int edge, int which_orig_mesh, int orig_loop_index) { DerivedMesh *dm = export_data->dm; MLoop *mloop = &export_data->mloop[loop_index]; DerivedMesh *dm_orig; BLI_assert(loop_index >= 0 && loop_index < dm->getNumLoops(dm)); BLI_assert(vertex >= 0 && vertex < dm->getNumVerts(dm)); BLI_assert(edge >= 0 && vertex < dm->getNumEdges(dm)); dm_orig = which_dm(export_data, which_orig_mesh); if (dm_orig) { BLI_assert(orig_loop_index >= 0 && orig_loop_index < dm_orig->getNumLoops(dm_orig)); /* Copy all loop layers, including mloop. */ *mloop = which_mloop(export_data, which_orig_mesh)[orig_loop_index]; CustomData_copy_data(&dm_orig->loopData, &dm->loopData, orig_loop_index, loop_index, 1); } /* Set original index of the loop. */ if (export_data->loop_origindex) { if (which_orig_mesh == CARVE_MESH_LEFT) { export_data->loop_origindex[loop_index] = orig_loop_index; } else { export_data->loop_origindex[loop_index] = ORIGINDEX_NONE; } } mloop->v = vertex; mloop->e = edge; } /* Edge index from a loop index for a given original mesh. */ static int exporter_MapLoopToEdge(ExportMeshData *export_data, int which_mesh, int loop_index) { DerivedMesh *dm = which_dm(export_data, which_mesh); MLoop *mloop = which_mloop(export_data, which_mesh); (void) dm; /* Unused in release builds. */ BLI_assert(dm != NULL); BLI_assert(loop_index >= 0 && loop_index < dm->getNumLoops(dm)); return mloop[loop_index].e; } static CarveMeshExporter MeshExporter = { exporter_InitGeomArrays, exporter_SetVert, exporter_SetEdge, exporter_SetPoly, exporter_SetLoop, exporter_MapLoopToEdge }; static int operation_from_optype(int int_op_type) { int operation; switch (int_op_type) { case 1: operation = CARVE_OP_INTERSECTION; break; case 2: operation = CARVE_OP_UNION; break; case 3: operation = CARVE_OP_A_MINUS_B; break; default: BLI_assert(!"Should not happen"); operation = -1; break; } return operation; } static void prepare_import_data(Object *object, DerivedMesh *dm, const DMArrays *dm_arrays, ImportMeshData *import_data) { import_data->dm = dm; copy_m4_m4(import_data->obmat, object->obmat); import_data->mvert = dm_arrays->mvert; import_data->medge = dm_arrays->medge; import_data->mloop = dm_arrays->mloop; import_data->mpoly = dm_arrays->mpoly; } static struct CarveMeshDescr *carve_mesh_from_dm(Object *object, DerivedMesh *dm, const DMArrays *dm_arrays) { ImportMeshData import_data; prepare_import_data(object, dm, dm_arrays, &import_data); return carve_addMesh(&import_data, &MeshImporter); } static void prepare_export_data(Object *object_left, DerivedMesh *dm_left, const DMArrays *dm_left_arrays, Object *object_right, DerivedMesh *dm_right, const DMArrays *dm_right_arrays, ExportMeshData *export_data) { float object_right_imat[4][4]; invert_m4_m4(export_data->obimat, object_left->obmat); export_data->ob_left = object_left; export_data->ob_right = object_right; export_data->dm_left = dm_left; export_data->dm_right = dm_right; export_data->mvert_left = dm_left_arrays->mvert; export_data->medge_left = dm_left_arrays->medge; export_data->mloop_left = dm_left_arrays->mloop; export_data->mpoly_left = dm_left_arrays->mpoly; export_data->mvert_right = dm_right_arrays->mvert; export_data->medge_right = dm_right_arrays->medge; export_data->mloop_right = dm_right_arrays->mloop; export_data->mpoly_right = dm_right_arrays->mpoly; export_data->material_hash = BLI_ghash_ptr_new("CSG_mat gh"); /* Matrix to convert coord from left object's loca; space to * right object's local space. */ invert_m4_m4(object_right_imat, object_right->obmat); mul_m4_m4m4(export_data->left_to_right_mat, object_left->obmat, object_right_imat); } DerivedMesh *NewBooleanDerivedMesh(DerivedMesh *dm, struct Object *ob, DerivedMesh *dm_select, struct Object *ob_select, int int_op_type) { struct CarveMeshDescr *left, *right, *output = NULL; DerivedMesh *output_dm = NULL; int operation; bool result; DMArrays dm_left_arrays, dm_right_arrays; if (dm == NULL || dm_select == NULL) { return NULL; } operation = operation_from_optype(int_op_type); if (operation == -1) { return NULL; } dm_arrays_get(dm_select, &dm_left_arrays); dm_arrays_get(dm, &dm_right_arrays); left = carve_mesh_from_dm(ob_select, dm_select, &dm_left_arrays); right = carve_mesh_from_dm(ob, dm, &dm_right_arrays); result = carve_performBooleanOperation(left, right, operation, &output); carve_deleteMesh(left); carve_deleteMesh(right); if (result) { ExportMeshData export_data; prepare_export_data(ob_select, dm_select, &dm_left_arrays, ob, dm, &dm_right_arrays, &export_data); carve_exportMesh(output, &MeshExporter, &export_data); output_dm = export_data.dm; /* Free memory used by export mesh. */ BLI_ghash_free(export_data.material_hash, NULL, NULL); output_dm->cd_flag |= dm->cd_flag | dm_select->cd_flag; output_dm->dirty |= DM_DIRTY_NORMALS; carve_deleteMesh(output); } dm_arrays_free(&dm_left_arrays); dm_arrays_free(&dm_right_arrays); return output_dm; }