diff options
Diffstat (limited to 'source/blender/modifiers')
65 files changed, 2962 insertions, 1635 deletions
diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index c19782df44b..0138dd0c3ad 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -50,7 +50,7 @@ set(SRC intern/MOD_armature.c intern/MOD_array.c intern/MOD_bevel.c - intern/MOD_boolean.c + intern/MOD_boolean.cc intern/MOD_build.c intern/MOD_cast.c intern/MOD_cloth.c @@ -79,6 +79,7 @@ set(SRC intern/MOD_mirror.c intern/MOD_multires.c intern/MOD_nodes.cc + intern/MOD_nodes_evaluator.cc intern/MOD_none.c intern/MOD_normal_edit.c intern/MOD_ocean.c @@ -118,6 +119,7 @@ set(SRC MOD_modifiertypes.h MOD_nodes.h intern/MOD_meshcache_util.h + intern/MOD_nodes_evaluator.hh intern/MOD_solidify_util.h intern/MOD_ui_common.h intern/MOD_util.h @@ -181,6 +183,30 @@ endif() if(WITH_GMP) add_definitions(-DWITH_GMP) + + list(APPEND INC_SYS + ${GMP_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${GMP_LIBRARIES} + ) +endif() + +if(WITH_TBB) + add_definitions(-DWITH_TBB) + if(WIN32) + # TBB includes Windows.h which will define min/max macros + # that will collide with the stl versions. + add_definitions(-DNOMINMAX) + endif() + list(APPEND INC_SYS + ${TBB_INCLUDE_DIRS} + ) + + list(APPEND LIB + ${TBB_LIBRARIES} + ) endif() if(WITH_OPENVDB) @@ -198,7 +224,7 @@ if(WITH_OPENVDB) endif() if(WITH_EXPERIMENTAL_FEATURES) - add_definitions(-DWITH_GEOMETRY_NODES) + add_definitions(-DWITH_SIMULATION_DATABLOCK) add_definitions(-DWITH_POINT_CLOUD) add_definitions(-DWITH_HAIR_NODES) endif() diff --git a/source/blender/modifiers/intern/MOD_armature.c b/source/blender/modifiers/intern/MOD_armature.c index 6769f14f88f..649d36e3d57 100644 --- a/source/blender/modifiers/intern/MOD_armature.c +++ b/source/blender/modifiers/intern/MOD_armature.c @@ -296,7 +296,6 @@ ModifierTypeInfo modifierType_Armature = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_array.c b/source/blender/modifiers/intern/MOD_array.c index b48bf722526..93a9e76ffe4 100644 --- a/source/blender/modifiers/intern/MOD_array.c +++ b/source/blender/modifiers/intern/MOD_array.c @@ -39,6 +39,7 @@ #include "DNA_scene_types.h" #include "DNA_screen_types.h" +#include "BKE_anim_path.h" #include "BKE_context.h" #include "BKE_curve.h" #include "BKE_displist.h" @@ -471,9 +472,9 @@ static Mesh *arrayModifier_doArray(ArrayModifierData *amd, if (amd->fit_type == MOD_ARR_FITCURVE && amd->curve_ob != NULL) { Object *curve_ob = amd->curve_ob; CurveCache *curve_cache = curve_ob->runtime.curve_cache; - if (curve_cache != NULL && curve_cache->path != NULL) { + if (curve_cache != NULL && curve_cache->anim_path_accum_length != NULL) { float scale_fac = mat4_to_scale(curve_ob->obmat); - length = scale_fac * curve_cache->path->totdist; + length = scale_fac * BKE_anim_path_get_length(curve_cache); } } @@ -1020,7 +1021,6 @@ ModifierTypeInfo modifierType_Array = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_bevel.c b/source/blender/modifiers/intern/MOD_bevel.c index a94411d897e..8fdd222402e 100644 --- a/source/blender/modifiers/intern/MOD_bevel.c +++ b/source/blender/modifiers/intern/MOD_bevel.c @@ -448,7 +448,6 @@ ModifierTypeInfo modifierType_Bevel = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, /* freeData */ freeData, diff --git a/source/blender/modifiers/intern/MOD_boolean.c b/source/blender/modifiers/intern/MOD_boolean.c deleted file mode 100644 index fae8ac72108..00000000000 --- a/source/blender/modifiers/intern/MOD_boolean.c +++ /dev/null @@ -1,967 +0,0 @@ -/* - * 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) 2005 by the Blender Foundation. - * All rights reserved. - */ - -/** \file - * \ingroup modifiers - */ - -// #define DEBUG_TIME - -#include <stdio.h> - -#include "BLI_utildefines.h" - -#include "BLI_alloca.h" -#include "BLI_array.h" -#include "BLI_math_geom.h" -#include "BLI_math_matrix.h" - -#include "BLT_translation.h" - -#include "DNA_collection_types.h" -#include "DNA_defaults.h" -#include "DNA_mesh_types.h" -#include "DNA_meshdata_types.h" -#include "DNA_object_types.h" -#include "DNA_scene_types.h" -#include "DNA_screen_types.h" - -#include "BKE_collection.h" -#include "BKE_context.h" -#include "BKE_global.h" /* only to check G.debug */ -#include "BKE_lib_id.h" -#include "BKE_lib_query.h" -#include "BKE_material.h" -#include "BKE_mesh.h" -#include "BKE_mesh_boolean_convert.h" -#include "BKE_mesh_wrapper.h" -#include "BKE_modifier.h" -#include "BKE_screen.h" - -#include "UI_interface.h" -#include "UI_resources.h" - -#include "RNA_access.h" - -#include "MOD_ui_common.h" -#include "MOD_util.h" - -#include "DEG_depsgraph_query.h" - -#include "MEM_guardedalloc.h" - -#include "bmesh.h" -#include "bmesh_tools.h" -#include "tools/bmesh_boolean.h" -#include "tools/bmesh_intersect.h" - -#ifdef DEBUG_TIME -# include "PIL_time.h" -# include "PIL_time_utildefines.h" -#endif - -#ifdef WITH_GMP -static const bool bypass_bmesh = true; -#else -static const bool bypass_bmesh = false; -#endif - -static void initData(ModifierData *md) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - - BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(bmd, modifier)); - - MEMCPY_STRUCT_AFTER(bmd, DNA_struct_default_get(BooleanModifierData), modifier); -} - -static bool isDisabled(const struct Scene *UNUSED(scene), - ModifierData *md, - bool UNUSED(useRenderParams)) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - Collection *col = bmd->collection; - - if (bmd->flag & eBooleanModifierFlag_Object) { - return !bmd->object || bmd->object->type != OB_MESH; - } - if (bmd->flag & eBooleanModifierFlag_Collection) { - /* The Exact solver tolerates an empty collection. */ - return !col && bmd->solver != eBooleanModifierSolver_Exact; - } - return false; -} - -static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - - walk(userData, ob, (ID **)&bmd->collection, IDWALK_CB_NOP); - walk(userData, ob, (ID **)&bmd->object, IDWALK_CB_NOP); -} - -static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - if ((bmd->flag & eBooleanModifierFlag_Object) && bmd->object != NULL) { - DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_TRANSFORM, "Boolean Modifier"); - DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_GEOMETRY, "Boolean Modifier"); - } - - Collection *col = bmd->collection; - - if ((bmd->flag & eBooleanModifierFlag_Collection) && col != NULL) { - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, operand_ob) { - if (operand_ob->type == OB_MESH && operand_ob != ctx->object) { - DEG_add_object_relation(ctx->node, operand_ob, DEG_OB_COMP_TRANSFORM, "Boolean Modifier"); - DEG_add_object_relation(ctx->node, operand_ob, DEG_OB_COMP_GEOMETRY, "Boolean Modifier"); - } - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - } - /* We need own transformation as well. */ - DEG_add_modifier_to_transform_relation(ctx->node, "Boolean Modifier"); -} - -static Mesh *get_quick_mesh( - Object *ob_self, Mesh *mesh_self, Object *ob_operand_ob, Mesh *mesh_operand_ob, int operation) -{ - Mesh *result = NULL; - - if (mesh_self->totpoly == 0 || mesh_operand_ob->totpoly == 0) { - switch (operation) { - case eBooleanModifierOp_Intersect: - result = BKE_mesh_new_nomain(0, 0, 0, 0, 0); - break; - - case eBooleanModifierOp_Union: - if (mesh_self->totpoly != 0) { - result = mesh_self; - } - else { - result = (Mesh *)BKE_id_copy_ex(NULL, &mesh_operand_ob->id, NULL, LIB_ID_COPY_LOCALIZE); - - float imat[4][4]; - float omat[4][4]; - - invert_m4_m4(imat, ob_self->obmat); - mul_m4_m4m4(omat, imat, ob_operand_ob->obmat); - - const int mverts_len = result->totvert; - MVert *mv = result->mvert; - - for (int i = 0; i < mverts_len; i++, mv++) { - mul_m4_v3(omat, mv->co); - } - - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - } - - break; - - case eBooleanModifierOp_Difference: - result = mesh_self; - break; - } - } - - return result; -} - -/* has no meaning for faces, do this so we can tell which face is which */ -#define BM_FACE_TAG BM_ELEM_DRAW - -/** - * Compare selected/unselected. - */ -static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) -{ - return BM_elem_flag_test(f, BM_FACE_TAG) ? 1 : 0; -} - -static bool BMD_error_messages(const Object *ob, ModifierData *md, Collection *col) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - - bool error_returns_result = false; - - const bool operand_collection = (bmd->flag & eBooleanModifierFlag_Collection) != 0; - const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; - const bool operation_intersect = bmd->operation == eBooleanModifierOp_Intersect; - -#ifndef WITH_GMP - /* If compiled without GMP, return a error. */ - if (use_exact) { - BKE_modifier_set_error(ob, md, "Compiled without GMP, using fast solver"); - error_returns_result = false; - } -#endif - - /* If intersect is selected using fast solver, return a error. */ - if (operand_collection && operation_intersect && !use_exact) { - BKE_modifier_set_error(ob, md, "Cannot execute, intersect only available using exact solver"); - error_returns_result = true; - } - - /* If the selected collection is empty and using fast solver, return a error. */ - if (operand_collection) { - if (!use_exact && BKE_collection_is_empty(col)) { - BKE_modifier_set_error(ob, md, "Cannot execute, fast solver and empty collection"); - error_returns_result = true; - } - - /* If the selected collection contain non mesh objects, return a error. */ - if (col) { - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, operand_ob) { - if (operand_ob->type != OB_MESH) { - BKE_modifier_set_error( - ob, md, "Cannot execute, the selected collection contains non mesh objects"); - error_returns_result = true; - } - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - } - } - - return error_returns_result; -} - -static BMesh *BMD_mesh_bm_create( - Mesh *mesh, Object *object, Mesh *mesh_operand_ob, Object *operand_ob, bool *r_is_flip) -{ - BMesh *bm; - - *r_is_flip = (is_negative_m4(object->obmat) != is_negative_m4(operand_ob->obmat)); - - const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh, mesh_operand_ob); - - bm = BM_mesh_create(&allocsize, - &((struct BMeshCreateParams){ - .use_toolflags = false, - })); - - BM_mesh_bm_from_me(bm, - mesh_operand_ob, - &((struct BMeshFromMeshParams){ - .calc_face_normal = true, - })); - - if (UNLIKELY(*r_is_flip)) { - const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); - BMIter iter; - BMFace *efa; - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true); - } - } - - BM_mesh_bm_from_me(bm, - mesh, - &((struct BMeshFromMeshParams){ - .calc_face_normal = true, - })); - - return bm; -} - -/* Snap entries that are near 0 or 1 or -1 to those values. */ -static void clean_obmat(float cleaned[4][4], const float mat[4][4]) -{ - const float fuzz = 1e-6f; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - float f = mat[i][j]; - if (fabsf(f) <= fuzz) { - f = 0.0f; - } - else if (fabsf(f - 1.0f) <= fuzz) { - f = 1.0f; - } - else if (fabsf(f + 1.0f) <= fuzz) { - f = -1.0f; - } - cleaned[i][j] = f; - } - } -} - -static void BMD_mesh_intersection(BMesh *bm, - ModifierData *md, - const ModifierEvalContext *ctx, - Mesh *mesh_operand_ob, - Object *object, - Object *operand_ob, - bool is_flip) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - - /* main bmesh intersection setup */ - /* create tessface & intersect */ - const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); - int tottri; - BMLoop *(*looptris)[3]; - -#ifdef WITH_GMP - const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; - const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0; -#else - const bool use_exact = false; - const bool use_self = false; -#endif - - looptris = MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__); - - BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); - - /* postpone this until after tessellating - * so we can use the original normals before the vertex are moved */ - { - BMIter iter; - int i; - const int i_verts_end = mesh_operand_ob->totvert; - const int i_faces_end = mesh_operand_ob->totpoly; - - float imat[4][4]; - float omat[4][4]; - - if (use_exact) { - /* The user-expected coplanar faces will actually be coplanar more - * often if use an object matrix that doesn't multiply by values - * other than 0, -1, or 1 in the scaling part of the matrix. - */ - float cleaned_object_obmat[4][4]; - float cleaned_operand_obmat[4][4]; - clean_obmat(cleaned_object_obmat, object->obmat); - invert_m4_m4(imat, cleaned_object_obmat); - clean_obmat(cleaned_operand_obmat, operand_ob->obmat); - mul_m4_m4m4(omat, imat, cleaned_operand_obmat); - } - else { - invert_m4_m4(imat, object->obmat); - mul_m4_m4m4(omat, imat, operand_ob->obmat); - } - - BMVert *eve; - i = 0; - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - mul_m4_v3(omat, eve->co); - if (++i == i_verts_end) { - break; - } - } - - /* we need face normals because of 'BM_face_split_edgenet' - * we could calculate on the fly too (before calling split). */ - { - float nmat[3][3]; - copy_m3_m4(nmat, omat); - invert_m3(nmat); - - if (UNLIKELY(is_flip)) { - negate_m3(nmat); - } - - const short ob_src_totcol = operand_ob->totcol; - short *material_remap = BLI_array_alloca(material_remap, ob_src_totcol ? ob_src_totcol : 1); - - /* Using original (not evaluated) object here since we are writing to it. */ - /* XXX Pretty sure comment above is fully wrong now with CoW & co ? */ - BKE_object_material_remap_calc(ctx->object, operand_ob, material_remap); - - BMFace *efa; - i = 0; - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - mul_transposed_m3_v3(nmat, efa->no); - normalize_v3(efa->no); - - /* Temp tag to test which side split faces are from. */ - BM_elem_flag_enable(efa, BM_FACE_TAG); - - /* remap material */ - if (LIKELY(efa->mat_nr < ob_src_totcol)) { - efa->mat_nr = material_remap[efa->mat_nr]; - } - - if (++i == i_faces_end) { - break; - } - } - } - } - - /* not needed, but normals for 'dm' will be invalid, - * currently this is ok for 'BM_mesh_intersect' */ - // BM_mesh_normals_update(bm); - - bool use_separate = false; - bool use_dissolve = true; - bool use_island_connect = true; - - /* change for testing */ - if (G.debug & G_DEBUG) { - use_separate = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_Separate) != 0; - use_dissolve = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoDissolve) == 0; - use_island_connect = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoConnectRegions) == 0; - } - - if (use_exact) { - BM_mesh_boolean( - bm, looptris, tottri, bm_face_isect_pair, NULL, 2, use_self, false, false, bmd->operation); - } - else { - BM_mesh_intersect(bm, - looptris, - tottri, - bm_face_isect_pair, - NULL, - false, - use_separate, - use_dissolve, - use_island_connect, - false, - false, - bmd->operation, - bmd->double_threshold); - } - MEM_freeN(looptris); -} - -static int bm_face_isect_nary(BMFace *f, void *user_data) -{ - int *shape = (int *)user_data; - return shape[BM_elem_index_get(f)]; -} - -/* The Exact solver can do all operands of a collection at once. */ -static Mesh *collection_boolean_exact(BooleanModifierData *bmd, - const ModifierEvalContext *ctx, - Mesh *mesh) -{ - int i; - Mesh *result = mesh; - Collection *col = bmd->collection; - int num_shapes = 1; - Mesh **meshes = NULL; - Object **objects = NULL; - BLI_array_declare(meshes); - BLI_array_declare(objects); - BMAllocTemplate bat; - bat.totvert = mesh->totvert; - bat.totedge = mesh->totedge; - bat.totloop = mesh->totloop; - bat.totface = mesh->totpoly; - BLI_array_append(meshes, mesh); - BLI_array_append(objects, ctx->object); - Mesh *col_mesh; - /* Allow col to be empty: then target mesh will just remove self-intersections. */ - if (col) { - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, ob) { - if (ob->type == OB_MESH && ob != ctx->object) { - col_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob, false); - /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! - * But for 2.90 better not try to be smart here. */ - BKE_mesh_wrapper_ensure_mdata(col_mesh); - BLI_array_append(meshes, col_mesh); - BLI_array_append(objects, ob); - bat.totvert += col_mesh->totvert; - bat.totedge += col_mesh->totedge; - bat.totloop += col_mesh->totloop; - bat.totface += col_mesh->totpoly; - ++num_shapes; - } - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - } - int *shape_face_end = MEM_mallocN(num_shapes * sizeof(int), __func__); - int *shape_vert_end = MEM_mallocN(num_shapes * sizeof(int), __func__); - bool is_neg_mat0 = is_negative_m4(ctx->object->obmat); - BMesh *bm = BM_mesh_create(&bat, - &((struct BMeshCreateParams){ - .use_toolflags = false, - })); - for (i = 0; i < num_shapes; i++) { - Mesh *me = meshes[i]; - Object *ob = objects[i]; - /* Need normals for triangulation. */ - BM_mesh_bm_from_me(bm, - me, - &((struct BMeshFromMeshParams){ - .calc_face_normal = true, - })); - shape_face_end[i] = me->totpoly + (i == 0 ? 0 : shape_face_end[i - 1]); - shape_vert_end[i] = me->totvert + (i == 0 ? 0 : shape_vert_end[i - 1]); - if (i > 0) { - bool is_flip = (is_neg_mat0 != is_negative_m4(ob->obmat)); - if (UNLIKELY(is_flip)) { - const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); - BMIter iter; - BMFace *efa; - BM_mesh_elem_index_ensure(bm, BM_FACE); - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - if (BM_elem_index_get(efa) >= shape_face_end[i - 1]) { - BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true); - } - } - } - } - } - - /* Triangulate the mesh. */ - const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); - int tottri; - BMLoop *(*looptris)[3]; - looptris = MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__); - BM_mesh_calc_tessellation_beauty(bm, looptris, &tottri); - - /* Move the vertices of all but the first shape into transformation space of first mesh. - * Do this after tesselation so don't need to recalculate normals. - * The Exact solver doesn't need normals on the input faces. */ - float imat[4][4]; - float omat[4][4]; - float cleaned_object_obmat[4][4]; - clean_obmat(cleaned_object_obmat, ctx->object->obmat); - invert_m4_m4(imat, cleaned_object_obmat); - int curshape = 0; - int curshape_vert_end = shape_vert_end[0]; - BMVert *eve; - BMIter iter; - i = 0; - BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { - if (i == curshape_vert_end) { - curshape++; - curshape_vert_end = shape_vert_end[curshape]; - clean_obmat(cleaned_object_obmat, objects[curshape]->obmat); - mul_m4_m4m4(omat, imat, cleaned_object_obmat); - } - if (curshape > 0) { - mul_m4_v3(omat, eve->co); - } - i++; - } - - /* Remap the materials. Fill a shape array for test function. Calculate normals. */ - int *shape = MEM_mallocN(bm->totface * sizeof(int), __func__); - curshape = 0; - int curshape_face_end = shape_face_end[0]; - int curshape_ncol = ctx->object->totcol; - short *material_remap = NULL; - BMFace *efa; - i = 0; - BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { - if (i == curshape_face_end) { - curshape++; - curshape_face_end = shape_face_end[curshape]; - if (material_remap != NULL) { - MEM_freeN(material_remap); - } - curshape_ncol = objects[curshape]->totcol; - material_remap = MEM_mallocN(curshape_ncol ? curshape_ncol : 1, __func__); - BKE_object_material_remap_calc(ctx->object, objects[curshape], material_remap); - } - shape[i] = curshape; - if (curshape > 0) { - /* Normals for other shapes changed because vertex positions changed. - * Boolean doesn't need these, but post-boolean code (interpolation) does. */ - BM_face_normal_update(efa); - if (LIKELY(efa->mat_nr < curshape_ncol)) { - efa->mat_nr = material_remap[efa->mat_nr]; - } - } - i++; - } - - BM_mesh_elem_index_ensure(bm, BM_FACE); - BM_mesh_boolean(bm, - looptris, - tottri, - bm_face_isect_nary, - shape, - num_shapes, - true, - false, - false, - bmd->operation); - - result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); - BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - - MEM_freeN(shape); - MEM_freeN(shape_face_end); - MEM_freeN(shape_vert_end); - MEM_freeN(looptris); - if (material_remap != NULL) { - MEM_freeN(material_remap); - } - BLI_array_free(meshes); - BLI_array_free(objects); - return result; -} - -#ifdef WITH_GMP - -/* Get a mapping from material slot numbers in the src_ob to slot numbers in the dst_ob. - * If a material doesn't exist in the dst_ob, the mapping just goes to the same slot - * or to zero if there aren't enough slots in the destination. - * Caller must MEM_freeN the returned array. */ -static short *get_material_remap(Object *dest_ob, Object *src_ob) -{ - short *remap; - int n = dest_ob->totcol; - if (n <= 0) { - n = 1; - } - remap = MEM_mallocN(n * sizeof(short), __func__); - BKE_object_material_remap_calc(dest_ob, src_ob, remap); - return remap; -} - -/* New method: bypass trip through BMesh. */ -static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, - const ModifierEvalContext *ctx, - Mesh *mesh) -{ - Mesh *result; - Mesh *mesh_operand; - short *remap; - Mesh **meshes = NULL; - const float(**obmats)[4][4] = NULL; - short **material_remaps = NULL; - BLI_array_declare(meshes); - BLI_array_declare(obmats); - BLI_array_declare(material_remaps); - -# ifdef DEBUG_TIME - TIMEIT_START(boolean_bmesh); -# endif - - if ((bmd->flag & eBooleanModifierFlag_Object) && bmd->object == NULL) { - return mesh; - } - - BLI_array_append(meshes, mesh); - BLI_array_append(obmats, &ctx->object->obmat); - BLI_array_append(material_remaps, NULL); - if (bmd->flag & eBooleanModifierFlag_Object) { - mesh_operand = BKE_modifier_get_evaluated_mesh_from_evaluated_object(bmd->object, false); - BKE_mesh_wrapper_ensure_mdata(mesh_operand); - BLI_array_append(meshes, mesh_operand); - BLI_array_append(obmats, &bmd->object->obmat); - remap = get_material_remap(ctx->object, bmd->object); - BLI_array_append(material_remaps, remap); - } - else if (bmd->flag & eBooleanModifierFlag_Collection) { - Collection *collection = bmd->collection; - /* Allow collection to be empty: then target mesh will just removed self-intersections. */ - if (collection) { - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, ob) { - if (ob->type == OB_MESH && ob != ctx->object) { - Mesh *collection_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob, false); - BKE_mesh_wrapper_ensure_mdata(collection_mesh); - BLI_array_append(meshes, collection_mesh); - BLI_array_append(obmats, &ob->obmat); - remap = get_material_remap(ctx->object, ob); - BLI_array_append(material_remaps, remap); - } - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - } - } - - const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0; - const bool hole_tolerant = (bmd->flag & eBooleanModifierFlag_HoleTolerant) != 0; - result = BKE_mesh_boolean((const Mesh **)meshes, - (const float(**)[4][4])obmats, - (const short **)material_remaps, - BLI_array_len(meshes), - use_self, - hole_tolerant, - bmd->operation); - - BLI_array_free(meshes); - BLI_array_free(obmats); - for (int i = 0; i < BLI_array_len(material_remaps); i++) { - remap = material_remaps[i]; - if (remap) { - MEM_freeN(remap); - } - } - BLI_array_free(material_remaps); - -# ifdef DEBUG_TIME - TIMEIT_END(boolean_bmesh); -# endif - - return result; -} -#endif - -static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) -{ - BooleanModifierData *bmd = (BooleanModifierData *)md; - Object *object = ctx->object; - Mesh *result = mesh; - Mesh *mesh_operand_ob; - BMesh *bm; - Collection *collection = bmd->collection; - - bool is_flip = false; - const bool confirm_return = true; -#ifdef WITH_GMP - const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; - if (use_exact && bypass_bmesh) { - return exact_boolean_mesh(bmd, ctx, mesh); - } -#else - UNUSED_VARS(bypass_bmesh); - const bool use_exact = false; -#endif - -#ifdef DEBUG_TIME - TIMEIT_START(boolean_bmesh); -#endif - - if (bmd->flag & eBooleanModifierFlag_Object) { - if (bmd->object == NULL) { - return result; - } - - BMD_error_messages(ctx->object, md, NULL); - - Object *operand_ob = bmd->object; - -#ifdef DEBUG_TIME - TIMEIT_BLOCK_INIT(operand_get_evaluated_mesh); - TIMEIT_BLOCK_START(operand_get_evaluated_mesh); -#endif - mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, false); -#ifdef DEBUG_TIME - TIMEIT_BLOCK_END(operand_get_evaluated_mesh); - TIMEIT_BLOCK_STATS(operand_get_evaluated_mesh); -#endif - - if (mesh_operand_ob) { - /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! - * But for 2.90 better not try to be smart here. */ - BKE_mesh_wrapper_ensure_mdata(mesh_operand_ob); - /* when one of objects is empty (has got no faces) we could speed up - * calculation a bit returning one of objects' derived meshes (or empty one) - * Returning mesh is depended on modifiers operation (sergey) */ - result = get_quick_mesh(object, mesh, operand_ob, mesh_operand_ob, bmd->operation); - - if (result == NULL) { -#ifdef DEBUG_TIME - TIMEIT_BLOCK_INIT(object_BMD_mesh_bm_create); - TIMEIT_BLOCK_START(object_BMD_mesh_bm_create); -#endif - bm = BMD_mesh_bm_create(mesh, object, mesh_operand_ob, operand_ob, &is_flip); -#ifdef DEBUG_TIME - TIMEIT_BLOCK_END(object_BMD_mesh_bm_create); - TIMEIT_BLOCK_STATS(object_BMD_mesh_bm_create); -#endif - -#ifdef DEBUG_TIME - TIMEIT_BLOCK_INIT(BMD_mesh_intersection); - TIMEIT_BLOCK_START(BMD_mesh_intersection); -#endif - BMD_mesh_intersection(bm, md, ctx, mesh_operand_ob, object, operand_ob, is_flip); -#ifdef DEBUG_TIME - TIMEIT_BLOCK_END(BMD_mesh_intersection); - TIMEIT_BLOCK_STATS(BMD_mesh_intersection); -#endif - -#ifdef DEBUG_TIME - TIMEIT_BLOCK_INIT(BKE_mesh_from_bmesh_for_eval_nomain); - TIMEIT_BLOCK_START(BKE_mesh_from_bmesh_for_eval_nomain); -#endif - result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); -#ifdef DEBUG_TIME - TIMEIT_BLOCK_END(BKE_mesh_from_bmesh_for_eval_nomain); - TIMEIT_BLOCK_STATS(BKE_mesh_from_bmesh_for_eval_nomain); -#endif - BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - } - - /* if new mesh returned, return it; otherwise there was - * an error, so delete the modifier object */ - if (result == NULL) { - BKE_modifier_set_error(object, md, "Cannot execute boolean operation"); - } - } - } - - else { - if (collection == NULL && !use_exact) { - return result; - } - - /* Return result for certain errors. */ - if (BMD_error_messages(ctx->object, md, collection) == confirm_return) { - return result; - } - - if (use_exact) { - result = collection_boolean_exact(bmd, ctx, mesh); - } - else { - FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, operand_ob) { - if (operand_ob->type == OB_MESH && operand_ob != ctx->object) { - - mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, - false); - - if (mesh_operand_ob) { - /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! - * But for 2.90 better not try to be smart here. */ - BKE_mesh_wrapper_ensure_mdata(mesh_operand_ob); - - bm = BMD_mesh_bm_create(mesh, object, mesh_operand_ob, operand_ob, &is_flip); - - BMD_mesh_intersection(bm, md, ctx, mesh_operand_ob, object, operand_ob, is_flip); - - /* Needed for multiple objects to work. */ - BM_mesh_bm_to_me(NULL, - bm, - mesh, - (&(struct BMeshToMeshParams){ - .calc_object_remap = false, - })); - - result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh); - BM_mesh_free(bm); - result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; - } - } - } - FOREACH_COLLECTION_OBJECT_RECURSIVE_END; - } - } - -#ifdef DEBUG_TIME - TIMEIT_END(boolean_bmesh); -#endif - - return result; -} - -static void requiredDataMask(Object *UNUSED(ob), - ModifierData *UNUSED(md), - CustomData_MeshMasks *r_cddata_masks) -{ - r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT; - r_cddata_masks->emask |= CD_MASK_MEDGE; - r_cddata_masks->fmask |= CD_MASK_MTFACE; -} - -static void panel_draw(const bContext *UNUSED(C), Panel *panel) -{ - uiLayout *layout = panel->layout; - - PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); - - uiItemR(layout, ptr, "operation", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - - uiLayoutSetPropSep(layout, true); - - uiItemR(layout, ptr, "operand_type", 0, NULL, ICON_NONE); - - const bool operand_object = RNA_enum_get(ptr, "operand_type") == eBooleanModifierFlag_Object; - - if (operand_object) { - uiItemR(layout, ptr, "object", 0, NULL, ICON_NONE); - } - else { - uiItemR(layout, ptr, "collection", 0, NULL, ICON_NONE); - } - - uiItemR(layout, ptr, "solver", UI_ITEM_R_EXPAND, NULL, ICON_NONE); - - modifier_panel_end(layout, ptr); -} - -static void solver_options_panel_draw(const bContext *UNUSED(C), Panel *panel) -{ - uiLayout *layout = panel->layout; - - PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); - - const bool use_exact = RNA_enum_get(ptr, "solver") == eBooleanModifierSolver_Exact; - const bool operand_object = RNA_enum_get(ptr, "operand_type") == eBooleanModifierFlag_Object; - - uiLayoutSetPropSep(layout, true); - - uiLayout *col = uiLayoutColumn(layout, true); - if (use_exact) { - /* When operand is collection, we always use_self. */ - if (operand_object) { - uiItemR(col, ptr, "use_self", 0, NULL, ICON_NONE); - } - uiItemR(col, ptr, "use_hole_tolerant", 0, NULL, ICON_NONE); - } - else { - uiItemR(col, ptr, "double_threshold", 0, NULL, ICON_NONE); - } - - if (G.debug) { - uiItemR(col, ptr, "debug_options", 0, NULL, ICON_NONE); - } -} - -static void panelRegister(ARegionType *region_type) -{ - PanelType *panel = modifier_panel_register(region_type, eModifierType_Boolean, panel_draw); - modifier_subpanel_register( - region_type, "solver_options", "Solver Options", NULL, solver_options_panel_draw, panel); -} - -ModifierTypeInfo modifierType_Boolean = { - /* name */ "Boolean", - /* structName */ "BooleanModifierData", - /* structSize */ sizeof(BooleanModifierData), - /* srna */ &RNA_BooleanModifier, - /* type */ eModifierTypeType_Nonconstructive, - /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode, - /* icon */ ICON_MOD_BOOLEAN, - - /* copyData */ BKE_modifier_copydata_generic, - - /* deformVerts */ NULL, - /* deformMatrices */ NULL, - /* deformVertsEM */ NULL, - /* deformMatricesEM */ NULL, - /* modifyMesh */ modifyMesh, - /* modifyHair */ NULL, - /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, - - /* initData */ initData, - /* requiredDataMask */ requiredDataMask, - /* freeData */ NULL, - /* isDisabled */ isDisabled, - /* updateDepsgraph */ updateDepsgraph, - /* dependsOnTime */ NULL, - /* dependsOnNormals */ NULL, - /* foreachIDLink */ foreachIDLink, - /* foreachTexLink */ NULL, - /* freeRuntimeData */ NULL, - /* panelRegister */ panelRegister, - /* blendWrite */ NULL, - /* blendRead */ NULL, -}; diff --git a/source/blender/modifiers/intern/MOD_boolean.cc b/source/blender/modifiers/intern/MOD_boolean.cc new file mode 100644 index 00000000000..4b9b24e4e47 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_boolean.cc @@ -0,0 +1,651 @@ +/* + * 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) 2005 by the Blender Foundation. + * All rights reserved. + */ + +/** \file + * \ingroup modifiers + */ + +#include <cstdio> + +#include "BLI_utildefines.h" + +#include "BLI_array.hh" +#include "BLI_float4x4.hh" +#include "BLI_math_geom.h" +#include "BLI_math_matrix.h" +#include "BLI_vector.hh" + +#include "BLT_translation.h" + +#include "DNA_collection_types.h" +#include "DNA_defaults.h" +#include "DNA_mesh_types.h" +#include "DNA_meshdata_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" +#include "DNA_screen_types.h" + +#include "BKE_collection.h" +#include "BKE_context.h" +#include "BKE_global.h" /* only to check G.debug */ +#include "BKE_lib_id.h" +#include "BKE_lib_query.h" +#include "BKE_material.h" +#include "BKE_mesh.h" +#include "BKE_mesh_boolean_convert.hh" +#include "BKE_mesh_wrapper.h" +#include "BKE_modifier.h" + +#include "UI_interface.h" +#include "UI_resources.h" + +#include "RNA_access.h" + +#include "MOD_ui_common.h" +#include "MOD_util.h" + +#include "DEG_depsgraph_query.h" + +#include "MEM_guardedalloc.h" + +#include "bmesh.h" +#include "bmesh_tools.h" +#include "tools/bmesh_boolean.h" +#include "tools/bmesh_intersect.h" + +// #define DEBUG_TIME + +#ifdef DEBUG_TIME +# include "BLI_timeit.hh" +#endif + +using blender::Array; +using blender::float4x4; +using blender::Vector; + +static void initData(ModifierData *md) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + + BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(bmd, modifier)); + + MEMCPY_STRUCT_AFTER(bmd, DNA_struct_default_get(BooleanModifierData), modifier); +} + +static bool isDisabled(const struct Scene *UNUSED(scene), + ModifierData *md, + bool UNUSED(useRenderParams)) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + Collection *col = bmd->collection; + + if (bmd->flag & eBooleanModifierFlag_Object) { + return !bmd->object || bmd->object->type != OB_MESH; + } + if (bmd->flag & eBooleanModifierFlag_Collection) { + /* The Exact solver tolerates an empty collection. */ + return !col && bmd->solver != eBooleanModifierSolver_Exact; + } + return false; +} + +static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + + walk(userData, ob, (ID **)&bmd->collection, IDWALK_CB_NOP); + walk(userData, ob, (ID **)&bmd->object, IDWALK_CB_NOP); +} + +static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + if ((bmd->flag & eBooleanModifierFlag_Object) && bmd->object != nullptr) { + DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_TRANSFORM, "Boolean Modifier"); + DEG_add_object_relation(ctx->node, bmd->object, DEG_OB_COMP_GEOMETRY, "Boolean Modifier"); + } + + Collection *col = bmd->collection; + + if ((bmd->flag & eBooleanModifierFlag_Collection) && col != nullptr) { + DEG_add_collection_geometry_relation(ctx->node, col, "Boolean Modifier"); + } + /* We need own transformation as well. */ + DEG_add_modifier_to_transform_relation(ctx->node, "Boolean Modifier"); +} + +static Mesh *get_quick_mesh( + Object *ob_self, Mesh *mesh_self, Object *ob_operand_ob, Mesh *mesh_operand_ob, int operation) +{ + Mesh *result = nullptr; + + if (mesh_self->totpoly == 0 || mesh_operand_ob->totpoly == 0) { + switch (operation) { + case eBooleanModifierOp_Intersect: + result = BKE_mesh_new_nomain(0, 0, 0, 0, 0); + break; + + case eBooleanModifierOp_Union: + if (mesh_self->totpoly != 0) { + result = mesh_self; + } + else { + result = (Mesh *)BKE_id_copy_ex( + nullptr, &mesh_operand_ob->id, nullptr, LIB_ID_COPY_LOCALIZE); + + float imat[4][4]; + float omat[4][4]; + invert_m4_m4(imat, ob_self->obmat); + mul_m4_m4m4(omat, imat, ob_operand_ob->obmat); + + const int mverts_len = result->totvert; + MVert *mv = result->mvert; + + for (int i = 0; i < mverts_len; i++, mv++) { + mul_m4_v3(omat, mv->co); + } + + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + + break; + + case eBooleanModifierOp_Difference: + result = mesh_self; + break; + } + } + + return result; +} + +/* has no meaning for faces, do this so we can tell which face is which */ +#define BM_FACE_TAG BM_ELEM_DRAW + +/** + * Compare selected/unselected. + */ +static int bm_face_isect_pair(BMFace *f, void *UNUSED(user_data)) +{ + return BM_elem_flag_test(f, BM_FACE_TAG) ? 1 : 0; +} + +static bool BMD_error_messages(const Object *ob, ModifierData *md) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + Collection *col = bmd->collection; + + bool error_returns_result = false; + + const bool operand_collection = (bmd->flag & eBooleanModifierFlag_Collection) != 0; + const bool use_exact = bmd->solver == eBooleanModifierSolver_Exact; + const bool operation_intersect = bmd->operation == eBooleanModifierOp_Intersect; + +#ifndef WITH_GMP + /* If compiled without GMP, return a error. */ + if (use_exact) { + BKE_modifier_set_error(ob, md, "Compiled without GMP, using fast solver"); + error_returns_result = false; + } +#endif + + /* If intersect is selected using fast solver, return a error. */ + if (operand_collection && operation_intersect && !use_exact) { + BKE_modifier_set_error(ob, md, "Cannot execute, intersect only available using exact solver"); + error_returns_result = true; + } + + /* If the selected collection is empty and using fast solver, return a error. */ + if (operand_collection) { + if (!use_exact && BKE_collection_is_empty(col)) { + BKE_modifier_set_error(ob, md, "Cannot execute, fast solver and empty collection"); + error_returns_result = true; + } + + /* If the selected collection contain non mesh objects, return a error. */ + if (col) { + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (col, operand_ob) { + if (operand_ob->type != OB_MESH) { + BKE_modifier_set_error( + ob, md, "Cannot execute, the selected collection contains non mesh objects"); + error_returns_result = true; + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } + } + + return error_returns_result; +} + +static BMesh *BMD_mesh_bm_create( + Mesh *mesh, Object *object, Mesh *mesh_operand_ob, Object *operand_ob, bool *r_is_flip) +{ +#ifdef DEBUG_TIME + SCOPED_TIMER(__func__) +#endif + + *r_is_flip = (is_negative_m4(object->obmat) != is_negative_m4(operand_ob->obmat)); + + const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh, mesh_operand_ob); + + BMeshCreateParams bmcp = {false}; + BMesh *bm = BM_mesh_create(&allocsize, &bmcp); + + BMeshFromMeshParams params{}; + params.calc_face_normal = true; + BM_mesh_bm_from_me(bm, mesh_operand_ob, ¶ms); + + if (UNLIKELY(*r_is_flip)) { + const int cd_loop_mdisp_offset = CustomData_get_offset(&bm->ldata, CD_MDISPS); + BMIter iter; + BMFace *efa; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + BM_face_normal_flip_ex(bm, efa, cd_loop_mdisp_offset, true); + } + } + + BM_mesh_bm_from_me(bm, mesh, ¶ms); + + return bm; +} + +static void BMD_mesh_intersection(BMesh *bm, + ModifierData *md, + const ModifierEvalContext *ctx, + Mesh *mesh_operand_ob, + Object *object, + Object *operand_ob, + bool is_flip) +{ +#ifdef DEBUG_TIME + SCOPED_TIMER(__func__) +#endif + + BooleanModifierData *bmd = (BooleanModifierData *)md; + + /* main bmesh intersection setup */ + /* create tessface & intersect */ + const int looptris_tot = poly_to_tri_count(bm->totface, bm->totloop); + BMLoop *(*looptris)[3] = (BMLoop * (*)[3]) + MEM_malloc_arrayN(looptris_tot, sizeof(*looptris), __func__); + + BM_mesh_calc_tessellation_beauty(bm, looptris); + + /* postpone this until after tessellating + * so we can use the original normals before the vertex are moved */ + { + BMIter iter; + int i; + const int i_verts_end = mesh_operand_ob->totvert; + const int i_faces_end = mesh_operand_ob->totpoly; + + float imat[4][4]; + float omat[4][4]; + invert_m4_m4(imat, object->obmat); + mul_m4_m4m4(omat, imat, operand_ob->obmat); + + BMVert *eve; + i = 0; + BM_ITER_MESH (eve, &iter, bm, BM_VERTS_OF_MESH) { + mul_m4_v3(omat, eve->co); + if (++i == i_verts_end) { + break; + } + } + + /* we need face normals because of 'BM_face_split_edgenet' + * we could calculate on the fly too (before calling split). */ + float nmat[3][3]; + copy_m3_m4(nmat, omat); + invert_m3(nmat); + + if (UNLIKELY(is_flip)) { + negate_m3(nmat); + } + + Array<short> material_remap(operand_ob->totcol ? operand_ob->totcol : 1); + + /* Using original (not evaluated) object here since we are writing to it. */ + /* XXX Pretty sure comment above is fully wrong now with CoW & co ? */ + BKE_object_material_remap_calc(ctx->object, operand_ob, material_remap.data()); + + BMFace *efa; + i = 0; + BM_ITER_MESH (efa, &iter, bm, BM_FACES_OF_MESH) { + mul_transposed_m3_v3(nmat, efa->no); + normalize_v3(efa->no); + + /* Temp tag to test which side split faces are from. */ + BM_elem_flag_enable(efa, BM_FACE_TAG); + + /* remap material */ + if (LIKELY(efa->mat_nr < operand_ob->totcol)) { + efa->mat_nr = material_remap[efa->mat_nr]; + } + + if (++i == i_faces_end) { + break; + } + } + } + + /* not needed, but normals for 'dm' will be invalid, + * currently this is ok for 'BM_mesh_intersect' */ + // BM_mesh_normals_update(bm); + + bool use_separate = false; + bool use_dissolve = true; + bool use_island_connect = true; + + /* change for testing */ + if (G.debug & G_DEBUG) { + use_separate = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_Separate) != 0; + use_dissolve = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoDissolve) == 0; + use_island_connect = (bmd->bm_flag & eBooleanModifierBMeshFlag_BMesh_NoConnectRegions) == 0; + } + + BM_mesh_intersect(bm, + looptris, + looptris_tot, + bm_face_isect_pair, + nullptr, + false, + use_separate, + use_dissolve, + use_island_connect, + false, + false, + bmd->operation, + bmd->double_threshold); + + MEM_freeN(looptris); +} + +#ifdef WITH_GMP + +/* Get a mapping from material slot numbers in the src_ob to slot numbers in the dst_ob. + * If a material doesn't exist in the dst_ob, the mapping just goes to the same slot + * or to zero if there aren't enough slots in the destination. + * Caller owns the returned array. */ +static Array<short> get_material_remap(Object *dest_ob, Object *src_ob) +{ + int n = dest_ob->totcol; + if (n <= 0) { + n = 1; + } + Array<short> remap(n); + BKE_object_material_remap_calc(dest_ob, src_ob, remap.data()); + return remap; +} + +static Mesh *exact_boolean_mesh(BooleanModifierData *bmd, + const ModifierEvalContext *ctx, + Mesh *mesh) +{ + Vector<const Mesh *> meshes; + Vector<float4x4 *> obmats; + Vector<Array<short>> material_remaps; + +# ifdef DEBUG_TIME + SCOPED_TIMER(__func__) +# endif + + if ((bmd->flag & eBooleanModifierFlag_Object) && bmd->object == nullptr) { + return mesh; + } + + meshes.append(mesh); + obmats.append((float4x4 *)&ctx->object->obmat); + material_remaps.append({}); + if (bmd->flag & eBooleanModifierFlag_Object) { + Mesh *mesh_operand = BKE_modifier_get_evaluated_mesh_from_evaluated_object(bmd->object, false); + if (!mesh_operand) { + return mesh; + } + BKE_mesh_wrapper_ensure_mdata(mesh_operand); + meshes.append(mesh_operand); + obmats.append((float4x4 *)&bmd->object->obmat); + material_remaps.append(get_material_remap(ctx->object, bmd->object)); + } + else if (bmd->flag & eBooleanModifierFlag_Collection) { + Collection *collection = bmd->collection; + /* Allow collection to be empty; then target mesh will just removed self-intersections. */ + if (collection) { + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, ob) { + if (ob->type == OB_MESH && ob != ctx->object) { + Mesh *collection_mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(ob, false); + if (!collection_mesh) { + continue; + } + BKE_mesh_wrapper_ensure_mdata(collection_mesh); + meshes.append(collection_mesh); + obmats.append((float4x4 *)&ob->obmat); + material_remaps.append(get_material_remap(ctx->object, ob)); + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } + } + + const bool use_self = (bmd->flag & eBooleanModifierFlag_Self) != 0; + const bool hole_tolerant = (bmd->flag & eBooleanModifierFlag_HoleTolerant) != 0; + return blender::meshintersect::direct_mesh_boolean(meshes, + obmats, + *(float4x4 *)&ctx->object->obmat, + material_remaps, + use_self, + hole_tolerant, + bmd->operation); +} +#endif + +static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) +{ + BooleanModifierData *bmd = (BooleanModifierData *)md; + Object *object = ctx->object; + Mesh *result = mesh; + Collection *collection = bmd->collection; + + /* Return result for certain errors. */ + if (BMD_error_messages(ctx->object, md)) { + return result; + } + +#ifdef WITH_GMP + if (bmd->solver == eBooleanModifierSolver_Exact) { + return exact_boolean_mesh(bmd, ctx, mesh); + } +#endif + +#ifdef DEBUG_TIME + SCOPED_TIMER(__func__) +#endif + + if (bmd->flag & eBooleanModifierFlag_Object) { + if (bmd->object == nullptr) { + return result; + } + + Object *operand_ob = bmd->object; + + Mesh *mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, + false); + + if (mesh_operand_ob) { + /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! + * But for 2.90 better not try to be smart here. */ + BKE_mesh_wrapper_ensure_mdata(mesh_operand_ob); + /* when one of objects is empty (has got no faces) we could speed up + * calculation a bit returning one of objects' derived meshes (or empty one) + * Returning mesh is depended on modifiers operation (sergey) */ + result = get_quick_mesh(object, mesh, operand_ob, mesh_operand_ob, bmd->operation); + + if (result == nullptr) { + bool is_flip; + BMesh *bm = BMD_mesh_bm_create(mesh, object, mesh_operand_ob, operand_ob, &is_flip); + + BMD_mesh_intersection(bm, md, ctx, mesh_operand_ob, object, operand_ob, is_flip); + + result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); + + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + + if (result == nullptr) { + BKE_modifier_set_error(object, md, "Cannot execute boolean operation"); + } + } + } + else { + if (collection == nullptr) { + return result; + } + + FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, operand_ob) { + if (operand_ob->type == OB_MESH && operand_ob != ctx->object) { + Mesh *mesh_operand_ob = BKE_modifier_get_evaluated_mesh_from_evaluated_object(operand_ob, + false); + + if (mesh_operand_ob) { + /* XXX This is utterly non-optimal, we may go from a bmesh to a mesh back to a bmesh! + * But for 2.90 better not try to be smart here. */ + BKE_mesh_wrapper_ensure_mdata(mesh_operand_ob); + + bool is_flip; + BMesh *bm = BMD_mesh_bm_create(mesh, object, mesh_operand_ob, operand_ob, &is_flip); + + BMD_mesh_intersection(bm, md, ctx, mesh_operand_ob, object, operand_ob, is_flip); + + /* Needed for multiple objects to work. */ + BMeshToMeshParams params{}; + params.calc_object_remap = false; + BM_mesh_bm_to_me(nullptr, bm, mesh, ¶ms); + + result = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh); + BM_mesh_free(bm); + result->runtime.cd_dirty_vert |= CD_MASK_NORMAL; + } + } + } + FOREACH_COLLECTION_OBJECT_RECURSIVE_END; + } + + return result; +} + +static void requiredDataMask(Object *UNUSED(ob), + ModifierData *UNUSED(md), + CustomData_MeshMasks *r_cddata_masks) +{ + r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT; + r_cddata_masks->emask |= CD_MASK_MEDGE; + r_cddata_masks->fmask |= CD_MASK_MTFACE; +} + +static void panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); + + uiItemR(layout, ptr, "operation", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + + uiLayoutSetPropSep(layout, true); + + uiItemR(layout, ptr, "operand_type", 0, nullptr, ICON_NONE); + if (RNA_enum_get(ptr, "operand_type") == eBooleanModifierFlag_Object) { + uiItemR(layout, ptr, "object", 0, nullptr, ICON_NONE); + } + else { + uiItemR(layout, ptr, "collection", 0, nullptr, ICON_NONE); + } + + uiItemR(layout, ptr, "solver", UI_ITEM_R_EXPAND, nullptr, ICON_NONE); + + modifier_panel_end(layout, ptr); +} + +static void solver_options_panel_draw(const bContext *UNUSED(C), Panel *panel) +{ + uiLayout *layout = panel->layout; + PointerRNA *ptr = modifier_panel_get_property_pointers(panel, nullptr); + + const bool use_exact = RNA_enum_get(ptr, "solver") == eBooleanModifierSolver_Exact; + + uiLayoutSetPropSep(layout, true); + + uiLayout *col = uiLayoutColumn(layout, true); + if (use_exact) { + /* When operand is collection, we always use_self. */ + if (RNA_enum_get(ptr, "operand_type") == eBooleanModifierFlag_Object) { + uiItemR(col, ptr, "use_self", 0, nullptr, ICON_NONE); + } + uiItemR(col, ptr, "use_hole_tolerant", 0, nullptr, ICON_NONE); + } + else { + uiItemR(col, ptr, "double_threshold", 0, nullptr, ICON_NONE); + } + + if (G.debug) { + uiItemR(col, ptr, "debug_options", 0, nullptr, ICON_NONE); + } +} + +static void panelRegister(ARegionType *region_type) +{ + PanelType *panel = modifier_panel_register(region_type, eModifierType_Boolean, panel_draw); + modifier_subpanel_register( + region_type, "solver_options", "Solver Options", nullptr, solver_options_panel_draw, panel); +} + +ModifierTypeInfo modifierType_Boolean = { + /* name */ "Boolean", + /* structName */ "BooleanModifierData", + /* structSize */ sizeof(BooleanModifierData), + /* srna */ &RNA_BooleanModifier, + /* type */ eModifierTypeType_Nonconstructive, + /* flags */ + (ModifierTypeFlag)(eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsEditmode), + /* icon */ ICON_MOD_BOOLEAN, + + /* copyData */ BKE_modifier_copydata_generic, + + /* deformVerts */ nullptr, + /* deformMatrices */ nullptr, + /* deformVertsEM */ nullptr, + /* deformMatricesEM */ nullptr, + /* modifyMesh */ modifyMesh, + /* modifyHair */ nullptr, + /* modifyGeometrySet */ nullptr, + + /* initData */ initData, + /* requiredDataMask */ requiredDataMask, + /* freeData */ nullptr, + /* isDisabled */ isDisabled, + /* updateDepsgraph */ updateDepsgraph, + /* dependsOnTime */ nullptr, + /* dependsOnNormals */ nullptr, + /* foreachIDLink */ foreachIDLink, + /* foreachTexLink */ nullptr, + /* freeRuntimeData */ nullptr, + /* panelRegister */ panelRegister, + /* blendWrite */ nullptr, + /* blendRead */ nullptr, +}; diff --git a/source/blender/modifiers/intern/MOD_build.c b/source/blender/modifiers/intern/MOD_build.c index 0b1c661baed..c38e5126f6b 100644 --- a/source/blender/modifiers/intern/MOD_build.c +++ b/source/blender/modifiers/intern/MOD_build.c @@ -347,7 +347,6 @@ ModifierTypeInfo modifierType_Build = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_cast.c b/source/blender/modifiers/intern/MOD_cast.c index f905a38ae12..715bc26e5d3 100644 --- a/source/blender/modifiers/intern/MOD_cast.c +++ b/source/blender/modifiers/intern/MOD_cast.c @@ -591,7 +591,6 @@ ModifierTypeInfo modifierType_Cast = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_cloth.c b/source/blender/modifiers/intern/MOD_cloth.c index a25d65347c5..40d027f3044 100644 --- a/source/blender/modifiers/intern/MOD_cloth.c +++ b/source/blender/modifiers/intern/MOD_cloth.c @@ -311,7 +311,6 @@ ModifierTypeInfo modifierType_Cloth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_collision.c b/source/blender/modifiers/intern/MOD_collision.c index e72e0279263..5dd57469914 100644 --- a/source/blender/modifiers/intern/MOD_collision.c +++ b/source/blender/modifiers/intern/MOD_collision.c @@ -317,7 +317,6 @@ ModifierTypeInfo modifierType_Collision = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_correctivesmooth.c b/source/blender/modifiers/intern/MOD_correctivesmooth.c index 001c7d8d098..fef235b456b 100644 --- a/source/blender/modifiers/intern/MOD_correctivesmooth.c +++ b/source/blender/modifiers/intern/MOD_correctivesmooth.c @@ -852,7 +852,6 @@ ModifierTypeInfo modifierType_CorrectiveSmooth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_curve.c b/source/blender/modifiers/intern/MOD_curve.c index d5d53edfd54..20dbb299767 100644 --- a/source/blender/modifiers/intern/MOD_curve.c +++ b/source/blender/modifiers/intern/MOD_curve.c @@ -236,7 +236,6 @@ ModifierTypeInfo modifierType_Curve = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_datatransfer.c b/source/blender/modifiers/intern/MOD_datatransfer.c index 8b299a82f95..dbdc76f0edc 100644 --- a/source/blender/modifiers/intern/MOD_datatransfer.c +++ b/source/blender/modifiers/intern/MOD_datatransfer.c @@ -495,7 +495,6 @@ ModifierTypeInfo modifierType_DataTransfer = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_decimate.c b/source/blender/modifiers/intern/MOD_decimate.c index 03db09a5aec..faad1175f3a 100644 --- a/source/blender/modifiers/intern/MOD_decimate.c +++ b/source/blender/modifiers/intern/MOD_decimate.c @@ -300,7 +300,6 @@ ModifierTypeInfo modifierType_Decimate = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_displace.c b/source/blender/modifiers/intern/MOD_displace.c index abe78943508..a7ac9f618af 100644 --- a/source/blender/modifiers/intern/MOD_displace.c +++ b/source/blender/modifiers/intern/MOD_displace.c @@ -507,7 +507,6 @@ ModifierTypeInfo modifierType_Displace = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_dynamicpaint.c b/source/blender/modifiers/intern/MOD_dynamicpaint.c index 3e607e88cdd..8b1d541d45d 100644 --- a/source/blender/modifiers/intern/MOD_dynamicpaint.c +++ b/source/blender/modifiers/intern/MOD_dynamicpaint.c @@ -221,7 +221,6 @@ ModifierTypeInfo modifierType_DynamicPaint = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_edgesplit.c b/source/blender/modifiers/intern/MOD_edgesplit.c index e02befd7efa..2874bebe13a 100644 --- a/source/blender/modifiers/intern/MOD_edgesplit.c +++ b/source/blender/modifiers/intern/MOD_edgesplit.c @@ -187,7 +187,6 @@ ModifierTypeInfo modifierType_EdgeSplit = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_explode.c b/source/blender/modifiers/intern/MOD_explode.c index c12019a325e..e1197439c7c 100644 --- a/source/blender/modifiers/intern/MOD_explode.c +++ b/source/blender/modifiers/intern/MOD_explode.c @@ -1255,7 +1255,6 @@ ModifierTypeInfo modifierType_Explode = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_fluid.c b/source/blender/modifiers/intern/MOD_fluid.c index 8a8d6f2305f..36d2ab2a11a 100644 --- a/source/blender/modifiers/intern/MOD_fluid.c +++ b/source/blender/modifiers/intern/MOD_fluid.c @@ -239,7 +239,6 @@ ModifierTypeInfo modifierType_Fluid = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_hook.c b/source/blender/modifiers/intern/MOD_hook.c index 2fa05a319d5..ff581e92cdd 100644 --- a/source/blender/modifiers/intern/MOD_hook.c +++ b/source/blender/modifiers/intern/MOD_hook.c @@ -574,7 +574,6 @@ ModifierTypeInfo modifierType_Hook = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_laplaciandeform.c b/source/blender/modifiers/intern/MOD_laplaciandeform.c index bda0f9ba5a4..6efeec1970f 100644 --- a/source/blender/modifiers/intern/MOD_laplaciandeform.c +++ b/source/blender/modifiers/intern/MOD_laplaciandeform.c @@ -889,7 +889,6 @@ ModifierTypeInfo modifierType_LaplacianDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_laplaciansmooth.c b/source/blender/modifiers/intern/MOD_laplaciansmooth.c index fc527304e76..78e0bf3fa8f 100644 --- a/source/blender/modifiers/intern/MOD_laplaciansmooth.c +++ b/source/blender/modifiers/intern/MOD_laplaciansmooth.c @@ -635,7 +635,6 @@ ModifierTypeInfo modifierType_LaplacianSmooth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ init_data, /* requiredDataMask */ required_data_mask, diff --git a/source/blender/modifiers/intern/MOD_lattice.c b/source/blender/modifiers/intern/MOD_lattice.c index e3c42e39dda..29d1ecf6050 100644 --- a/source/blender/modifiers/intern/MOD_lattice.c +++ b/source/blender/modifiers/intern/MOD_lattice.c @@ -193,7 +193,6 @@ ModifierTypeInfo modifierType_Lattice = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_mask.cc b/source/blender/modifiers/intern/MOD_mask.cc index 191d39d9fce..a77f6cfe8ba 100644 --- a/source/blender/modifiers/intern/MOD_mask.cc +++ b/source/blender/modifiers/intern/MOD_mask.cc @@ -69,6 +69,19 @@ using blender::MutableSpan; using blender::Span; using blender::Vector; +/* For delete geometry node. */ +void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span<int> vertex_map); +void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map); +void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map, + Span<int> masked_poly_indices, + Span<int> new_loop_starts); + static void initData(ModifierData *md) { MaskModifierData *mmd = (MaskModifierData *)md; @@ -237,9 +250,7 @@ static void computed_masked_polygons(const Mesh *mesh, *r_num_masked_loops = num_masked_loops; } -static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, - Mesh &dst_mesh, - Span<int> vertex_map) +void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, Mesh &dst_mesh, Span<int> vertex_map) { BLI_assert(src_mesh.totvert == vertex_map.size()); for (const int i_src : vertex_map.index_range()) { @@ -256,10 +267,10 @@ static void copy_masked_vertices_to_new_mesh(const Mesh &src_mesh, } } -static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, - Mesh &dst_mesh, - Span<int> vertex_map, - Span<int> edge_map) +void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map) { BLI_assert(src_mesh.totvert == vertex_map.size()); BLI_assert(src_mesh.totedge == edge_map.size()); @@ -279,12 +290,12 @@ static void copy_masked_edges_to_new_mesh(const Mesh &src_mesh, } } -static void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, - Mesh &dst_mesh, - Span<int> vertex_map, - Span<int> edge_map, - Span<int> masked_poly_indices, - Span<int> new_loop_starts) +void copy_masked_polys_to_new_mesh(const Mesh &src_mesh, + Mesh &dst_mesh, + Span<int> vertex_map, + Span<int> edge_map, + Span<int> masked_poly_indices, + Span<int> new_loop_starts) { for (const int i_dst : masked_poly_indices.index_range()) { const int i_src = masked_poly_indices[i_dst]; @@ -463,7 +474,6 @@ ModifierTypeInfo modifierType_Mask = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ nullptr, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc index cc007651733..778b5746471 100644 --- a/source/blender/modifiers/intern/MOD_mesh_to_volume.cc +++ b/source/blender/modifiers/intern/MOD_mesh_to_volume.cc @@ -20,6 +20,7 @@ #include <vector> +#include "BKE_geometry_set.hh" #include "BKE_lib_query.h" #include "BKE_mesh_runtime.h" #include "BKE_mesh_wrapper.h" @@ -204,7 +205,9 @@ static float compute_voxel_size(const ModifierEvalContext *ctx, } #endif -static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Volume *input_volume) +static Volume *mesh_to_volume(ModifierData *md, + const ModifierEvalContext *ctx, + Volume *input_volume) { #ifdef WITH_OPENVDB using namespace blender; @@ -278,6 +281,17 @@ static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Vo #endif } +static void modifyGeometrySet(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet *geometry_set) +{ + Volume *input_volume = geometry_set->get_volume_for_write(); + Volume *result_volume = mesh_to_volume(md, ctx, input_volume); + if (result_volume != input_volume) { + geometry_set->replace_volume(result_volume); + } +} + ModifierTypeInfo modifierType_MeshToVolume = { /* name */ "Mesh to Volume", /* structName */ "MeshToVolumeModifierData", @@ -295,8 +309,7 @@ ModifierTypeInfo modifierType_MeshToVolume = { /* deformMatricesEM */ nullptr, /* modifyMesh */ nullptr, /* modifyHair */ nullptr, - /* modifyGeometrySet */ nullptr, - /* modifyVolume */ modifyVolume, + /* modifyGeometrySet */ modifyGeometrySet, /* initData */ initData, /* requiredDataMask */ nullptr, diff --git a/source/blender/modifiers/intern/MOD_meshcache.c b/source/blender/modifiers/intern/MOD_meshcache.c index 6d2e0f242d7..361454120ca 100644 --- a/source/blender/modifiers/intern/MOD_meshcache.c +++ b/source/blender/modifiers/intern/MOD_meshcache.c @@ -389,7 +389,6 @@ ModifierTypeInfo modifierType_MeshCache = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_meshdeform.c b/source/blender/modifiers/intern/MOD_meshdeform.c index a94dd6da477..8e1e03570b5 100644 --- a/source/blender/modifiers/intern/MOD_meshdeform.c +++ b/source/blender/modifiers/intern/MOD_meshdeform.c @@ -644,7 +644,6 @@ ModifierTypeInfo modifierType_MeshDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_meshsequencecache.c b/source/blender/modifiers/intern/MOD_meshsequencecache.c index 2c01857adb1..c2f9cd8c867 100644 --- a/source/blender/modifiers/intern/MOD_meshsequencecache.c +++ b/source/blender/modifiers/intern/MOD_meshsequencecache.c @@ -271,7 +271,6 @@ ModifierTypeInfo modifierType_MeshSequenceCache = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_mirror.c b/source/blender/modifiers/intern/MOD_mirror.c index afe94d8dead..6116cf8146a 100644 --- a/source/blender/modifiers/intern/MOD_mirror.c +++ b/source/blender/modifiers/intern/MOD_mirror.c @@ -165,6 +165,13 @@ static void panel_draw(const bContext *UNUSED(C), Panel *panel) uiLayoutSetActive(sub, RNA_boolean_get(ptr, "use_mirror_merge")); uiItemR(sub, ptr, "merge_threshold", 0, "", ICON_NONE); + bool is_bisect_set[3]; + RNA_boolean_get_array(ptr, "use_bisect_axis", is_bisect_set); + + sub = uiLayoutRow(col, true); + uiLayoutSetActive(sub, is_bisect_set[0] || is_bisect_set[1] || is_bisect_set[2]); + uiItemR(sub, ptr, "bisect_threshold", 0, IFACE_("Bisect Distance"), ICON_NONE); + modifier_panel_end(layout, ptr); } @@ -232,7 +239,6 @@ ModifierTypeInfo modifierType_Mirror = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_multires.c b/source/blender/modifiers/intern/MOD_multires.c index 1182c8db093..c3b34f3cd23 100644 --- a/source/blender/modifiers/intern/MOD_multires.c +++ b/source/blender/modifiers/intern/MOD_multires.c @@ -520,7 +520,6 @@ ModifierTypeInfo modifierType_Multires = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_nodes.cc b/source/blender/modifiers/intern/MOD_nodes.cc index 6236dc87791..8fa80025790 100644 --- a/source/blender/modifiers/intern/MOD_nodes.cc +++ b/source/blender/modifiers/intern/MOD_nodes.cc @@ -29,12 +29,14 @@ #include "BLI_float3.hh" #include "BLI_listbase.h" +#include "BLI_multi_value_map.hh" #include "BLI_set.hh" #include "BLI_string.h" #include "BLI_utildefines.h" #include "DNA_collection_types.h" #include "DNA_defaults.h" +#include "DNA_material_types.h" #include "DNA_mesh_types.h" #include "DNA_meshdata_types.h" #include "DNA_modifier_types.h" @@ -43,17 +45,23 @@ #include "DNA_pointcloud_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" +#include "DNA_space_types.h" +#include "DNA_windowmanager_types.h" #include "BKE_customdata.h" +#include "BKE_geometry_set_instances.hh" #include "BKE_global.h" #include "BKE_idprop.h" #include "BKE_lib_query.h" +#include "BKE_main.h" #include "BKE_mesh.h" #include "BKE_modifier.h" #include "BKE_node_ui_storage.hh" +#include "BKE_object.h" #include "BKE_pointcloud.h" #include "BKE_screen.h" #include "BKE_simulation.h" +#include "BKE_workspace.h" #include "BLO_read_write.h" @@ -68,13 +76,14 @@ #include "MOD_modifiertypes.h" #include "MOD_nodes.h" +#include "MOD_nodes_evaluator.hh" #include "MOD_ui_common.h" +#include "ED_spreadsheet.h" + #include "NOD_derived_node_tree.hh" #include "NOD_geometry.h" -#include "NOD_geometry_exec.hh" #include "NOD_node_tree_multi_function.hh" -#include "NOD_type_callbacks.hh" using blender::float3; using blender::FunctionRef; @@ -85,11 +94,8 @@ using blender::Span; using blender::StringRef; using blender::StringRefNull; using blender::Vector; -using blender::bke::PersistentCollectionHandle; -using blender::bke::PersistentDataHandleMap; -using blender::bke::PersistentObjectHandle; using blender::fn::GMutablePointer; -using blender::fn::GValueMap; +using blender::fn::GPointer; using blender::nodes::GeoNodeExecParams; using namespace blender::fn::multi_function_types; using namespace blender::nodes::derived_node_tree_types; @@ -118,6 +124,18 @@ static void addIdsUsedBySocket(const ListBase *sockets, Set<ID *> &ids) ids.add(&collection->id); } } + else if (socket->type == SOCK_MATERIAL) { + Material *material = ((bNodeSocketValueMaterial *)socket->default_value)->value; + if (material != nullptr) { + ids.add(&material->id); + } + } + else if (socket->type == SOCK_TEXTURE) { + Tex *texture = ((bNodeSocketValueTexture *)socket->default_value)->value; + if (texture != nullptr) { + ids.add(&texture->id); + } + } } } @@ -129,7 +147,7 @@ static void find_used_ids_from_nodes(const bNodeTree &tree, Set<ID *> &ids) addIdsUsedBySocket(&node->inputs, ids); addIdsUsedBySocket(&node->outputs, ids); - if (node->type == NODE_GROUP) { + if (ELEM(node->type, NODE_GROUP, NODE_CUSTOM_GROUP)) { const bNodeTree *group = (bNodeTree *)node->id; if (group != nullptr && handled_groups.add(group)) { find_used_ids_from_nodes(*group, ids); @@ -153,40 +171,34 @@ static void find_used_ids_from_settings(const NodesModifierSettings &settings, S &ids); } -static void add_collection_object_relations_recursive(const ModifierUpdateDepsgraphContext *ctx, - Collection &collection); +/* We don't know exactly what attributes from the other object we will need. */ +static const CustomData_MeshMasks dependency_data_mask{CD_MASK_PROP_ALL | CD_MASK_MDEFORMVERT, + CD_MASK_PROP_ALL, + CD_MASK_PROP_ALL, + CD_MASK_PROP_ALL, + CD_MASK_PROP_ALL}; + +static void add_collection_relation(const ModifierUpdateDepsgraphContext *ctx, + Collection &collection) +{ + DEG_add_collection_geometry_relation(ctx->node, &collection, "Nodes Modifier"); + DEG_add_collection_geometry_customdata_mask(ctx->node, &collection, &dependency_data_mask); +} static void add_object_relation(const ModifierUpdateDepsgraphContext *ctx, Object &object) { DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_TRANSFORM, "Nodes Modifier"); if (&(ID &)object != &ctx->object->id) { - if (object.type == OB_EMPTY) { - Collection *collection_instance = object.instance_collection; - if (collection_instance != nullptr) { - add_collection_object_relations_recursive(ctx, *collection_instance); - } + if (object.type == OB_EMPTY && object.instance_collection != nullptr) { + add_collection_relation(ctx, *object.instance_collection); } - else { + else if (DEG_object_has_geometry_component(&object)) { DEG_add_object_relation(ctx->node, &object, DEG_OB_COMP_GEOMETRY, "Nodes Modifier"); + DEG_add_customdata_mask(ctx->node, &object, &dependency_data_mask); } } } -static void add_collection_object_relations_recursive(const ModifierUpdateDepsgraphContext *ctx, - Collection &collection) -{ - LISTBASE_FOREACH (CollectionObject *, collection_object, &collection.gobject) { - BLI_assert(collection_object->ob != nullptr); - Object &object = *collection_object->ob; - add_object_relation(ctx, object); - } - LISTBASE_FOREACH (CollectionChild *, collection_child, &collection.children) { - BLI_assert(collection_child->collection != nullptr); - Collection &collection = *collection_child->collection; - add_collection_object_relations_recursive(ctx, collection); - } -} - static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx) { NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md); @@ -198,18 +210,28 @@ static void updateDepsgraph(ModifierData *md, const ModifierUpdateDepsgraphConte find_used_ids_from_settings(nmd->settings, used_ids); find_used_ids_from_nodes(*nmd->node_group, used_ids); for (ID *id : used_ids) { - if (GS(id->name) == ID_OB) { - Object *object = reinterpret_cast<Object *>(id); - add_object_relation(ctx, *object); - } - if (GS(id->name) == ID_GR) { - Collection *collection = reinterpret_cast<Collection *>(id); - add_collection_object_relations_recursive(ctx, *collection); + switch ((ID_Type)GS(id->name)) { + case ID_OB: { + Object *object = reinterpret_cast<Object *>(id); + add_object_relation(ctx, *object); + break; + } + case ID_GR: { + Collection *collection = reinterpret_cast<Collection *>(id); + add_collection_relation(ctx, *collection); + break; + } + case ID_TE: { + DEG_add_generic_id_relation(ctx->node, id, "Nodes Modifier"); + } + default: { + /* Purposefully don't add relations for materials. While there are material sockets, + * the pointers are only passed around as handles rather than dereferenced. */ + break; + } } } } - - /* TODO: Add dependency for adding and removing objects in collections. */ } static void foreachIDLink(ModifierData *md, Object *ob, IDWalkFunc walk, void *userData) @@ -252,368 +274,16 @@ static bool isDisabled(const struct Scene *UNUSED(scene), return false; } -class GeometryNodesEvaluator { - private: - blender::LinearAllocator<> allocator_; - Map<std::pair<DInputSocket, DOutputSocket>, GMutablePointer> value_by_input_; - Vector<DInputSocket> group_outputs_; - blender::nodes::MultiFunctionByNode &mf_by_node_; - const blender::nodes::DataTypeConversions &conversions_; - const PersistentDataHandleMap &handle_map_; - const Object *self_object_; - const ModifierData *modifier_; - Depsgraph *depsgraph_; - - public: - GeometryNodesEvaluator(const Map<DOutputSocket, GMutablePointer> &group_input_data, - Vector<DInputSocket> group_outputs, - blender::nodes::MultiFunctionByNode &mf_by_node, - const PersistentDataHandleMap &handle_map, - const Object *self_object, - const ModifierData *modifier, - Depsgraph *depsgraph) - : group_outputs_(std::move(group_outputs)), - mf_by_node_(mf_by_node), - conversions_(blender::nodes::get_implicit_type_conversions()), - handle_map_(handle_map), - self_object_(self_object), - modifier_(modifier), - depsgraph_(depsgraph) - { - for (auto item : group_input_data.items()) { - this->forward_to_inputs(item.key, item.value); - } - } - - Vector<GMutablePointer> execute() - { - Vector<GMutablePointer> results; - for (const DInputSocket &group_output : group_outputs_) { - Vector<GMutablePointer> result = this->get_input_values(group_output); - results.append(result[0]); - } - for (GMutablePointer value : value_by_input_.values()) { - value.destruct(); - } - return results; - } - - private: - Vector<GMutablePointer> get_input_values(const DInputSocket socket_to_compute) - { - Vector<DSocket> from_sockets; - socket_to_compute.foreach_origin_socket([&](DSocket socket) { from_sockets.append(socket); }); - - if (from_sockets.is_empty()) { - /* The input is not connected, use the value from the socket itself. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - return {get_unlinked_input_value(socket_to_compute, type)}; - } - - /* Multi-input sockets contain a vector of inputs. */ - if (socket_to_compute->is_multi_input_socket()) { - return this->get_inputs_from_incoming_links(socket_to_compute, from_sockets); - } - - const DSocket from_socket = from_sockets[0]; - GMutablePointer value = this->get_input_from_incoming_link(socket_to_compute, from_socket); - return {value}; - } - - Vector<GMutablePointer> get_inputs_from_incoming_links(const DInputSocket socket_to_compute, - const Span<DSocket> from_sockets) - { - Vector<GMutablePointer> values; - for (const int i : from_sockets.index_range()) { - const DSocket from_socket = from_sockets[i]; - const int first_occurence = from_sockets.take_front(i).first_index_try(from_socket); - if (first_occurence == -1) { - values.append(this->get_input_from_incoming_link(socket_to_compute, from_socket)); - } - else { - /* If the same from-socket occurs more than once, we make a copy of the first value. This - * can happen when a node linked to a multi-input-socket is muted. */ - GMutablePointer value = values[first_occurence]; - const CPPType *type = value.type(); - void *copy_buffer = allocator_.allocate(type->size(), type->alignment()); - type->copy_to_uninitialized(value.get(), copy_buffer); - values.append({type, copy_buffer}); - } - } - return values; - } - - GMutablePointer get_input_from_incoming_link(const DInputSocket socket_to_compute, - const DSocket from_socket) - { - if (from_socket->is_output()) { - const DOutputSocket from_output_socket{from_socket}; - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(socket_to_compute, - from_output_socket); - std::optional<GMutablePointer> value = value_by_input_.pop_try(key); - if (value.has_value()) { - /* This input has been computed before, return it directly. */ - return {*value}; - } - - /* Compute the socket now. */ - this->compute_output_and_forward(from_output_socket); - return {value_by_input_.pop(key)}; - } - - /* Get value from an unlinked input socket. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - const DInputSocket from_input_socket{from_socket}; - return {get_unlinked_input_value(from_input_socket, type)}; - } - - void compute_output_and_forward(const DOutputSocket socket_to_compute) - { - const DNode node{socket_to_compute.context(), &socket_to_compute->node()}; - - if (!socket_to_compute->is_available()) { - /* If the output is not available, use a default value. */ - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_to_compute->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(type.default_value(), buffer); - this->forward_to_inputs(socket_to_compute, {type, buffer}); - return; - } - - /* Prepare inputs required to execute the node. */ - GValueMap<StringRef> node_inputs_map{allocator_}; - for (const InputSocketRef *input_socket : node->inputs()) { - if (input_socket->is_available()) { - Vector<GMutablePointer> values = this->get_input_values({node.context(), input_socket}); - for (int i = 0; i < values.size(); ++i) { - /* Values from Multi Input Sockets are stored in input map with the format - * <identifier>[<index>]. */ - blender::StringRefNull key = allocator_.copy_string( - input_socket->identifier() + (i > 0 ? ("[" + std::to_string(i)) + "]" : "")); - node_inputs_map.add_new_direct(key, std::move(values[i])); - } - } - } - - /* Execute the node. */ - GValueMap<StringRef> node_outputs_map{allocator_}; - GeoNodeExecParams params{ - node, node_inputs_map, node_outputs_map, handle_map_, self_object_, modifier_, depsgraph_}; - this->execute_node(node, params); - - /* Forward computed outputs to linked input sockets. */ - for (const OutputSocketRef *output_socket : node->outputs()) { - if (output_socket->is_available()) { - GMutablePointer value = node_outputs_map.extract(output_socket->identifier()); - this->forward_to_inputs({node.context(), output_socket}, value); - } - } - } - - void execute_node(const DNode node, GeoNodeExecParams params) - { - const bNode &bnode = params.node(); - - this->store_ui_hints(node, params); - - /* Use the geometry-node-execute callback if it exists. */ - if (bnode.typeinfo->geometry_node_execute != nullptr) { - bnode.typeinfo->geometry_node_execute(params); - return; - } - - /* Use the multi-function implementation if it exists. */ - const MultiFunction *multi_function = mf_by_node_.lookup_default(node, nullptr); - if (multi_function != nullptr) { - this->execute_multi_function_node(node, params, *multi_function); - return; - } - - /* Just output default values if no implementation exists. */ - this->execute_unknown_node(node, params); - } - - void store_ui_hints(const DNode node, GeoNodeExecParams params) const - { - for (const InputSocketRef *socket_ref : node->inputs()) { - if (!socket_ref->is_available()) { - continue; - } - if (socket_ref->bsocket()->type != SOCK_GEOMETRY) { - continue; - } - if (socket_ref->is_multi_input_socket()) { - /* Not needed currently. */ - continue; - } - - bNodeTree *btree_cow = node->btree(); - bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow); - const NodeTreeEvaluationContext context(*self_object_, *modifier_); - - const GeometrySet &geometry_set = params.get_input<GeometrySet>(socket_ref->identifier()); - const Vector<const GeometryComponent *> components = geometry_set.get_components_for_read(); - - for (const GeometryComponent *component : components) { - component->attribute_foreach( - [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) { - BKE_nodetree_attribute_hint_add(*btree_original, - context, - *node->bnode(), - attribute_name, - meta_data.domain, - meta_data.data_type); - return true; - }); - } - } - } - - void execute_multi_function_node(const DNode node, - GeoNodeExecParams params, - const MultiFunction &fn) - { - MFContextBuilder fn_context; - MFParamsBuilder fn_params{fn, 1}; - Vector<GMutablePointer> input_data; - for (const InputSocketRef *socket_ref : node->inputs()) { - if (socket_ref->is_available()) { - GMutablePointer data = params.extract_input(socket_ref->identifier()); - fn_params.add_readonly_single_input(GSpan(*data.type(), data.get(), 1)); - input_data.append(data); - } - } - Vector<GMutablePointer> output_data; - for (const OutputSocketRef *socket_ref : node->outputs()) { - if (socket_ref->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket_ref->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - fn_params.add_uninitialized_single_output(GMutableSpan(type, buffer, 1)); - output_data.append(GMutablePointer(type, buffer)); - } - } - fn.call(IndexRange(1), fn_params, fn_context); - for (GMutablePointer value : input_data) { - value.destruct(); - } - int output_index = 0; - for (const int i : node->outputs().index_range()) { - if (node->output(i).is_available()) { - GMutablePointer value = output_data[output_index]; - params.set_output_by_move(node->output(i).identifier(), value); - value.destruct(); - output_index++; - } - } - } - - void execute_unknown_node(const DNode node, GeoNodeExecParams params) - { - for (const OutputSocketRef *socket : node->outputs()) { - if (socket->is_available()) { - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - params.set_output_by_copy(socket->identifier(), {type, type.default_value()}); - } - } - } - - void forward_to_inputs(const DOutputSocket from_socket, GMutablePointer value_to_forward) - { - /* For all sockets that are linked with the from_socket push the value to their node. */ - Vector<DInputSocket> to_sockets_all; - from_socket.foreach_target_socket( - [&](DInputSocket to_socket) { to_sockets_all.append_non_duplicates(to_socket); }); - - const CPPType &from_type = *value_to_forward.type(); - Vector<DInputSocket> to_sockets_same_type; - for (const DInputSocket &to_socket : to_sockets_all) { - const CPPType &to_type = *blender::nodes::socket_cpp_type_get(*to_socket->typeinfo()); - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - if (from_type == to_type) { - to_sockets_same_type.append(to_socket); - } - else { - void *buffer = allocator_.allocate(to_type.size(), to_type.alignment()); - if (conversions_.is_convertible(from_type, to_type)) { - conversions_.convert(from_type, to_type, value_to_forward.get(), buffer); - } - else { - to_type.copy_to_uninitialized(to_type.default_value(), buffer); - } - add_value_to_input_socket(key, GMutablePointer{to_type, buffer}); - } - } - - if (to_sockets_same_type.size() == 0) { - /* This value is not further used, so destruct it. */ - value_to_forward.destruct(); - } - else if (to_sockets_same_type.size() == 1) { - /* This value is only used on one input socket, no need to copy it. */ - const DInputSocket to_socket = to_sockets_same_type[0]; - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - - add_value_to_input_socket(key, value_to_forward); - } - else { - /* Multiple inputs use the value, make a copy for every input except for one. */ - const DInputSocket first_to_socket = to_sockets_same_type[0]; - Span<DInputSocket> other_to_sockets = to_sockets_same_type.as_span().drop_front(1); - const CPPType &type = *value_to_forward.type(); - const std::pair<DInputSocket, DOutputSocket> first_key = std::make_pair(first_to_socket, - from_socket); - add_value_to_input_socket(first_key, value_to_forward); - for (const DInputSocket &to_socket : other_to_sockets) { - const std::pair<DInputSocket, DOutputSocket> key = std::make_pair(to_socket, from_socket); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - type.copy_to_uninitialized(value_to_forward.get(), buffer); - add_value_to_input_socket(key, GMutablePointer{type, buffer}); - } - } - } - - void add_value_to_input_socket(const std::pair<DInputSocket, DOutputSocket> key, - GMutablePointer value) - { - value_by_input_.add_new(key, value); +static bool logging_enabled(const ModifierEvalContext *ctx) +{ + if (!DEG_is_active(ctx->depsgraph)) { + return false; } - - GMutablePointer get_unlinked_input_value(const DInputSocket &socket, - const CPPType &required_type) - { - bNodeSocket *bsocket = socket->bsocket(); - const CPPType &type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); - void *buffer = allocator_.allocate(type.size(), type.alignment()); - - if (bsocket->type == SOCK_OBJECT) { - Object *object = socket->default_value<bNodeSocketValueObject>()->value; - PersistentObjectHandle object_handle = handle_map_.lookup(object); - new (buffer) PersistentObjectHandle(object_handle); - } - else if (bsocket->type == SOCK_COLLECTION) { - Collection *collection = socket->default_value<bNodeSocketValueCollection>()->value; - PersistentCollectionHandle collection_handle = handle_map_.lookup(collection); - new (buffer) PersistentCollectionHandle(collection_handle); - } - else { - blender::nodes::socket_cpp_value_get(*bsocket, buffer); - } - - if (type == required_type) { - return {type, buffer}; - } - if (conversions_.is_convertible(type, required_type)) { - void *converted_buffer = allocator_.allocate(required_type.size(), - required_type.alignment()); - conversions_.convert(type, required_type, buffer, converted_buffer); - type.destruct(buffer); - return {required_type, converted_buffer}; - } - void *default_buffer = allocator_.allocate(required_type.size(), required_type.alignment()); - required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); - return {required_type, default_buffer}; + if ((ctx->flag & MOD_APPLY_ORCO) != 0) { + return false; } -}; + return true; +} /** * This code is responsible for creating the new property and also creating the group of @@ -634,9 +304,7 @@ struct SocketPropertyType { IDProperty *(*create_default_ui_prop)(const bNodeSocket &socket, const char *name); PropertyType (*rna_subtype_get)(const bNodeSocket &socket); bool (*is_correct_type)(const IDProperty &property); - void (*init_cpp_value)(const IDProperty &property, - const PersistentDataHandleMap &handles, - void *r_value); + void (*init_cpp_value)(const IDProperty &property, void *r_value); }; static IDProperty *socket_add_property(IDProperty *settings_prop_group, @@ -659,6 +327,14 @@ static IDProperty *socket_add_property(IDProperty *settings_prop_group, IDP_AddToGroup(ui_container, prop_ui_group); } + /* Set property description (tooltip). */ + IDPropertyTemplate property_description_template; + property_description_template.string.str = socket.description; + property_description_template.string.len = BLI_strnlen(socket.description, MAX_NAME) + 1; + property_description_template.string.subtype = IDP_STRING_SUB_UTF8; + IDProperty *description = IDP_New(IDP_STRING, &property_description_template, "description"); + IDP_AddToGroup(prop_ui_group, description); + /* Create the properties for the socket's UI settings. */ if (property_type.create_min_ui_prop != nullptr) { IDP_AddToGroup(prop_ui_group, property_type.create_min_ui_prop(socket, "min")); @@ -720,10 +396,15 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso [](const bNodeSocket &socket) { return (PropertyType)((bNodeSocketValueFloat *)socket.default_value)->subtype; }, - [](const IDProperty &property) { return property.type == IDP_FLOAT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(float *)r_value = IDP_Float(&property); }, + [](const IDProperty &property) { return ELEM(property.type, IDP_FLOAT, IDP_DOUBLE); }, + [](const IDProperty &property, void *r_value) { + if (property.type == IDP_FLOAT) { + *(float *)r_value = IDP_Float(&property); + } + else if (property.type == IDP_DOUBLE) { + *(float *)r_value = (float)IDP_Double(&property); + } + }, }; return &float_type; } @@ -757,9 +438,7 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return (PropertyType)((bNodeSocketValueInt *)socket.default_value)->subtype; }, [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(int *)r_value = IDP_Int(&property); }, + [](const IDProperty &property, void *r_value) { *(int *)r_value = IDP_Int(&property); }, }; return &int_type; } @@ -802,9 +481,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso return property.type == IDP_ARRAY && property.subtype == IDP_FLOAT && property.len == 3; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); }, + [](const IDProperty &property, void *r_value) { + copy_v3_v3((float *)r_value, (const float *)IDP_Array(&property)); + }, }; return &vector_type; } @@ -834,9 +513,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso }, nullptr, [](const IDProperty &property) { return property.type == IDP_INT; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { *(bool *)r_value = IDP_Int(&property) != 0; }, + [](const IDProperty &property, void *r_value) { + *(bool *)r_value = IDP_Int(&property) != 0; + }, }; return &boolean_type; } @@ -856,9 +535,9 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso }, nullptr, [](const IDProperty &property) { return property.type == IDP_STRING; }, - [](const IDProperty &property, - const PersistentDataHandleMap &UNUSED(handles), - void *r_value) { new (r_value) std::string(IDP_String(&property)); }, + [](const IDProperty &property, void *r_value) { + new (r_value) std::string(IDP_String(&property)); + }, }; return &string_type; } @@ -875,10 +554,10 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso nullptr, nullptr, [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, const PersistentDataHandleMap &handles, void *r_value) { + [](const IDProperty &property, void *r_value) { ID *id = IDP_Id(&property); Object *object = (id && GS(id->name) == ID_OB) ? (Object *)id : nullptr; - new (r_value) PersistentObjectHandle(handles.lookup(object)); + *(Object **)r_value = object; }, }; return &object_type; @@ -896,10 +575,52 @@ static const SocketPropertyType *get_socket_property_type(const bNodeSocket &bso nullptr, nullptr, [](const IDProperty &property) { return property.type == IDP_ID; }, - [](const IDProperty &property, const PersistentDataHandleMap &handles, void *r_value) { + [](const IDProperty &property, void *r_value) { ID *id = IDP_Id(&property); Collection *collection = (id && GS(id->name) == ID_GR) ? (Collection *)id : nullptr; - new (r_value) PersistentCollectionHandle(handles.lookup(collection)); + *(Collection **)r_value = collection; + }, + }; + return &collection_type; + } + case SOCK_TEXTURE: { + static const SocketPropertyType collection_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueTexture *value = (bNodeSocketValueTexture *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, name); + }, + nullptr, + nullptr, + nullptr, + nullptr, + [](const IDProperty &property) { return property.type == IDP_ID; }, + [](const IDProperty &property, void *r_value) { + ID *id = IDP_Id(&property); + Tex *texture = (id && GS(id->name) == ID_TE) ? (Tex *)id : nullptr; + *(Tex **)r_value = texture; + }, + }; + return &collection_type; + } + case SOCK_MATERIAL: { + static const SocketPropertyType collection_type = { + [](const bNodeSocket &socket, const char *name) { + bNodeSocketValueMaterial *value = (bNodeSocketValueMaterial *)socket.default_value; + IDPropertyTemplate idprop = {0}; + idprop.id = (ID *)value->value; + return IDP_New(IDP_ID, &idprop, name); + }, + nullptr, + nullptr, + nullptr, + nullptr, + [](const IDProperty &property) { return property.type == IDP_ID; }, + [](const IDProperty &property, void *r_value) { + ID *id = IDP_Id(&property); + Material *material = (id && GS(id->name) == ID_MA) ? (Material *)id : nullptr; + *(Material **)r_value = material; }, }; return &collection_type; @@ -987,7 +708,6 @@ void MOD_nodes_init(Main *bmain, NodesModifierData *nmd) } static void initialize_group_input(NodesModifierData &nmd, - const PersistentDataHandleMap &handle_map, const bNodeSocket &socket, const CPPType &cpp_type, void *r_value) @@ -1011,22 +731,7 @@ static void initialize_group_input(NodesModifierData &nmd, blender::nodes::socket_cpp_value_get(socket, r_value); return; } - property_type->init_cpp_value(*property, handle_map, r_value); -} - -static void fill_data_handle_map(const NodesModifierSettings &settings, - const DerivedNodeTree &tree, - PersistentDataHandleMap &handle_map) -{ - Set<ID *> used_ids; - find_used_ids_from_settings(settings, used_ids); - find_used_ids_from_nodes(*tree.root_context().tree().btree(), used_ids); - - int current_handle = 0; - for (ID *id : used_ids) { - handle_map.add(current_handle, *id); - current_handle++; - } + property_type->init_cpp_value(*property, r_value); } static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> trees, @@ -1042,29 +747,190 @@ static void reset_tree_ui_storage(Span<const blender::nodes::NodeTreeRef *> tree } } +static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain) +{ + wmWindowManager *wm = (wmWindowManager *)bmain->wm.first; + if (wm == nullptr) { + return {}; + } + Vector<SpaceSpreadsheet *> spreadsheets; + LISTBASE_FOREACH (wmWindow *, window, &wm->windows) { + bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook); + LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) { + SpaceLink *sl = (SpaceLink *)area->spacedata.first; + if (sl->spacetype == SPACE_SPREADSHEET) { + spreadsheets.append((SpaceSpreadsheet *)sl); + } + } + } + return spreadsheets; +} + +using PreviewSocketMap = blender::MultiValueMap<DSocket, uint64_t>; + +static DSocket try_find_preview_socket_in_node(const DNode node) +{ + for (const SocketRef *socket : node->outputs()) { + if (socket->bsocket()->type == SOCK_GEOMETRY) { + return {node.context(), socket}; + } + } + for (const SocketRef *socket : node->inputs()) { + if (socket->bsocket()->type == SOCK_GEOMETRY && + (socket->bsocket()->flag & SOCK_MULTI_INPUT) == 0) { + return {node.context(), socket}; + } + } + return {}; +} + +static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadsheet, + NodesModifierData *nmd, + const ModifierEvalContext *ctx, + const DerivedNodeTree &tree) +{ + Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path; + if (context_path.size() < 3) { + return {}; + } + if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) { + return {}; + } + if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) { + return {}; + } + SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0]; + if (object_context->object != DEG_get_original_object(ctx->object)) { + return {}; + } + SpreadsheetContextModifier *modifier_context = (SpreadsheetContextModifier *)context_path[1]; + if (StringRef(modifier_context->modifier_name) != nmd->modifier.name) { + return {}; + } + for (SpreadsheetContext *context : context_path.as_span().drop_front(2)) { + if (context->type != SPREADSHEET_CONTEXT_NODE) { + return {}; + } + } + + Span<SpreadsheetContextNode *> nested_group_contexts = + context_path.as_span().drop_front(2).drop_back(1).cast<SpreadsheetContextNode *>(); + SpreadsheetContextNode *last_context = (SpreadsheetContextNode *)context_path.last(); + + const DTreeContext *context = &tree.root_context(); + for (SpreadsheetContextNode *node_context : nested_group_contexts) { + const NodeTreeRef &tree_ref = context->tree(); + const NodeRef *found_node = nullptr; + for (const NodeRef *node_ref : tree_ref.nodes()) { + if (node_ref->name() == node_context->node_name) { + found_node = node_ref; + break; + } + } + if (found_node == nullptr) { + return {}; + } + context = context->child_context(*found_node); + if (context == nullptr) { + return {}; + } + } + + const NodeTreeRef &tree_ref = context->tree(); + for (const NodeRef *node_ref : tree_ref.nodes()) { + if (node_ref->name() == last_context->node_name) { + return try_find_preview_socket_in_node({context, node_ref}); + } + } + return {}; +} + +static void find_sockets_to_preview(NodesModifierData *nmd, + const ModifierEvalContext *ctx, + const DerivedNodeTree &tree, + PreviewSocketMap &r_sockets_to_preview) +{ + Main *bmain = DEG_get_bmain(ctx->depsgraph); + + /* Based on every visible spreadsheet context path, get a list of sockets that need to have their + * intermediate geometries cached for display. */ + Vector<SpaceSpreadsheet *> spreadsheets = find_spreadsheet_editors(bmain); + for (SpaceSpreadsheet *sspreadsheet : spreadsheets) { + const DSocket socket = try_get_socket_to_preview_for_spreadsheet(sspreadsheet, nmd, ctx, tree); + if (socket) { + const uint64_t key = ED_spreadsheet_context_path_hash(sspreadsheet); + r_sockets_to_preview.add_non_duplicates(socket, key); + } + } +} + +static void log_preview_socket_value(const Span<GPointer> values, + Object *object, + Span<uint64_t> keys) +{ + GeometrySet geometry_set = *(const GeometrySet *)values[0].get(); + geometry_set.ensure_owns_direct_data(); + for (uint64_t key : keys) { + BKE_object_preview_geometry_set_add(object, key, new GeometrySet(geometry_set)); + } +} + +static void log_ui_hints(const DSocket socket, + const Span<GPointer> values, + Object *self_object, + NodesModifierData *nmd) +{ + const DNode node = socket.node(); + if (node->is_reroute_node() || socket->typeinfo()->type != SOCK_GEOMETRY) { + return; + } + bNodeTree *btree_cow = node->btree(); + bNodeTree *btree_original = (bNodeTree *)DEG_get_original_id((ID *)btree_cow); + const NodeTreeEvaluationContext context{*self_object, nmd->modifier}; + for (const GPointer &data : values) { + if (data.type() == &CPPType::get<GeometrySet>()) { + const GeometrySet &geometry_set = *(const GeometrySet *)data.get(); + blender::bke::geometry_set_instances_attribute_foreach( + geometry_set, + [&](StringRefNull attribute_name, const AttributeMetaData &meta_data) { + BKE_nodetree_attribute_hint_add(*btree_original, + context, + *node->bnode(), + attribute_name, + meta_data.domain, + meta_data.data_type); + return true; + }, + 8); + } + } +} + /** * Evaluate a node group to compute the output geometry. * Currently, this uses a fairly basic and inefficient algorithm that might compute things more * often than necessary. It's going to be replaced soon. */ static GeometrySet compute_geometry(const DerivedNodeTree &tree, - Span<const OutputSocketRef *> group_input_sockets, + Span<const NodeRef *> group_input_nodes, const InputSocketRef &socket_to_compute, GeometrySet input_geometry_set, NodesModifierData *nmd, const ModifierEvalContext *ctx) { - blender::ResourceCollector resources; - blender::LinearAllocator<> &allocator = resources.linear_allocator(); - blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, resources); - - PersistentDataHandleMap handle_map; - fill_data_handle_map(nmd->settings, tree, handle_map); + blender::ResourceScope scope; + blender::LinearAllocator<> &allocator = scope.linear_allocator(); + blender::nodes::MultiFunctionByNode mf_by_node = get_multi_function_per_node(tree, scope); Map<DOutputSocket, GMutablePointer> group_inputs; const DTreeContext *root_context = &tree.root_context(); - if (group_input_sockets.size() > 0) { + for (const NodeRef *group_input_node : group_input_nodes) { + Span<const OutputSocketRef *> group_input_sockets = group_input_node->outputs().drop_back(1); + if (group_input_sockets.is_empty()) { + continue; + } + Span<const OutputSocketRef *> remaining_input_sockets = group_input_sockets; /* If the group expects a geometry as first input, use the geometry that has been passed to @@ -1072,7 +938,7 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, const OutputSocketRef *first_input_socket = group_input_sockets[0]; if (first_input_socket->bsocket()->type == SOCK_GEOMETRY) { GeometrySet *geometry_set_in = - allocator.construct<GeometrySet>(std::move(input_geometry_set)).release(); + allocator.construct<GeometrySet>(input_geometry_set).release(); group_inputs.add_new({root_context, first_input_socket}, geometry_set_in); remaining_input_sockets = remaining_input_sockets.drop_front(1); } @@ -1081,28 +947,44 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree, for (const OutputSocketRef *socket : remaining_input_sockets) { const CPPType &cpp_type = *blender::nodes::socket_cpp_type_get(*socket->typeinfo()); void *value_in = allocator.allocate(cpp_type.size(), cpp_type.alignment()); - initialize_group_input(*nmd, handle_map, *socket->bsocket(), cpp_type, value_in); + initialize_group_input(*nmd, *socket->bsocket(), cpp_type, value_in); group_inputs.add_new({root_context, socket}, {cpp_type, value_in}); } } + /* Don't keep a reference to the input geometry components to avoid copies during evaluation. */ + input_geometry_set.clear(); + Vector<DInputSocket> group_outputs; group_outputs.append({root_context, &socket_to_compute}); - GeometryNodesEvaluator evaluator{group_inputs, - group_outputs, - mf_by_node, - handle_map, - ctx->object, - (ModifierData *)nmd, - ctx->depsgraph}; + PreviewSocketMap preview_sockets; + find_sockets_to_preview(nmd, ctx, tree, preview_sockets); - Vector<GMutablePointer> results = evaluator.execute(); - BLI_assert(results.size() == 1); - GMutablePointer result = results[0]; - - GeometrySet output_geometry = std::move(*(GeometrySet *)result.get()); - return output_geometry; + auto log_socket_value = [&](const DSocket socket, const Span<GPointer> values) { + if (!logging_enabled(ctx)) { + return; + } + Span<uint64_t> keys = preview_sockets.lookup(socket); + if (!keys.is_empty()) { + log_preview_socket_value(values, ctx->object, keys); + } + log_ui_hints(socket, values, ctx->object, nmd); + }; + + blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params; + eval_params.input_values = group_inputs; + eval_params.output_sockets = group_outputs; + eval_params.mf_by_node = &mf_by_node; + eval_params.modifier_ = nmd; + eval_params.depsgraph = ctx->depsgraph; + eval_params.self_object = ctx->object; + eval_params.log_socket_value_fn = log_socket_value; + blender::modifiers::geometry_nodes::evaluate_geometry_nodes(eval_params); + + BLI_assert(eval_params.r_output_values.size() == 1); + GMutablePointer result = eval_params.r_output_values[0]; + return result.relocate_out<GeometrySet>(); } /** @@ -1174,18 +1056,10 @@ static void modifyGeometry(ModifierData *md, Span<const NodeRef *> input_nodes = root_tree_ref.nodes_by_type("NodeGroupInput"); Span<const NodeRef *> output_nodes = root_tree_ref.nodes_by_type("NodeGroupOutput"); - if (input_nodes.size() > 1) { - return; - } if (output_nodes.size() != 1) { return; } - Span<const OutputSocketRef *> group_inputs; - if (input_nodes.size() == 1) { - group_inputs = input_nodes[0]->outputs().drop_back(1); - } - Span<const InputSocketRef *> group_outputs = output_nodes[0]->inputs().drop_back(1); if (group_outputs.size() == 0) { @@ -1197,10 +1071,12 @@ static void modifyGeometry(ModifierData *md, return; } - reset_tree_ui_storage(tree.used_node_tree_refs(), *ctx->object, *md); + if (logging_enabled(ctx)) { + reset_tree_ui_storage(tree.used_node_tree_refs(), *ctx->object, *md); + } geometry_set = compute_geometry( - tree, group_inputs, *group_outputs[0], std::move(geometry_set), nmd, ctx); + tree, input_nodes, *group_outputs[0], std::move(geometry_set), nmd, ctx); } static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) @@ -1209,6 +1085,11 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * geometry_set.get_component_for_write<MeshComponent>().copy_vertex_group_names_from_object( *ctx->object); modifyGeometry(md, ctx, geometry_set); + + /* This function is only called when applying modifiers. In this case it makes sense to realize + * instances, otherwise in some cases there might be no results when applying the modifier. */ + geometry_set = blender::bke::geometry_set_realize_mesh_for_modifier(geometry_set); + Mesh *new_mesh = geometry_set.get_component_for_write<MeshComponent>().release(); if (new_mesh == nullptr) { return BKE_mesh_new_nomain(0, 0, 0, 0, 0); @@ -1242,36 +1123,45 @@ static void draw_property_for_socket(uiLayout *layout, /* IDProperties can be removed with python, so there could be a situation where * there isn't a property for a socket or it doesn't have the correct type. */ - if (property != nullptr && property_type->is_correct_type(*property)) { + if (property == nullptr || !property_type->is_correct_type(*property)) { + return; + } - char socket_id_esc[sizeof(socket.identifier) * 2]; - BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); + char socket_id_esc[sizeof(socket.identifier) * 2]; + BLI_str_escape(socket_id_esc, socket.identifier, sizeof(socket_id_esc)); - char rna_path[sizeof(socket_id_esc) + 4]; - BLI_snprintf(rna_path, ARRAY_SIZE(rna_path), "[\"%s\"]", socket_id_esc); + char rna_path[sizeof(socket_id_esc) + 4]; + BLI_snprintf(rna_path, ARRAY_SIZE(rna_path), "[\"%s\"]", socket_id_esc); - /* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough - * information about what type of ID to select for editing the values. This is because - * pointer IDProperties contain no information about their type. */ - switch (socket.type) { - case SOCK_OBJECT: { - uiItemPointerR( - layout, md_ptr, rna_path, bmain_ptr, "objects", socket.name, ICON_OBJECT_DATA); - break; - } - case SOCK_COLLECTION: { - uiItemPointerR(layout, - md_ptr, - rna_path, - bmain_ptr, - "collections", - socket.name, - ICON_OUTLINER_COLLECTION); - break; - } - default: - uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); + /* Use #uiItemPointerR to draw pointer properties because #uiItemR would not have enough + * information about what type of ID to select for editing the values. This is because + * pointer IDProperties contain no information about their type. */ + switch (socket.type) { + case SOCK_OBJECT: { + uiItemPointerR( + layout, md_ptr, rna_path, bmain_ptr, "objects", socket.name, ICON_OBJECT_DATA); + break; + } + case SOCK_COLLECTION: { + uiItemPointerR(layout, + md_ptr, + rna_path, + bmain_ptr, + "collections", + socket.name, + ICON_OUTLINER_COLLECTION); + break; + } + case SOCK_MATERIAL: { + uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "materials", socket.name, ICON_MATERIAL); + break; + } + case SOCK_TEXTURE: { + uiItemPointerR(layout, md_ptr, rna_path, bmain_ptr, "textures", socket.name, ICON_TEXTURE); + break; } + default: + uiItemR(layout, md_ptr, rna_path, 0, socket.name, ICON_NONE); } } @@ -1359,6 +1249,7 @@ static void requiredDataMask(Object *UNUSED(ob), /* We don't know what the node tree will need. If there are vertex groups, it is likely that the * node tree wants to access them. */ r_cddata_masks->vmask |= CD_MASK_MDEFORMVERT; + r_cddata_masks->vmask |= CD_MASK_PROP_ALL; } ModifierTypeInfo modifierType_Nodes = { @@ -1382,7 +1273,6 @@ ModifierTypeInfo modifierType_Nodes = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ modifyGeometrySet, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.cc b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc new file mode 100644 index 00000000000..170ad0f2c48 --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.cc @@ -0,0 +1,1553 @@ +/* + * 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. + */ + +#include "MOD_nodes_evaluator.hh" + +#include "NOD_geometry_exec.hh" +#include "NOD_type_conversions.hh" + +#include "DEG_depsgraph_query.h" + +#include "FN_generic_value_map.hh" +#include "FN_multi_function.hh" + +#include "BLI_enumerable_thread_specific.hh" +#include "BLI_stack.hh" +#include "BLI_task.h" +#include "BLI_task.hh" +#include "BLI_vector_set.hh" + +namespace blender::modifiers::geometry_nodes { + +using fn::CPPType; +using fn::GValueMap; +using nodes::GeoNodeExecParams; +using namespace fn::multi_function_types; + +enum class ValueUsage : uint8_t { + /* The value is definitely used. */ + Required, + /* The value may be used. */ + Maybe, + /* The value will definitely not be used. */ + Unused, +}; + +struct SingleInputValue { + /** + * Points either to null or to a value of the type of input. + */ + void *value = nullptr; +}; + +struct MultiInputValueItem { + /** + * The socket where this value is coming from. This is required to sort the inputs correctly + * based on the link order later on. + */ + DSocket origin; + /** + * Should only be null directly after construction. After that it should always point to a value + * of the correct type. + */ + void *value = nullptr; +}; + +struct MultiInputValue { + /** + * Collection of all the inputs that have been provided already. Note, the same origin can occur + * multiple times. However, it is guaranteed that if two items have the same origin, they will + * also have the same value (the pointer is different, but they point to values that would + * compare equal). + */ + Vector<MultiInputValueItem> items; + /** + * Number of items that need to be added until all inputs have been provided. + */ + int expected_size = 0; +}; + +struct InputState { + + /** + * Type of the socket. If this is null, the socket should just be ignored. + */ + const CPPType *type = nullptr; + + /** + * Value of this input socket. By default, the value is empty. When other nodes are done + * computing their outputs, the computed values will be forwarded to linked input sockets. + * The value will then live here until it is consumed by the node or it was found that the value + * is not needed anymore. + * Whether the `single` or `multi` value is used depends on the socket. + */ + union { + SingleInputValue *single; + MultiInputValue *multi; + } value; + + /** + * How the node intends to use this input. By default all inputs may be used. Based on which + * outputs are used, a node can tell the evaluator that an input will definitely be used or is + * never used. This allows the evaluator to free values early, avoid copies and other unnecessary + * computations. + */ + ValueUsage usage = ValueUsage::Maybe; + + /** + * True when this input is/was used for an execution. While a node is running, only the inputs + * that have this set to true are allowed to be used. This makes sure that inputs created while + * the node is running correctly trigger the node to run again. Furthermore, it gives the node a + * consistent view of which inputs are available that does not change unexpectedly. + * + * While the node is running, this can be checked without a lock, because no one is writing to + * it. If this is true, the value can be read without a lock as well, because the value is not + * changed by others anymore. + */ + bool was_ready_for_execution = false; +}; + +struct OutputState { + /** + * If this output has been computed and forwarded already. If this is true, the value is not + * computed/forwarded again. + */ + bool has_been_computed = false; + + /** + * Keeps track of how the output value is used. If a connected input becomes required, this + * output has to become required as well. The output becomes ignored when it has zero potential + * users that are counted below. + */ + ValueUsage output_usage = ValueUsage::Maybe; + + /** + * This is a copy of `output_usage` that is done right before node execution starts. This is + * done so that the node gets a consistent view of what outputs are used, even when this changes + * while the node is running (the node might be reevaluated in that case). + * + * While the node is running, this can be checked without a lock, because no one is writing to + * it. + */ + ValueUsage output_usage_for_execution = ValueUsage::Maybe; + + /** + * Counts how many times the value from this output might be used. If this number reaches zero, + * the output is not needed anymore. + */ + int potential_users = 0; +}; + +enum class NodeScheduleState { + /** + * Default state of every node. + */ + NotScheduled, + /** + * The node has been added to the task group and will be executed by it in the future. + */ + Scheduled, + /** + * The node is currently running. + */ + Running, + /** + * The node is running and has been rescheduled while running. In this case the node will run + * again. However, we don't add it to the task group immediately, because then the node might run + * twice at the same time, which is not allowed. Instead, once the node is done running, it will + * reschedule itself. + */ + RunningAndRescheduled, +}; + +struct NodeState { + /** + * Needs to be locked when any data in this state is accessed that is not explicitly marked as + * otherwise. + */ + std::mutex mutex; + + /** + * States of the individual input and output sockets. One can index into these arrays without + * locking. However, to access the data inside a lock is generally necessary. + * + * These spans have to be indexed with the socket index. Unavailable sockets have a state as + * well. Maybe we can handle unavailable sockets differently in Blender in general, so I did not + * want to add complexity around it here. + */ + MutableSpan<InputState> inputs; + MutableSpan<OutputState> outputs; + + /** + * Nodes that don't support laziness have some special handling the first time they are executed. + */ + bool non_lazy_node_is_initialized = false; + + /** + * Used to check that nodes that don't support laziness do not run more than once. + */ + bool has_been_executed = false; + + /** + * Becomes true when the node will never be executed again and its inputs are destructed. + * Generally, a node has finished once all of its outputs with (potential) users have been + * computed. + */ + bool node_has_finished = false; + + /** + * Counts the number of values that still have to be forwarded to this node until it should run + * again. It counts values from a multi input socket separately. + * This is used as an optimization so that nodes are not scheduled unnecessarily in many cases. + */ + int missing_required_inputs = 0; + + /** + * A node is always in one specific schedule state. This helps to ensure that the same node does + * not run twice at the same time accidentally. + */ + NodeScheduleState schedule_state = NodeScheduleState::NotScheduled; +}; + +/** + * Container for a node and its state. Packing them into a single struct allows the use of + * `VectorSet` instead of a `Map` for `node_states_` which simplifies parallel loops over all + * states. + * + * Equality operators and a hash function for `DNode` are provided so that one can lookup this type + * in `node_states_` just with a `DNode`. + */ +struct NodeWithState { + DNode node; + /* Store a pointer instead of `NodeState` directly to keep it small and movable. */ + NodeState *state = nullptr; + + friend bool operator==(const NodeWithState &a, const NodeWithState &b) + { + return a.node == b.node; + } + + friend bool operator==(const NodeWithState &a, const DNode &b) + { + return a.node == b; + } + + friend bool operator==(const DNode &a, const NodeWithState &b) + { + return a == b.node; + } + + uint64_t hash() const + { + return node.hash(); + } + + static uint64_t hash_as(const DNode &node) + { + return node.hash(); + } +}; + +class GeometryNodesEvaluator; + +/** + * Utility class that locks the state of a node. Having this is a separate class is useful because + * it allows methods to communicate that they expect the node to be locked. + */ +class LockedNode : NonCopyable, NonMovable { + private: + GeometryNodesEvaluator &evaluator_; + + public: + /** + * This is the node that is currently locked. + */ + const DNode node; + NodeState &node_state; + + /** + * Used to delay notifying (and therefore locking) other nodes until the current node is not + * locked anymore. This might not be strictly necessary to avoid deadlocks in the current code, + * but it is a good measure to avoid accidentally adding a deadlock later on. By not locking + * more than one node per thread at a time, deadlocks are avoided. + * + * The notifications will be send right after the node is not locked anymore. + */ + Vector<DOutputSocket> delayed_required_outputs; + Vector<DOutputSocket> delayed_unused_outputs; + Vector<DNode> delayed_scheduled_nodes; + + LockedNode(GeometryNodesEvaluator &evaluator, const DNode node, NodeState &node_state) + : evaluator_(evaluator), node(node), node_state(node_state) + { + node_state.mutex.lock(); + } + + ~LockedNode(); +}; + +static const CPPType *get_socket_cpp_type(const DSocket socket) +{ + return nodes::socket_cpp_type_get(*socket->typeinfo()); +} + +static const CPPType *get_socket_cpp_type(const SocketRef &socket) +{ + return nodes::socket_cpp_type_get(*socket.typeinfo()); +} + +static bool node_supports_laziness(const DNode node) +{ + return node->typeinfo()->geometry_node_execute_supports_laziness; +} + +/** Implements the callbacks that might be called when a node is executed. */ +class NodeParamsProvider : public nodes::GeoNodeExecParamsProvider { + private: + GeometryNodesEvaluator &evaluator_; + NodeState &node_state_; + + public: + NodeParamsProvider(GeometryNodesEvaluator &evaluator, DNode dnode, NodeState &node_state); + + bool can_get_input(StringRef identifier) const override; + bool can_set_output(StringRef identifier) const override; + GMutablePointer extract_input(StringRef identifier) override; + Vector<GMutablePointer> extract_multi_input(StringRef identifier) override; + GPointer get_input(StringRef identifier) const override; + GMutablePointer alloc_output_value(const CPPType &type) override; + void set_output(StringRef identifier, GMutablePointer value) override; + void set_input_unused(StringRef identifier) override; + bool output_is_required(StringRef identifier) const override; + + bool lazy_require_input(StringRef identifier) override; + bool lazy_output_is_required(StringRef identifier) const override; +}; + +class GeometryNodesEvaluator { + private: + /** + * This allocator lives on after the evaluator has been destructed. Therefore outputs of the + * entire evaluator should be allocated here. + */ + LinearAllocator<> &outer_allocator_; + /** + * A local linear allocator for each thread. Only use this for values that do not need to live + * longer than the lifetime of the evaluator itself. Considerations for the future: + * - We could use an allocator that can free here, some temporary values don't live long. + * - If we ever run into false sharing bottlenecks, we could use local allocators that allocate + * on cache line boundaries. Note, just because a value is allocated in one specific thread, + * does not mean that it will only be used by that thread. + */ + EnumerableThreadSpecific<LinearAllocator<>> local_allocators_; + + /** + * Every node that is reachable from the output gets its own state. Once all states have been + * constructed, this map can be used for lookups from multiple threads. + */ + VectorSet<NodeWithState> node_states_; + + /** + * Contains all the tasks for the nodes that are currently scheduled. + */ + TaskPool *task_pool_ = nullptr; + + GeometryNodesEvaluationParams ¶ms_; + const blender::nodes::DataTypeConversions &conversions_; + + friend NodeParamsProvider; + + public: + GeometryNodesEvaluator(GeometryNodesEvaluationParams ¶ms) + : outer_allocator_(params.allocator), + params_(params), + conversions_(blender::nodes::get_implicit_type_conversions()) + { + } + + void execute() + { + task_pool_ = BLI_task_pool_create(this, TASK_PRIORITY_HIGH, TASK_ISOLATION_OFF); + + this->create_states_for_reachable_nodes(); + this->forward_group_inputs(); + this->schedule_initial_nodes(); + + /* This runs until all initially requested inputs have been computed. */ + BLI_task_pool_work_and_wait(task_pool_); + BLI_task_pool_free(task_pool_); + + this->extract_group_outputs(); + this->destruct_node_states(); + } + + void create_states_for_reachable_nodes() + { + /* This does a depth first search for all the nodes that are reachable from the group + * outputs. This finds all nodes that are relevant. */ + Stack<DNode> nodes_to_check; + /* Start at the output sockets. */ + for (const DInputSocket &socket : params_.output_sockets) { + nodes_to_check.push(socket.node()); + } + /* Use the local allocator because the states do not need to outlive the evaluator. */ + LinearAllocator<> &allocator = local_allocators_.local(); + while (!nodes_to_check.is_empty()) { + const DNode node = nodes_to_check.pop(); + if (node_states_.contains_as(node)) { + /* This node has been handled already. */ + continue; + } + /* Create a new state for the node. */ + NodeState &node_state = *allocator.construct<NodeState>().release(); + node_states_.add_new({node, &node_state}); + + /* Push all linked origins on the stack. */ + for (const InputSocketRef *input_ref : node->inputs()) { + const DInputSocket input{node.context(), input_ref}; + input.foreach_origin_socket( + [&](const DSocket origin) { nodes_to_check.push(origin.node()); }); + } + } + + /* Initialize the more complex parts of the node states in parallel. At this point no new + * node states are added anymore, so it is safe to lookup states from `node_states_` from + * multiple threads. */ + parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) { + LinearAllocator<> &allocator = this->local_allocators_.local(); + for (const NodeWithState &item : node_states_.as_span().slice(range)) { + this->initialize_node_state(item.node, *item.state, allocator); + } + }); + } + + void initialize_node_state(const DNode node, NodeState &node_state, LinearAllocator<> &allocator) + { + /* Construct arrays of the correct size. */ + node_state.inputs = allocator.construct_array<InputState>(node->inputs().size()); + node_state.outputs = allocator.construct_array<OutputState>(node->outputs().size()); + + /* Initialize input states. */ + for (const int i : node->inputs().index_range()) { + InputState &input_state = node_state.inputs[i]; + const DInputSocket socket = node.input(i); + if (!socket->is_available()) { + /* Unavailable sockets should never be used. */ + input_state.type = nullptr; + input_state.usage = ValueUsage::Unused; + continue; + } + const CPPType *type = get_socket_cpp_type(socket); + input_state.type = type; + if (type == nullptr) { + /* This is not a known data socket, it shouldn't be used. */ + input_state.usage = ValueUsage::Unused; + continue; + } + /* Construct the correct struct that can hold the input(s). */ + if (socket->is_multi_input_socket()) { + input_state.value.multi = allocator.construct<MultiInputValue>().release(); + /* Count how many values should be added until the socket is complete. */ + socket.foreach_origin_socket( + [&](DSocket UNUSED(origin)) { input_state.value.multi->expected_size++; }); + /* If no links are connected, we do read the value from socket itself. */ + if (input_state.value.multi->expected_size == 0) { + input_state.value.multi->expected_size = 1; + } + } + else { + input_state.value.single = allocator.construct<SingleInputValue>().release(); + } + } + /* Initialize output states. */ + for (const int i : node->outputs().index_range()) { + OutputState &output_state = node_state.outputs[i]; + const DOutputSocket socket = node.output(i); + if (!socket->is_available()) { + /* Unavailable outputs should never be used. */ + output_state.output_usage = ValueUsage::Unused; + continue; + } + const CPPType *type = get_socket_cpp_type(socket); + if (type == nullptr) { + /* Non data sockets should never be used. */ + output_state.output_usage = ValueUsage::Unused; + continue; + } + /* Count the number of potential users for this socket. */ + socket.foreach_target_socket( + [&, this](const DInputSocket target_socket) { + const DNode target_node = target_socket.node(); + if (!this->node_states_.contains_as(target_node)) { + /* The target node is not computed because it is not computed to the output. */ + return; + } + output_state.potential_users += 1; + }, + {}); + if (output_state.potential_users == 0) { + /* If it does not have any potential users, it is unused. */ + output_state.output_usage = ValueUsage::Unused; + } + } + } + + void destruct_node_states() + { + parallel_for(IndexRange(node_states_.size()), 50, [&, this](const IndexRange range) { + for (const NodeWithState &item : node_states_.as_span().slice(range)) { + this->destruct_node_state(item.node, *item.state); + } + }); + } + + void destruct_node_state(const DNode node, NodeState &node_state) + { + /* Need to destruct stuff manually, because it's allocated by a custom allocator. */ + for (const int i : node->inputs().index_range()) { + InputState &input_state = node_state.inputs[i]; + if (input_state.type == nullptr) { + continue; + } + const InputSocketRef &socket_ref = node->input(i); + if (socket_ref.is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + for (MultiInputValueItem &item : multi_value.items) { + input_state.type->destruct(item.value); + } + multi_value.~MultiInputValue(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + if (value != nullptr) { + input_state.type->destruct(value); + } + single_value.~SingleInputValue(); + } + } + + destruct_n(node_state.inputs.data(), node_state.inputs.size()); + destruct_n(node_state.outputs.data(), node_state.outputs.size()); + + node_state.~NodeState(); + } + + void forward_group_inputs() + { + for (auto &&item : params_.input_values.items()) { + const DOutputSocket socket = item.key; + GMutablePointer value = item.value; + this->log_socket_value(socket, value); + + const DNode node = socket.node(); + if (!node_states_.contains_as(node)) { + /* The socket is not connected to any output. */ + value.destruct(); + continue; + } + this->forward_output(socket, value); + } + } + + void schedule_initial_nodes() + { + for (const DInputSocket &socket : params_.output_sockets) { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + LockedNode locked_node{*this, node, node_state}; + /* Setting an input as required will schedule any linked node. */ + this->set_input_required(locked_node, socket); + } + } + + void schedule_node(LockedNode &locked_node) + { + switch (locked_node.node_state.schedule_state) { + case NodeScheduleState::NotScheduled: { + /* The node will be scheduled once it is not locked anymore. We could schedule the node + * right here, but that would result in a deadlock if the task pool decides to run the task + * immediately (this only happens when Blender is started with a single thread). */ + locked_node.node_state.schedule_state = NodeScheduleState::Scheduled; + locked_node.delayed_scheduled_nodes.append(locked_node.node); + break; + } + case NodeScheduleState::Scheduled: { + /* Scheduled already, nothing to do. */ + break; + } + case NodeScheduleState::Running: { + /* Reschedule node while it is running. + * The node will reschedule itself when it is done. */ + locked_node.node_state.schedule_state = NodeScheduleState::RunningAndRescheduled; + break; + } + case NodeScheduleState::RunningAndRescheduled: { + /* Scheduled already, nothing to do. */ + break; + } + } + } + + static void run_node_from_task_pool(TaskPool *task_pool, void *task_data) + { + void *user_data = BLI_task_pool_user_data(task_pool); + GeometryNodesEvaluator &evaluator = *(GeometryNodesEvaluator *)user_data; + const NodeWithState *node_with_state = (const NodeWithState *)task_data; + + evaluator.node_task_run(node_with_state->node, *node_with_state->state); + } + + void node_task_run(const DNode node, NodeState &node_state) + { + /* These nodes are sometimes scheduled. We could also check for them in other places, but + * it's the easiest to do it here. */ + if (node->is_group_input_node() || node->is_group_output_node()) { + return; + } + + const bool do_execute_node = this->node_task_preprocessing(node, node_state); + + /* Only execute the node if all prerequisites are met. There has to be an output that is + * required and all required inputs have to be provided already. */ + if (do_execute_node) { + this->execute_node(node, node_state); + } + + this->node_task_postprocessing(node, node_state); + } + + bool node_task_preprocessing(const DNode node, NodeState &node_state) + { + LockedNode locked_node{*this, node, node_state}; + BLI_assert(node_state.schedule_state == NodeScheduleState::Scheduled); + node_state.schedule_state = NodeScheduleState::Running; + + /* Early return if the node has finished already. */ + if (locked_node.node_state.node_has_finished) { + return false; + } + /* Prepare outputs and check if actually any new outputs have to be computed. */ + if (!this->prepare_node_outputs_for_execution(locked_node)) { + return false; + } + /* Initialize nodes that don't support laziness. This is done after at least one output is + * required and before we check that all required inputs are provided. This reduces the + * number of "round-trips" through the task pool by one for most nodes. */ + if (!node_state.non_lazy_node_is_initialized && !node_supports_laziness(node)) { + this->initialize_non_lazy_node(locked_node); + node_state.non_lazy_node_is_initialized = true; + } + /* Prepare inputs and check if all required inputs are provided. */ + if (!this->prepare_node_inputs_for_execution(locked_node)) { + return false; + } + return true; + } + + /* A node is finished when it has computed all outputs that may be used. */ + bool finish_node_if_possible(LockedNode &locked_node) + { + if (locked_node.node_state.node_has_finished) { + /* Early return in case this node is known to have finished already. */ + return true; + } + + /* Check if there is any output that might be used but has not been computed yet. */ + bool has_remaining_output = false; + for (OutputState &output_state : locked_node.node_state.outputs) { + if (output_state.has_been_computed) { + continue; + } + if (output_state.output_usage != ValueUsage::Unused) { + has_remaining_output = true; + break; + } + } + if (!has_remaining_output) { + /* If there are no remaining outputs, all the inputs can be destructed and/or can become + * unused. This can also trigger a chain reaction where nodes to the left become finished + * too. */ + for (const int i : locked_node.node->inputs().index_range()) { + const DInputSocket socket = locked_node.node.input(i); + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.usage == ValueUsage::Maybe) { + this->set_input_unused(locked_node, socket); + } + else if (input_state.usage == ValueUsage::Required) { + /* The value was required, so it cannot become unused. However, we can destruct the + * value. */ + this->destruct_input_value_if_exists(locked_node, socket); + } + } + locked_node.node_state.node_has_finished = true; + } + return locked_node.node_state.node_has_finished; + } + + bool prepare_node_outputs_for_execution(LockedNode &locked_node) + { + bool execution_is_necessary = false; + for (OutputState &output_state : locked_node.node_state.outputs) { + /* Update the output usage for execution to the latest value. */ + output_state.output_usage_for_execution = output_state.output_usage; + if (!output_state.has_been_computed) { + if (output_state.output_usage == ValueUsage::Required) { + /* Only evaluate when there is an output that is required but has not been computed. */ + execution_is_necessary = true; + } + } + } + return execution_is_necessary; + } + + void initialize_non_lazy_node(LockedNode &locked_node) + { + for (const int i : locked_node.node->inputs().index_range()) { + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.type == nullptr) { + /* Ignore unavailable/non-data sockets. */ + continue; + } + /* Nodes that don't support laziness require all inputs. */ + const DInputSocket input_socket = locked_node.node.input(i); + this->set_input_required(locked_node, input_socket); + } + } + + /** + * Checks if requested inputs are available and "marks" all the inputs that are available + * during the node execution. Inputs that are provided after this function ends but before the + * node is executed, cannot be read by the node in the execution (note that this only affects + * nodes that support lazy inputs). + */ + bool prepare_node_inputs_for_execution(LockedNode &locked_node) + { + for (const int i : locked_node.node_state.inputs.index_range()) { + InputState &input_state = locked_node.node_state.inputs[i]; + if (input_state.type == nullptr) { + /* Ignore unavailable and non-data sockets. */ + continue; + } + const DInputSocket socket = locked_node.node.input(i); + const bool is_required = input_state.usage == ValueUsage::Required; + + /* No need to check this socket again. */ + if (input_state.was_ready_for_execution) { + continue; + } + + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + /* Checks if all the linked sockets have been provided already. */ + if (multi_value.items.size() == multi_value.expected_size) { + input_state.was_ready_for_execution = true; + this->log_socket_value(socket, input_state, multi_value.items); + } + else if (is_required) { + /* The input is required but is not fully provided yet. Therefore the node cannot be + * executed yet. */ + return false; + } + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value != nullptr) { + input_state.was_ready_for_execution = true; + this->log_socket_value(socket, GPointer{input_state.type, single_value.value}); + } + else if (is_required) { + /* The input is required but has not been provided yet. Therefore the node cannot be + * executed yet. */ + return false; + } + } + } + /* All required inputs have been provided. */ + return true; + } + + /** + * Actually execute the node. All the required inputs are available and at least one output is + * required. + */ + void execute_node(const DNode node, NodeState &node_state) + { + const bNode &bnode = *node->bnode(); + + if (node_state.has_been_executed) { + if (!node_supports_laziness(node)) { + /* Nodes that don't support laziness must not be executed more than once. */ + BLI_assert_unreachable(); + } + } + node_state.has_been_executed = true; + + /* Use the geometry node execute callback if it exists. */ + if (bnode.typeinfo->geometry_node_execute != nullptr) { + this->execute_geometry_node(node, node_state); + return; + } + + /* Use the multi-function implementation if it exists. */ + const MultiFunction *multi_function = params_.mf_by_node->lookup_default(node, nullptr); + if (multi_function != nullptr) { + this->execute_multi_function_node(node, *multi_function, node_state); + return; + } + + this->execute_unknown_node(node, node_state); + } + + void execute_geometry_node(const DNode node, NodeState &node_state) + { + const bNode &bnode = *node->bnode(); + + NodeParamsProvider params_provider{*this, node, node_state}; + GeoNodeExecParams params{params_provider}; + bnode.typeinfo->geometry_node_execute(params); + } + + void execute_multi_function_node(const DNode node, + const MultiFunction &fn, + NodeState &node_state) + { + MFContextBuilder fn_context; + MFParamsBuilder fn_params{fn, 1}; + LinearAllocator<> &allocator = local_allocators_.local(); + + /* Prepare the inputs for the multi function. */ + for (const int i : node->inputs().index_range()) { + const InputSocketRef &socket_ref = node->input(i); + if (!socket_ref.is_available()) { + continue; + } + BLI_assert(!socket_ref.is_multi_input_socket()); + InputState &input_state = node_state.inputs[i]; + BLI_assert(input_state.was_ready_for_execution); + SingleInputValue &single_value = *input_state.value.single; + BLI_assert(single_value.value != nullptr); + fn_params.add_readonly_single_input(GPointer{*input_state.type, single_value.value}); + } + /* Prepare the outputs for the multi function. */ + Vector<GMutablePointer> outputs; + for (const int i : node->outputs().index_range()) { + const OutputSocketRef &socket_ref = node->output(i); + if (!socket_ref.is_available()) { + continue; + } + const CPPType &type = *get_socket_cpp_type(socket_ref); + void *buffer = allocator.allocate(type.size(), type.alignment()); + fn_params.add_uninitialized_single_output(GMutableSpan{type, buffer, 1}); + outputs.append({type, buffer}); + } + + fn.call(IndexRange(1), fn_params, fn_context); + + /* Forward the computed outputs. */ + int output_index = 0; + for (const int i : node->outputs().index_range()) { + const OutputSocketRef &socket_ref = node->output(i); + if (!socket_ref.is_available()) { + continue; + } + OutputState &output_state = node_state.outputs[i]; + const DOutputSocket socket{node.context(), &socket_ref}; + GMutablePointer value = outputs[output_index]; + this->forward_output(socket, value); + output_state.has_been_computed = true; + output_index++; + } + } + + void execute_unknown_node(const DNode node, NodeState &node_state) + { + LinearAllocator<> &allocator = local_allocators_.local(); + for (const OutputSocketRef *socket : node->outputs()) { + if (!socket->is_available()) { + continue; + } + const CPPType *type = get_socket_cpp_type(*socket); + if (type == nullptr) { + continue; + } + /* Just forward the default value of the type as a fallback. That's typically better than + * crashing or doing nothing. */ + OutputState &output_state = node_state.outputs[socket->index()]; + output_state.has_been_computed = true; + void *buffer = allocator.allocate(type->size(), type->alignment()); + type->copy_to_uninitialized(type->default_value(), buffer); + this->forward_output({node.context(), socket}, {*type, buffer}); + } + } + + void node_task_postprocessing(const DNode node, NodeState &node_state) + { + LockedNode locked_node{*this, node, node_state}; + + const bool node_has_finished = this->finish_node_if_possible(locked_node); + const bool reschedule_requested = node_state.schedule_state == + NodeScheduleState::RunningAndRescheduled; + node_state.schedule_state = NodeScheduleState::NotScheduled; + if (reschedule_requested && !node_has_finished) { + /* Either the node rescheduled itself or another node tried to schedule it while it ran. */ + this->schedule_node(locked_node); + } + + this->assert_expected_outputs_have_been_computed(locked_node); + } + + void assert_expected_outputs_have_been_computed(LockedNode &locked_node) + { +#ifdef DEBUG + /* Outputs can only be computed when all required inputs have been provided. */ + if (locked_node.node_state.missing_required_inputs > 0) { + return; + } + /* If the node is still scheduled, it is not necessary that all its expected outputs are + * computed yet. */ + if (locked_node.node_state.schedule_state == NodeScheduleState::Scheduled) { + return; + } + + const bool supports_laziness = node_supports_laziness(locked_node.node); + /* Iterating over sockets instead of the states directly, because that makes it easier to + * figure out which socket is missing when one of the asserts is hit. */ + for (const OutputSocketRef *socket_ref : locked_node.node->outputs()) { + OutputState &output_state = locked_node.node_state.outputs[socket_ref->index()]; + if (supports_laziness) { + /* Expected that at least all required sockets have been computed. If more outputs become + * required later, the node will be executed again. */ + if (output_state.output_usage_for_execution == ValueUsage::Required) { + BLI_assert(output_state.has_been_computed); + } + } + else { + /* Expect that all outputs that may be used have been computed, because the node cannot + * be executed again. */ + if (output_state.output_usage_for_execution != ValueUsage::Unused) { + BLI_assert(output_state.has_been_computed); + } + } + } +#else + UNUSED_VARS(locked_node); +#endif + } + + void extract_group_outputs() + { + for (const DInputSocket &socket : params_.output_sockets) { + BLI_assert(socket->is_available()); + BLI_assert(!socket->is_multi_input_socket()); + + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + InputState &input_state = node_state.inputs[socket->index()]; + + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + + /* The value should have been computed by now. If this assert is hit, it means that there + * was some scheduling issue before. */ + BLI_assert(value != nullptr); + + /* Move value into memory owned by the outer allocator. */ + const CPPType &type = *input_state.type; + void *buffer = outer_allocator_.allocate(type.size(), type.alignment()); + type.move_to_uninitialized(value, buffer); + + params_.r_output_values.append({type, buffer}); + } + } + + /** + * Load the required input from the socket or trigger nodes to the left to compute the value. + * When this function is called, the node will always be executed again eventually (either + * immediately, or when all required inputs have been computed by other nodes). + */ + void set_input_required(LockedNode &locked_node, const DInputSocket input_socket) + { + BLI_assert(locked_node.node == input_socket.node()); + InputState &input_state = locked_node.node_state.inputs[input_socket->index()]; + + /* Value set as unused cannot become used again. */ + BLI_assert(input_state.usage != ValueUsage::Unused); + + if (input_state.usage == ValueUsage::Required) { + /* The value is already required, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + /* Returning here also ensure that the code below is executed at most once per input. */ + return; + } + input_state.usage = ValueUsage::Required; + + if (input_state.was_ready_for_execution) { + /* The value was already ready, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + return; + } + + /* Count how many values still have to be added to this input until it is "complete". */ + int missing_values = 0; + if (input_socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + missing_values = multi_value.expected_size - multi_value.items.size(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value == nullptr) { + missing_values = 1; + } + } + if (missing_values == 0) { + /* The input is fully available already, but the node might expect to be evaluated again. */ + this->schedule_node(locked_node); + return; + } + /* Increase the total number of missing required inputs. This ensures that the node will be + * scheduled correctly when all inputs have been provided. */ + locked_node.node_state.missing_required_inputs += missing_values; + + /* Get all origin sockets, because we have to tag those as required as well. */ + Vector<DSocket> origin_sockets; + input_socket.foreach_origin_socket( + [&](const DSocket origin_socket) { origin_sockets.append(origin_socket); }); + + if (origin_sockets.is_empty()) { + /* If there are no origin sockets, just load the value from the socket directly. */ + this->load_unlinked_input_value(locked_node, input_socket, input_state, input_socket); + locked_node.node_state.missing_required_inputs -= 1; + this->schedule_node(locked_node); + return; + } + bool will_be_triggered_by_other_node = false; + for (const DSocket origin_socket : origin_sockets) { + if (origin_socket->is_input()) { + /* Load the value directly from the origin socket. In most cases this is an unlinked + * group input. */ + this->load_unlinked_input_value(locked_node, input_socket, input_state, origin_socket); + locked_node.node_state.missing_required_inputs -= 1; + this->schedule_node(locked_node); + return; + } + /* The value has not been computed yet, so when it will be forwarded by another node, this + * node will be triggered. */ + will_be_triggered_by_other_node = true; + + locked_node.delayed_required_outputs.append(DOutputSocket(origin_socket)); + } + /* If this node will be triggered by another node, we don't have to schedule it now. */ + if (!will_be_triggered_by_other_node) { + this->schedule_node(locked_node); + } + } + + void set_input_unused(LockedNode &locked_node, const DInputSocket socket) + { + InputState &input_state = locked_node.node_state.inputs[socket->index()]; + + /* A required socket cannot become unused. */ + BLI_assert(input_state.usage != ValueUsage::Required); + + if (input_state.usage == ValueUsage::Unused) { + /* Nothing to do in this case. */ + return; + } + input_state.usage = ValueUsage::Unused; + + /* If the input is unused, it's value can be destructed now. */ + this->destruct_input_value_if_exists(locked_node, socket); + + if (input_state.was_ready_for_execution) { + /* If the value was already computed, we don't need to notify origin nodes. */ + return; + } + + /* Notify origin nodes that might want to set its inputs as unused as well. */ + socket.foreach_origin_socket([&](const DSocket origin_socket) { + if (origin_socket->is_input()) { + /* Values from these sockets are loaded directly from the sockets, so there is no node to + * notify. */ + return; + } + /* Delay notification of the other node until this node is not locked anymore. */ + locked_node.delayed_unused_outputs.append(DOutputSocket(origin_socket)); + }); + } + + void send_output_required_notification(const DOutputSocket socket) + { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + OutputState &output_state = node_state.outputs[socket->index()]; + + LockedNode locked_node{*this, node, node_state}; + if (output_state.output_usage == ValueUsage::Required) { + /* Output is marked as required already. So the node is scheduled already. */ + return; + } + /* The origin node needs to be scheduled so that it provides the requested input + * eventually. */ + output_state.output_usage = ValueUsage::Required; + this->schedule_node(locked_node); + } + + void send_output_unused_notification(const DOutputSocket socket) + { + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + OutputState &output_state = node_state.outputs[socket->index()]; + + LockedNode locked_node{*this, node, node_state}; + output_state.potential_users -= 1; + if (output_state.potential_users == 0) { + /* The output socket has no users anymore. */ + output_state.output_usage = ValueUsage::Unused; + /* Schedule the origin node in case it wants to set its inputs as unused as well. */ + this->schedule_node(locked_node); + } + } + + void add_node_to_task_pool(const DNode node) + { + /* Push the task to the pool while it is not locked to avoid a deadlock in case when the task + * is executed immediately. */ + const NodeWithState *node_with_state = node_states_.lookup_key_ptr_as(node); + BLI_task_pool_push( + task_pool_, run_node_from_task_pool, (void *)node_with_state, false, nullptr); + } + + /** + * Moves a newly computed value from an output socket to all the inputs that might need it. + */ + void forward_output(const DOutputSocket from_socket, GMutablePointer value_to_forward) + { + BLI_assert(value_to_forward.get() != nullptr); + + Vector<DInputSocket> to_sockets; + auto handle_target_socket_fn = [&, this](const DInputSocket to_socket) { + if (this->should_forward_to_socket(to_socket)) { + to_sockets.append(to_socket); + } + }; + auto handle_skipped_socket_fn = [&, this](const DSocket socket) { + /* Log socket value on intermediate sockets to support e.g. attribute search or spreadsheet + * breadcrumbs on group nodes. */ + this->log_socket_value(socket, value_to_forward); + }; + from_socket.foreach_target_socket(handle_target_socket_fn, handle_skipped_socket_fn); + + LinearAllocator<> &allocator = local_allocators_.local(); + + const CPPType &from_type = *value_to_forward.type(); + Vector<DInputSocket> to_sockets_same_type; + for (const DInputSocket &to_socket : to_sockets) { + const CPPType &to_type = *get_socket_cpp_type(to_socket); + if (from_type == to_type) { + /* All target sockets that do not need a conversion will be handled afterwards. */ + to_sockets_same_type.append(to_socket); + continue; + } + this->forward_to_socket_with_different_type( + allocator, value_to_forward, from_socket, to_socket, to_type); + } + this->forward_to_sockets_with_same_type( + allocator, to_sockets_same_type, value_to_forward, from_socket); + } + + bool should_forward_to_socket(const DInputSocket socket) + { + const DNode to_node = socket.node(); + const NodeWithState *target_node_with_state = node_states_.lookup_key_ptr_as(to_node); + if (target_node_with_state == nullptr) { + /* If the socket belongs to a node that has no state, the entire node is not used. */ + return false; + } + NodeState &target_node_state = *target_node_with_state->state; + InputState &target_input_state = target_node_state.inputs[socket->index()]; + + std::lock_guard lock{target_node_state.mutex}; + /* Do not forward to an input socket whose value won't be used. */ + return target_input_state.usage != ValueUsage::Unused; + } + + void forward_to_socket_with_different_type(LinearAllocator<> &allocator, + const GPointer value_to_forward, + const DOutputSocket from_socket, + const DInputSocket to_socket, + const CPPType &to_type) + { + const CPPType &from_type = *value_to_forward.type(); + + /* Allocate a buffer for the converted value. */ + void *buffer = allocator.allocate(to_type.size(), to_type.alignment()); + + if (conversions_.is_convertible(from_type, to_type)) { + /* Do the conversion if possible. */ + conversions_.convert_to_uninitialized(from_type, to_type, value_to_forward.get(), buffer); + } + else { + /* Cannot convert, use default value instead. */ + to_type.copy_to_uninitialized(to_type.default_value(), buffer); + } + this->add_value_to_input_socket(to_socket, from_socket, {to_type, buffer}); + } + + void forward_to_sockets_with_same_type(LinearAllocator<> &allocator, + Span<DInputSocket> to_sockets, + GMutablePointer value_to_forward, + const DOutputSocket from_socket) + { + if (to_sockets.is_empty()) { + /* Value is not used anymore, so it can be destructed. */ + value_to_forward.destruct(); + } + else if (to_sockets.size() == 1) { + /* Value is only used by one input socket, no need to copy it. */ + const DInputSocket to_socket = to_sockets[0]; + this->add_value_to_input_socket(to_socket, from_socket, value_to_forward); + } + else { + /* Multiple inputs use the value, make a copy for every input except for one. */ + /* First make the copies, so that the next node does not start modifying the value while we + * are still making copies. */ + const CPPType &type = *value_to_forward.type(); + for (const DInputSocket &to_socket : to_sockets.drop_front(1)) { + void *buffer = allocator.allocate(type.size(), type.alignment()); + type.copy_to_uninitialized(value_to_forward.get(), buffer); + this->add_value_to_input_socket(to_socket, from_socket, {type, buffer}); + } + /* Forward the original value to one of the targets. */ + const DInputSocket to_socket = to_sockets[0]; + this->add_value_to_input_socket(to_socket, from_socket, value_to_forward); + } + } + + void add_value_to_input_socket(const DInputSocket socket, + const DOutputSocket origin, + GMutablePointer value) + { + BLI_assert(socket->is_available()); + + const DNode node = socket.node(); + NodeState &node_state = this->get_node_state(node); + InputState &input_state = node_state.inputs[socket->index()]; + + /* Lock the node because we want to change its state. */ + LockedNode locked_node{*this, node, node_state}; + + if (socket->is_multi_input_socket()) { + /* Add a new value to the multi-input. */ + MultiInputValue &multi_value = *input_state.value.multi; + multi_value.items.append({origin, value.get()}); + } + else { + /* Assign the value to the input. */ + SingleInputValue &single_value = *input_state.value.single; + BLI_assert(single_value.value == nullptr); + single_value.value = value.get(); + } + + if (input_state.usage == ValueUsage::Required) { + node_state.missing_required_inputs--; + if (node_state.missing_required_inputs == 0) { + /* Schedule node if all the required inputs have been provided. */ + this->schedule_node(locked_node); + } + } + } + + void load_unlinked_input_value(LockedNode &locked_node, + const DInputSocket input_socket, + InputState &input_state, + const DSocket origin_socket) + { + /* Only takes locked node as parameter, because the node needs to be locked. */ + UNUSED_VARS(locked_node); + + GMutablePointer value = this->get_value_from_socket(origin_socket, *input_state.type); + if (input_socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + multi_value.items.append({origin_socket, value.get()}); + } + else { + SingleInputValue &single_value = *input_state.value.single; + single_value.value = value.get(); + } + } + + void destruct_input_value_if_exists(LockedNode &locked_node, const DInputSocket socket) + { + InputState &input_state = locked_node.node_state.inputs[socket->index()]; + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + for (MultiInputValueItem &item : multi_value.items) { + input_state.type->destruct(item.value); + } + multi_value.items.clear(); + } + else { + SingleInputValue &single_value = *input_state.value.single; + if (single_value.value != nullptr) { + input_state.type->destruct(single_value.value); + single_value.value = nullptr; + } + } + } + + GMutablePointer get_value_from_socket(const DSocket socket, const CPPType &required_type) + { + LinearAllocator<> &allocator = local_allocators_.local(); + + bNodeSocket *bsocket = socket->bsocket(); + const CPPType &type = *get_socket_cpp_type(socket); + void *buffer = allocator.allocate(type.size(), type.alignment()); + blender::nodes::socket_cpp_value_get(*bsocket, buffer); + + if (type == required_type) { + return {type, buffer}; + } + if (conversions_.is_convertible(type, required_type)) { + /* Convert the loaded value to the required type if possible. */ + void *converted_buffer = allocator.allocate(required_type.size(), required_type.alignment()); + conversions_.convert_to_uninitialized(type, required_type, buffer, converted_buffer); + type.destruct(buffer); + return {required_type, converted_buffer}; + } + /* Use a default fallback value when the loaded type is not compatible. */ + void *default_buffer = allocator.allocate(required_type.size(), required_type.alignment()); + required_type.copy_to_uninitialized(required_type.default_value(), default_buffer); + return {required_type, default_buffer}; + } + + NodeState &get_node_state(const DNode node) + { + return *node_states_.lookup_key_as(node).state; + } + + void log_socket_value(const DSocket socket, Span<GPointer> values) + { + if (params_.log_socket_value_fn) { + params_.log_socket_value_fn(socket, values); + } + } + + void log_socket_value(const DSocket socket, + InputState &input_state, + Span<MultiInputValueItem> values) + { + Vector<GPointer, 16> value_pointers; + value_pointers.reserve(values.size()); + const CPPType &type = *input_state.type; + for (const MultiInputValueItem &item : values) { + value_pointers.append({type, item.value}); + } + this->log_socket_value(socket, value_pointers); + } + + void log_socket_value(const DSocket socket, GPointer value) + { + this->log_socket_value(socket, Span<GPointer>(&value, 1)); + } +}; + +LockedNode::~LockedNode() +{ + /* First unlock the current node. */ + node_state.mutex.unlock(); + /* Then send notifications to the other nodes. */ + for (const DOutputSocket &socket : delayed_required_outputs) { + evaluator_.send_output_required_notification(socket); + } + for (const DOutputSocket &socket : delayed_unused_outputs) { + evaluator_.send_output_unused_notification(socket); + } + for (const DNode &node : delayed_scheduled_nodes) { + evaluator_.add_node_to_task_pool(node); + } +} + +NodeParamsProvider::NodeParamsProvider(GeometryNodesEvaluator &evaluator, + DNode dnode, + NodeState &node_state) + : evaluator_(evaluator), node_state_(node_state) +{ + this->dnode = dnode; + this->self_object = evaluator.params_.self_object; + this->modifier = &evaluator.params_.modifier_->modifier; + this->depsgraph = evaluator.params_.depsgraph; +} + +bool NodeParamsProvider::can_get_input(StringRef identifier) const +{ + const DInputSocket socket = this->dnode.input_by_identifier(identifier); + BLI_assert(socket); + + InputState &input_state = node_state_.inputs[socket->index()]; + if (!input_state.was_ready_for_execution) { + return false; + } + + if (socket->is_multi_input_socket()) { + MultiInputValue &multi_value = *input_state.value.multi; + return multi_value.items.size() == multi_value.expected_size; + } + SingleInputValue &single_value = *input_state.value.single; + return single_value.value != nullptr; +} + +bool NodeParamsProvider::can_set_output(StringRef identifier) const +{ + const DOutputSocket socket = this->dnode.output_by_identifier(identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + return !output_state.has_been_computed; +} + +GMutablePointer NodeParamsProvider::extract_input(StringRef identifier) +{ + const DInputSocket socket = this->dnode.input_by_identifier(identifier); + BLI_assert(socket); + BLI_assert(!socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + SingleInputValue &single_value = *input_state.value.single; + void *value = single_value.value; + single_value.value = nullptr; + return {*input_state.type, value}; +} + +Vector<GMutablePointer> NodeParamsProvider::extract_multi_input(StringRef identifier) +{ + const DInputSocket socket = this->dnode.input_by_identifier(identifier); + BLI_assert(socket); + BLI_assert(socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + MultiInputValue &multi_value = *input_state.value.multi; + + Vector<GMutablePointer> ret_values; + socket.foreach_origin_socket([&](DSocket origin) { + for (const MultiInputValueItem &item : multi_value.items) { + if (item.origin == origin) { + ret_values.append({*input_state.type, item.value}); + return; + } + } + BLI_assert_unreachable(); + }); + if (ret_values.is_empty()) { + /* If the socket is not linked, we just use the value from the socket itself. */ + BLI_assert(multi_value.items.size() == 1); + MultiInputValueItem &item = multi_value.items[0]; + BLI_assert(item.origin == socket); + ret_values.append({*input_state.type, item.value}); + } + multi_value.items.clear(); + return ret_values; +} + +GPointer NodeParamsProvider::get_input(StringRef identifier) const +{ + const DInputSocket socket = this->dnode.input_by_identifier(identifier); + BLI_assert(socket); + BLI_assert(!socket->is_multi_input_socket()); + BLI_assert(this->can_get_input(identifier)); + + InputState &input_state = node_state_.inputs[socket->index()]; + SingleInputValue &single_value = *input_state.value.single; + return {*input_state.type, single_value.value}; +} + +GMutablePointer NodeParamsProvider::alloc_output_value(const CPPType &type) +{ + LinearAllocator<> &allocator = evaluator_.local_allocators_.local(); + return {type, allocator.allocate(type.size(), type.alignment())}; +} + +void NodeParamsProvider::set_output(StringRef identifier, GMutablePointer value) +{ + const DOutputSocket socket = this->dnode.output_by_identifier(identifier); + BLI_assert(socket); + + evaluator_.log_socket_value(socket, value); + + OutputState &output_state = node_state_.outputs[socket->index()]; + BLI_assert(!output_state.has_been_computed); + evaluator_.forward_output(socket, value); + output_state.has_been_computed = true; +} + +bool NodeParamsProvider::lazy_require_input(StringRef identifier) +{ + BLI_assert(node_supports_laziness(this->dnode)); + const DInputSocket socket = this->dnode.input_by_identifier(identifier); + BLI_assert(socket); + + InputState &input_state = node_state_.inputs[socket->index()]; + if (input_state.was_ready_for_execution) { + return false; + } + LockedNode locked_node{evaluator_, this->dnode, node_state_}; + evaluator_.set_input_required(locked_node, socket); + return true; +} + +void NodeParamsProvider::set_input_unused(StringRef identifier) +{ + const DInputSocket socket = this->dnode.input_by_identifier(identifier); + BLI_assert(socket); + + LockedNode locked_node{evaluator_, this->dnode, node_state_}; + evaluator_.set_input_unused(locked_node, socket); +} + +bool NodeParamsProvider::output_is_required(StringRef identifier) const +{ + const DOutputSocket socket = this->dnode.output_by_identifier(identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + if (output_state.has_been_computed) { + return false; + } + return output_state.output_usage_for_execution != ValueUsage::Unused; +} + +bool NodeParamsProvider::lazy_output_is_required(StringRef identifier) const +{ + BLI_assert(node_supports_laziness(this->dnode)); + const DOutputSocket socket = this->dnode.output_by_identifier(identifier); + BLI_assert(socket); + + OutputState &output_state = node_state_.outputs[socket->index()]; + if (output_state.has_been_computed) { + return false; + } + return output_state.output_usage_for_execution == ValueUsage::Required; +} + +void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms) +{ + GeometryNodesEvaluator evaluator{params}; + evaluator.execute(); +} + +} // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/modifiers/intern/MOD_nodes_evaluator.hh b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh new file mode 100644 index 00000000000..84249e4244e --- /dev/null +++ b/source/blender/modifiers/intern/MOD_nodes_evaluator.hh @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#pragma once + +#include "BLI_map.hh" + +#include "NOD_derived_node_tree.hh" +#include "NOD_node_tree_multi_function.hh" + +#include "FN_generic_pointer.hh" + +#include "DNA_modifier_types.h" + +namespace blender::modifiers::geometry_nodes { + +using namespace nodes::derived_node_tree_types; +using fn::GMutablePointer; +using fn::GPointer; + +using LogSocketValueFn = std::function<void(DSocket, Span<GPointer>)>; + +struct GeometryNodesEvaluationParams { + blender::LinearAllocator<> allocator; + + Map<DOutputSocket, GMutablePointer> input_values; + Vector<DInputSocket> output_sockets; + nodes::MultiFunctionByNode *mf_by_node; + const NodesModifierData *modifier_; + Depsgraph *depsgraph; + Object *self_object; + LogSocketValueFn log_socket_value_fn; + + Vector<GMutablePointer> r_output_values; +}; + +void evaluate_geometry_nodes(GeometryNodesEvaluationParams ¶ms); + +} // namespace blender::modifiers::geometry_nodes diff --git a/source/blender/modifiers/intern/MOD_none.c b/source/blender/modifiers/intern/MOD_none.c index 4daa527577b..a01f63be791 100644 --- a/source/blender/modifiers/intern/MOD_none.c +++ b/source/blender/modifiers/intern/MOD_none.c @@ -60,7 +60,6 @@ ModifierTypeInfo modifierType_None = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ NULL, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_normal_edit.c b/source/blender/modifiers/intern/MOD_normal_edit.c index ec10b18665e..4bfd6aba4b2 100644 --- a/source/blender/modifiers/intern/MOD_normal_edit.c +++ b/source/blender/modifiers/intern/MOD_normal_edit.c @@ -805,7 +805,6 @@ ModifierTypeInfo modifierType_NormalEdit = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_ocean.c b/source/blender/modifiers/intern/MOD_ocean.c index 9c940745920..f7ac59f9e4b 100644 --- a/source/blender/modifiers/intern/MOD_ocean.c +++ b/source/blender/modifiers/intern/MOD_ocean.c @@ -737,7 +737,6 @@ ModifierTypeInfo modifierType_Ocean = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_particleinstance.c b/source/blender/modifiers/intern/MOD_particleinstance.c index e7f1fa9943e..60c5667472e 100644 --- a/source/blender/modifiers/intern/MOD_particleinstance.c +++ b/source/blender/modifiers/intern/MOD_particleinstance.c @@ -679,7 +679,6 @@ ModifierTypeInfo modifierType_ParticleInstance = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_particlesystem.c b/source/blender/modifiers/intern/MOD_particlesystem.c index 4c1179af431..38cce5e6a50 100644 --- a/source/blender/modifiers/intern/MOD_particlesystem.c +++ b/source/blender/modifiers/intern/MOD_particlesystem.c @@ -337,7 +337,6 @@ ModifierTypeInfo modifierType_ParticleSystem = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_remesh.c b/source/blender/modifiers/intern/MOD_remesh.c index 175435fcd44..88851f91337 100644 --- a/source/blender/modifiers/intern/MOD_remesh.c +++ b/source/blender/modifiers/intern/MOD_remesh.c @@ -301,7 +301,6 @@ ModifierTypeInfo modifierType_Remesh = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_screw.c b/source/blender/modifiers/intern/MOD_screw.c index 84360caa345..b236e0896b7 100644 --- a/source/blender/modifiers/intern/MOD_screw.c +++ b/source/blender/modifiers/intern/MOD_screw.c @@ -1258,7 +1258,6 @@ ModifierTypeInfo modifierType_Screw = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_shapekey.c b/source/blender/modifiers/intern/MOD_shapekey.c index 81a0ee72496..b517bc102f8 100644 --- a/source/blender/modifiers/intern/MOD_shapekey.c +++ b/source/blender/modifiers/intern/MOD_shapekey.c @@ -141,7 +141,6 @@ ModifierTypeInfo modifierType_ShapeKey = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ NULL, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_shrinkwrap.c b/source/blender/modifiers/intern/MOD_shrinkwrap.c index 93626309727..a12724ec23c 100644 --- a/source/blender/modifiers/intern/MOD_shrinkwrap.c +++ b/source/blender/modifiers/intern/MOD_shrinkwrap.c @@ -293,7 +293,6 @@ ModifierTypeInfo modifierType_Shrinkwrap = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_simpledeform.c b/source/blender/modifiers/intern/MOD_simpledeform.c index ea31bdc6e31..db01dec4d19 100644 --- a/source/blender/modifiers/intern/MOD_simpledeform.c +++ b/source/blender/modifiers/intern/MOD_simpledeform.c @@ -21,10 +21,9 @@ * \ingroup modifiers */ -#include "BLI_utildefines.h" - #include "BLI_math.h" - +#include "BLI_task.h" +#include "BLI_utildefines.h" #include "BLT_translation.h" #include "DNA_defaults.h" @@ -57,6 +56,21 @@ #define BEND_EPS 0.000001f +ALIGN_STRUCT struct DeformUserData { + bool invert_vgroup; + char mode; + char deform_axis; + int lock_axis; + int vgroup; + int limit_axis; + float weight; + float smd_factor; + float smd_limit[2]; + float (*vertexCos)[3]; + const SpaceTransform *transf; + const MDeformVert *dvert; +}; + /* Re-maps the indices for X Y Z by shifting them up and wrapping, such that * X = Y, Y = Z, Z = X (for X axis), and X = Z, Y = X, Z = Y (for Y axis). This * exists because the deformations (excluding bend) are based on the Z axis. @@ -170,10 +184,12 @@ static void simpleDeform_bend(const float factor, sint = sinf(theta); cost = cosf(theta); + /* NOTE: the operations below a susceptible to float precision errors + * regarding the order of operations, take care when changing, see: T85470 */ switch (axis) { case 0: r_co[0] = x; - r_co[1] = (y - 1.0f / factor) * cost + 1.0f / factor; + r_co[1] = y * cost + (1.0f - cost) / factor; r_co[2] = -(y - 1.0f / factor) * sint; { r_co[0] += dcut[0]; @@ -182,7 +198,7 @@ static void simpleDeform_bend(const float factor, } break; case 1: - r_co[0] = (x - 1.0f / factor) * cost + 1.0f / factor; + r_co[0] = x * cost + (1.0f - cost) / factor; r_co[1] = y; r_co[2] = -(x - 1.0f / factor) * sint; { @@ -193,7 +209,7 @@ static void simpleDeform_bend(const float factor, break; default: r_co[0] = -(y - 1.0f / factor) * sint; - r_co[1] = (y - 1.0f / factor) * cost + 1.0f / factor; + r_co[1] = y * cost + (1.0f - cost) / factor; r_co[2] = z; { r_co[0] += cost * dcut[0]; @@ -203,6 +219,88 @@ static void simpleDeform_bend(const float factor, } } +static void simple_helper(void *__restrict userdata, + const int iter, + const TaskParallelTLS *__restrict UNUSED(tls)) +{ + const struct DeformUserData *curr_deform_data = userdata; + float weight = BKE_defvert_array_find_weight_safe( + curr_deform_data->dvert, iter, curr_deform_data->vgroup); + const uint *axis_map = axis_map_table[(curr_deform_data->mode != MOD_SIMPLEDEFORM_MODE_BEND) ? + curr_deform_data->deform_axis : + 2]; + const float base_limit[2] = {0.0f, 0.0f}; + + if (curr_deform_data->invert_vgroup) { + weight = 1.0f - weight; + } + + if (weight != 0.0f) { + float co[3], dcut[3] = {0.0f, 0.0f, 0.0f}; + + if (curr_deform_data->transf) { + BLI_space_transform_apply(curr_deform_data->transf, curr_deform_data->vertexCos[iter]); + } + + copy_v3_v3(co, curr_deform_data->vertexCos[iter]); + + /* Apply axis limits, and axis mappings */ + if (curr_deform_data->lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_X) { + axis_limit(0, base_limit, co, dcut); + } + if (curr_deform_data->lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Y) { + axis_limit(1, base_limit, co, dcut); + } + if (curr_deform_data->lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Z) { + axis_limit(2, base_limit, co, dcut); + } + axis_limit(curr_deform_data->limit_axis, curr_deform_data->smd_limit, co, dcut); + + /* apply the deform to a mapped copy of the vertex, and then re-map it back. */ + float co_remap[3]; + float dcut_remap[3]; + copy_v3_v3_map(co_remap, co, axis_map); + copy_v3_v3_map(dcut_remap, dcut, axis_map); + switch (curr_deform_data->mode) { + case MOD_SIMPLEDEFORM_MODE_TWIST: + simpleDeform_twist(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + case MOD_SIMPLEDEFORM_MODE_BEND: + simpleDeform_bend(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + case MOD_SIMPLEDEFORM_MODE_TAPER: + simpleDeform_taper(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + case MOD_SIMPLEDEFORM_MODE_STRETCH: + simpleDeform_stretch(curr_deform_data->smd_factor, + curr_deform_data->deform_axis, + dcut_remap, + co_remap); /* apply deform */ + break; + default: + return; /* No simple-deform mode? */ + } + copy_v3_v3_unmap(co, co_remap, axis_map); + + /* Use vertex weight has coef of linear interpolation */ + interp_v3_v3v3( + curr_deform_data->vertexCos[iter], curr_deform_data->vertexCos[iter], co, weight); + + if (curr_deform_data->transf) { + BLI_space_transform_invert(curr_deform_data->transf, curr_deform_data->vertexCos[iter]); + } + } +} + /* simple deform modifier */ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, const ModifierEvalContext *UNUSED(ctx), @@ -211,14 +309,9 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, float (*vertexCos)[3], int numVerts) { - const float base_limit[2] = {0.0f, 0.0f}; int i; float smd_limit[2], smd_factor; SpaceTransform *transf = NULL, tmp_transf; - void (*simpleDeform_callback)(const float factor, - const int axis, - const float dcut[3], - float co[3]) = NULL; /* Mode callback */ int vgroup; MDeformVert *dvert; @@ -300,23 +393,6 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, smd_factor = smd->factor / max_ff(FLT_EPSILON, smd_limit[1] - smd_limit[0]); } - switch (smd->mode) { - case MOD_SIMPLEDEFORM_MODE_TWIST: - simpleDeform_callback = simpleDeform_twist; - break; - case MOD_SIMPLEDEFORM_MODE_BEND: - simpleDeform_callback = simpleDeform_bend; - break; - case MOD_SIMPLEDEFORM_MODE_TAPER: - simpleDeform_callback = simpleDeform_taper; - break; - case MOD_SIMPLEDEFORM_MODE_STRETCH: - simpleDeform_callback = simpleDeform_stretch; - break; - default: - return; /* No simple-deform mode? */ - } - if (smd->mode == MOD_SIMPLEDEFORM_MODE_BEND) { if (fabsf(smd_factor) < BEND_EPS) { return; @@ -325,53 +401,26 @@ static void SimpleDeformModifier_do(SimpleDeformModifierData *smd, MOD_get_vgroup(ob, mesh, smd->vgroup_name, &dvert, &vgroup); const bool invert_vgroup = (smd->flag & MOD_SIMPLEDEFORM_FLAG_INVERT_VGROUP) != 0; - const uint *axis_map = - axis_map_table[(smd->mode != MOD_SIMPLEDEFORM_MODE_BEND) ? deform_axis : 2]; - - for (i = 0; i < numVerts; i++) { - float weight = BKE_defvert_array_find_weight_safe(dvert, i, vgroup); - - if (invert_vgroup) { - weight = 1.0f - weight; - } - - if (weight != 0.0f) { - float co[3], dcut[3] = {0.0f, 0.0f, 0.0f}; - if (transf) { - BLI_space_transform_apply(transf, vertexCos[i]); - } - - copy_v3_v3(co, vertexCos[i]); - - /* Apply axis limits, and axis mappings */ - if (lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_X) { - axis_limit(0, base_limit, co, dcut); - } - if (lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Y) { - axis_limit(1, base_limit, co, dcut); - } - if (lock_axis & MOD_SIMPLEDEFORM_LOCK_AXIS_Z) { - axis_limit(2, base_limit, co, dcut); - } - axis_limit(limit_axis, smd_limit, co, dcut); - - /* apply the deform to a mapped copy of the vertex, and then re-map it back. */ - float co_remap[3]; - float dcut_remap[3]; - copy_v3_v3_map(co_remap, co, axis_map); - copy_v3_v3_map(dcut_remap, dcut, axis_map); - simpleDeform_callback(smd_factor, deform_axis, dcut_remap, co_remap); /* apply deform */ - copy_v3_v3_unmap(co, co_remap, axis_map); - - /* Use vertex weight has coef of linear interpolation */ - interp_v3_v3v3(vertexCos[i], vertexCos[i], co, weight); - - if (transf) { - BLI_space_transform_invert(transf, vertexCos[i]); - } - } - } + /* Build our data. */ + const struct DeformUserData deform_pool_data = { + .mode = smd->mode, + .smd_factor = smd_factor, + .deform_axis = deform_axis, + .transf = transf, + .vertexCos = vertexCos, + .invert_vgroup = invert_vgroup, + .lock_axis = lock_axis, + .vgroup = vgroup, + .smd_limit[0] = smd_limit[0], + .smd_limit[1] = smd_limit[1], + .dvert = dvert, + .limit_axis = limit_axis, + }; + /* Do deformation. */ + TaskParallelSettings settings; + BLI_parallel_range_settings_defaults(&settings); + BLI_task_parallel_range(0, numVerts, (void *)&deform_pool_data, simple_helper, &settings); } /* SimpleDeform */ @@ -553,7 +602,6 @@ ModifierTypeInfo modifierType_SimpleDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_skin.c b/source/blender/modifiers/intern/MOD_skin.c index 5e412185cea..58d70ef3a4a 100644 --- a/source/blender/modifiers/intern/MOD_skin.c +++ b/source/blender/modifiers/intern/MOD_skin.c @@ -90,6 +90,46 @@ #include "bmesh.h" +/* -------------------------------------------------------------------- */ +/** \name Generic BMesh Utilities + * \{ */ + +static void vert_face_normal_mark_set(BMVert *v) +{ + BMIter iter; + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + f->no[0] = FLT_MAX; + } +} + +static void vert_face_normal_mark_update(BMVert *v) +{ + BMIter iter; + BMFace *f; + BM_ITER_ELEM (f, &iter, v, BM_FACES_OF_VERT) { + if (f->no[0] == FLT_MAX) { + BM_face_normal_update(f); + } + } +} + +/** + * Recalculate the normals of all faces connected to `verts`. + */ +static void vert_array_face_normal_update(BMVert **verts, int verts_len) +{ + for (int i = 0; i < verts_len; i++) { + vert_face_normal_mark_set(verts[i]); + } + + for (int i = 0; i < verts_len; i++) { + vert_face_normal_mark_update(verts[i]); + } +} + +/** \} */ + typedef struct { float mat[3][3]; /* Vert that edge is pointing away from, no relation to @@ -1352,13 +1392,25 @@ static void skin_fix_hole_no_good_verts(BMesh *bm, Frame *frame, BMFace *split_f split_face = collapse_face_corners(bm, split_face, 4, vert_buf); } - /* Done with dynamic array, split_face must now be a quad */ - BLI_array_free(vert_buf); + /* `split_face` should now be a quad. */ BLI_assert(split_face->len == 4); + + /* Account for the highly unlikely case that it's not a quad. */ if (split_face->len != 4) { + /* Reuse `vert_buf` for updating normals. */ + BLI_array_clear(vert_buf); + BLI_array_grow_items(vert_buf, split_face->len); + + BM_iter_as_array(bm, BM_FACES_OF_VERT, split_face, (void **)vert_buf, split_face->len); + + vert_array_face_normal_update(vert_buf, split_face->len); + BLI_array_free(vert_buf); return; } + /* Done with dynamic array. */ + BLI_array_free(vert_buf); + /* Get split face's verts */ // BM_iter_as_array(bm, BM_VERTS_OF_FACE, split_face, (void **)verts, 4); BM_face_as_array_vert_quad(split_face, verts); @@ -1373,6 +1425,8 @@ static void skin_fix_hole_no_good_verts(BMesh *bm, Frame *frame, BMFace *split_f } BMO_op_exec(bm, &op); BMO_op_finish(bm, &op); + + vert_array_face_normal_update(frame->verts, 4); } /* If the frame has some vertices that are inside the hull (detached) @@ -1731,6 +1785,11 @@ static void skin_smooth_hulls(BMesh *bm, /* Done with original coordinates */ BM_data_layer_free_n(bm, &bm->vdata, CD_SHAPEKEY, skey); + + BMFace *f; + BM_ITER_MESH (f, &iter, bm, BM_FACES_OF_MESH) { + BM_face_normal_update(f); + } } /* Returns true if all hulls are successfully built, false otherwise */ @@ -2044,7 +2103,6 @@ ModifierTypeInfo modifierType_Skin = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_smooth.c b/source/blender/modifiers/intern/MOD_smooth.c index c5011ed15c1..97027e2ecff 100644 --- a/source/blender/modifiers/intern/MOD_smooth.c +++ b/source/blender/modifiers/intern/MOD_smooth.c @@ -286,7 +286,6 @@ ModifierTypeInfo modifierType_Smooth = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_softbody.c b/source/blender/modifiers/intern/MOD_softbody.c index 9a657c44fca..d7d2f948955 100644 --- a/source/blender/modifiers/intern/MOD_softbody.c +++ b/source/blender/modifiers/intern/MOD_softbody.c @@ -120,7 +120,6 @@ ModifierTypeInfo modifierType_Softbody = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ NULL, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_solidify.c b/source/blender/modifiers/intern/MOD_solidify.c index 8e519a72df1..736dd08a713 100644 --- a/source/blender/modifiers/intern/MOD_solidify.c +++ b/source/blender/modifiers/intern/MOD_solidify.c @@ -276,7 +276,6 @@ ModifierTypeInfo modifierType_Solidify = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_solidify_extrude.c b/source/blender/modifiers/intern/MOD_solidify_extrude.c index 99069919120..a77a6e595ad 100644 --- a/source/blender/modifiers/intern/MOD_solidify_extrude.c +++ b/source/blender/modifiers/intern/MOD_solidify_extrude.c @@ -216,7 +216,7 @@ Mesh *MOD_solidify_extrude_modifyMesh(ModifierData *md, const ModifierEvalContex numVerts, sizeof(*old_vert_arr), "old_vert_arr in solidify"); uint *edge_users = NULL; - char *edge_order = NULL; + int *edge_order = NULL; float(*vert_nors)[3] = NULL; float(*poly_nors)[3] = NULL; diff --git a/source/blender/modifiers/intern/MOD_subsurf.c b/source/blender/modifiers/intern/MOD_subsurf.c index c3611488f5f..4dc45ad0324 100644 --- a/source/blender/modifiers/intern/MOD_subsurf.c +++ b/source/blender/modifiers/intern/MOD_subsurf.c @@ -509,7 +509,6 @@ ModifierTypeInfo modifierType_Subsurf = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_surface.c b/source/blender/modifiers/intern/MOD_surface.c index 7416a4baf38..bfd4cd81803 100644 --- a/source/blender/modifiers/intern/MOD_surface.c +++ b/source/blender/modifiers/intern/MOD_surface.c @@ -184,13 +184,17 @@ static void deformVerts(ModifierData *md, surmd->cfra = cfra; - surmd->bvhtree = MEM_callocN(sizeof(BVHTreeFromMesh), "BVHTreeFromMesh"); + const bool has_poly = surmd->mesh->totpoly > 0; + const bool has_edge = surmd->mesh->totedge > 0; + if (has_poly || has_edge) { + surmd->bvhtree = MEM_callocN(sizeof(BVHTreeFromMesh), "BVHTreeFromMesh"); - if (surmd->mesh->totpoly) { - BKE_bvhtree_from_mesh_get(surmd->bvhtree, surmd->mesh, BVHTREE_FROM_LOOPTRI, 2); - } - else { - BKE_bvhtree_from_mesh_get(surmd->bvhtree, surmd->mesh, BVHTREE_FROM_EDGES, 2); + if (has_poly) { + BKE_bvhtree_from_mesh_get(surmd->bvhtree, surmd->mesh, BVHTREE_FROM_LOOPTRI, 2); + } + else if (has_edge) { + BKE_bvhtree_from_mesh_get(surmd->bvhtree, surmd->mesh, BVHTREE_FROM_EDGES, 2); + } } } } @@ -241,7 +245,6 @@ ModifierTypeInfo modifierType_Surface = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, diff --git a/source/blender/modifiers/intern/MOD_surfacedeform.c b/source/blender/modifiers/intern/MOD_surfacedeform.c index 99011c5e351..0cc68c2c4a3 100644 --- a/source/blender/modifiers/intern/MOD_surfacedeform.c +++ b/source/blender/modifiers/intern/MOD_surfacedeform.c @@ -1646,7 +1646,6 @@ ModifierTypeInfo modifierType_SurfaceDeform = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_triangulate.c b/source/blender/modifiers/intern/MOD_triangulate.c index 04d24ac0883..ef633494c7b 100644 --- a/source/blender/modifiers/intern/MOD_triangulate.c +++ b/source/blender/modifiers/intern/MOD_triangulate.c @@ -178,7 +178,6 @@ ModifierTypeInfo modifierType_Triangulate = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ NULL, // requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_ui_common.c b/source/blender/modifiers/intern/MOD_ui_common.c index 6b2facc16a2..0be5c164089 100644 --- a/source/blender/modifiers/intern/MOD_ui_common.c +++ b/source/blender/modifiers/intern/MOD_ui_common.c @@ -344,14 +344,39 @@ static void modifier_panel_header(const bContext *C, Panel *panel) } } /* Tessellation point for curve-typed objects. */ else if (ELEM(ob->type, OB_CURVE, OB_SURF, OB_FONT)) { - if (mti->type != eModifierTypeType_Constructive) { + /* Some modifiers can work with pre-tessellated curves only. */ + if (ELEM(md->type, eModifierType_Hook, eModifierType_Softbody, eModifierType_MeshDeform)) { + /* Add button (appearing to be ON) and add tip why this cant be changed. */ + sub = uiLayoutRow(row, true); + uiBlock *block = uiLayoutGetBlock(sub); + static int apply_on_spline_always_on_hack = eModifierMode_ApplyOnSpline; + uiBut *but = uiDefIconButBitI(block, + UI_BTYPE_TOGGLE, + eModifierMode_ApplyOnSpline, + 0, + ICON_SURFACE_DATA, + 0, + 0, + UI_UNIT_X - 2, + UI_UNIT_Y, + &apply_on_spline_always_on_hack, + 0.0, + 0.0, + 0.0, + 0.0, + TIP_("Apply on Spline")); + UI_but_disable( + but, TIP_("This modifier can only deform control points, not the filled curve/surface")); + buttons_number++; + } + else if (mti->type != eModifierTypeType_Constructive) { /* Constructive modifiers tessellates curve before applying. */ uiItemR(row, ptr, "use_apply_on_spline", 0, "", ICON_NONE); buttons_number++; } } /* Collision and Surface are always enabled, hide buttons. */ - if ((md->type != eModifierType_Collision) && (md->type != eModifierType_Surface)) { + if (!ELEM(md->type, eModifierType_Collision, eModifierType_Surface)) { if (mti->flags & eModifierTypeFlag_SupportsEditmode) { sub = uiLayoutRow(row, true); uiLayoutSetActive(sub, (md->mode & eModifierMode_Realtime)); diff --git a/source/blender/modifiers/intern/MOD_uvproject.c b/source/blender/modifiers/intern/MOD_uvproject.c index 3162a33edc2..724d1370a47 100644 --- a/source/blender/modifiers/intern/MOD_uvproject.c +++ b/source/blender/modifiers/intern/MOD_uvproject.c @@ -385,7 +385,6 @@ ModifierTypeInfo modifierType_UVProject = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_uvwarp.c b/source/blender/modifiers/intern/MOD_uvwarp.c index 77b79167c2f..5742144b6dd 100644 --- a/source/blender/modifiers/intern/MOD_uvwarp.c +++ b/source/blender/modifiers/intern/MOD_uvwarp.c @@ -342,7 +342,6 @@ ModifierTypeInfo modifierType_UVWarp = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_volume_displace.cc b/source/blender/modifiers/intern/MOD_volume_displace.cc index 745e089b8ff..af4b31d6bfc 100644 --- a/source/blender/modifiers/intern/MOD_volume_displace.cc +++ b/source/blender/modifiers/intern/MOD_volume_displace.cc @@ -18,6 +18,7 @@ * \ingroup modifiers */ +#include "BKE_geometry_set.hh" #include "BKE_lib_query.h" #include "BKE_mesh_runtime.h" #include "BKE_modifier.h" @@ -284,7 +285,7 @@ struct DisplaceGridOp { #endif -static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume) +static void displace_volume(ModifierData *md, const ModifierEvalContext *ctx, Volume *volume) { #ifdef WITH_OPENVDB VolumeDisplaceModifierData *vdmd = reinterpret_cast<VolumeDisplaceModifierData *>(md); @@ -293,7 +294,7 @@ static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Vo BKE_volume_load(volume, DEG_get_bmain(ctx->depsgraph)); const int grid_amount = BKE_volume_num_grids(volume); for (int grid_index = 0; grid_index < grid_amount; grid_index++) { - VolumeGrid *volume_grid = BKE_volume_grid_get(volume, grid_index); + VolumeGrid *volume_grid = BKE_volume_grid_get_for_write(volume, grid_index); BLI_assert(volume_grid != nullptr); openvdb::GridBase::Ptr grid = BKE_volume_grid_openvdb_for_write(volume, volume_grid, false); @@ -303,14 +304,22 @@ static Volume *modifyVolume(ModifierData *md, const ModifierEvalContext *ctx, Vo BKE_volume_grid_type_operation(grid_type, displace_grid_op); } - return volume; #else - UNUSED_VARS(md, ctx); + UNUSED_VARS(md, volume, ctx); BKE_modifier_set_error(ctx->object, md, "Compiled without OpenVDB"); - return volume; #endif } +static void modifyGeometrySet(ModifierData *md, + const ModifierEvalContext *ctx, + GeometrySet *geometry_set) +{ + Volume *input_volume = geometry_set->get_volume_for_write(); + if (input_volume != nullptr) { + displace_volume(md, ctx, input_volume); + } +} + ModifierTypeInfo modifierType_VolumeDisplace = { /* name */ "Volume Displace", /* structName */ "VolumeDisplaceModifierData", @@ -328,8 +337,7 @@ ModifierTypeInfo modifierType_VolumeDisplace = { /* deformMatricesEM */ nullptr, /* modifyMesh */ nullptr, /* modifyHair */ nullptr, - /* modifyGeometrySet */ nullptr, - /* modifyVolume */ modifyVolume, + /* modifyGeometrySet */ modifyGeometrySet, /* initData */ initData, /* requiredDataMask */ nullptr, diff --git a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc index 41ed7ae983a..c0bf07b8eec 100644 --- a/source/blender/modifiers/intern/MOD_volume_to_mesh.cc +++ b/source/blender/modifiers/intern/MOD_volume_to_mesh.cc @@ -155,10 +155,10 @@ static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh * return create_empty_mesh(input_mesh); } - Volume *volume = static_cast<Volume *>(vmmd->object->data); + const Volume *volume = static_cast<Volume *>(vmmd->object->data); BKE_volume_load(volume, DEG_get_bmain(ctx->depsgraph)); - VolumeGrid *volume_grid = BKE_volume_grid_find(volume, vmmd->grid_name); + const VolumeGrid *volume_grid = BKE_volume_grid_find_for_read(volume, vmmd->grid_name); if (volume_grid == nullptr) { BKE_modifier_set_error(ctx->object, md, "Cannot find '%s' grid", vmmd->grid_name); return create_empty_mesh(input_mesh); @@ -223,7 +223,6 @@ ModifierTypeInfo modifierType_VolumeToMesh = { /* modifyMesh */ modifyMesh, /* modifyHair */ nullptr, /* modifyGeometrySet */ nullptr, - /* modifyVolume */ nullptr, /* initData */ initData, /* requiredDataMask */ nullptr, diff --git a/source/blender/modifiers/intern/MOD_warp.c b/source/blender/modifiers/intern/MOD_warp.c index 9d3d5b0658c..3bebc52c503 100644 --- a/source/blender/modifiers/intern/MOD_warp.c +++ b/source/blender/modifiers/intern/MOD_warp.c @@ -539,7 +539,6 @@ ModifierTypeInfo modifierType_Warp = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_wave.c b/source/blender/modifiers/intern/MOD_wave.c index 863656b85a5..c6bab89247e 100644 --- a/source/blender/modifiers/intern/MOD_wave.c +++ b/source/blender/modifiers/intern/MOD_wave.c @@ -492,7 +492,6 @@ ModifierTypeInfo modifierType_Wave = { /* modifyMesh */ NULL, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.c b/source/blender/modifiers/intern/MOD_weighted_normal.c index 40265e37db9..2f2da7d6554 100644 --- a/source/blender/modifiers/intern/MOD_weighted_normal.c +++ b/source/blender/modifiers/intern/MOD_weighted_normal.c @@ -764,7 +764,6 @@ ModifierTypeInfo modifierType_WeightedNormal = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weightvgedit.c b/source/blender/modifiers/intern/MOD_weightvgedit.c index 915adccc745..b5f72c88800 100644 --- a/source/blender/modifiers/intern/MOD_weightvgedit.c +++ b/source/blender/modifiers/intern/MOD_weightvgedit.c @@ -428,7 +428,6 @@ ModifierTypeInfo modifierType_WeightVGEdit = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weightvgmix.c b/source/blender/modifiers/intern/MOD_weightvgmix.c index 52cee7ce7e5..a71a2f3b480 100644 --- a/source/blender/modifiers/intern/MOD_weightvgmix.c +++ b/source/blender/modifiers/intern/MOD_weightvgmix.c @@ -514,7 +514,6 @@ ModifierTypeInfo modifierType_WeightVGMix = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weightvgproximity.c b/source/blender/modifiers/intern/MOD_weightvgproximity.c index aac29cabf0f..cd03175f16c 100644 --- a/source/blender/modifiers/intern/MOD_weightvgproximity.c +++ b/source/blender/modifiers/intern/MOD_weightvgproximity.c @@ -768,7 +768,6 @@ ModifierTypeInfo modifierType_WeightVGProximity = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_weld.c b/source/blender/modifiers/intern/MOD_weld.c index fd1254fc948..1590f342666 100644 --- a/source/blender/modifiers/intern/MOD_weld.c +++ b/source/blender/modifiers/intern/MOD_weld.c @@ -2053,7 +2053,6 @@ ModifierTypeInfo modifierType_Weld = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, diff --git a/source/blender/modifiers/intern/MOD_wireframe.c b/source/blender/modifiers/intern/MOD_wireframe.c index 3d8e74d2cf5..16bf1f7d763 100644 --- a/source/blender/modifiers/intern/MOD_wireframe.c +++ b/source/blender/modifiers/intern/MOD_wireframe.c @@ -196,7 +196,6 @@ ModifierTypeInfo modifierType_Wireframe = { /* modifyMesh */ modifyMesh, /* modifyHair */ NULL, /* modifyGeometrySet */ NULL, - /* modifyVolume */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, |