diff options
36 files changed, 2839 insertions, 93 deletions
diff --git a/release/datafiles/locale b/release/datafiles/locale -Subproject 469c949d1ca882be19daa128842f813b72a944d +Subproject 59495b4b59077aa1cc68fffbdae1463af980f08 diff --git a/release/scripts/addons b/release/scripts/addons -Subproject c88411ff7776a2db5d6ef6117a1b2faa42a9561 +Subproject 27970761a18926abe1b0020aa350305e3109a53 diff --git a/release/scripts/addons_contrib b/release/scripts/addons_contrib -Subproject 310578043dec1aae382eb6a447ae1d103792d7e +Subproject 6a4f93c9b8f36b19bd02087abf3d7f5983df035 diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py index 467c3df3158..afee450bf06 100644 --- a/release/scripts/startup/bl_operators/mesh.py +++ b/release/scripts/startup/bl_operators/mesh.py @@ -203,57 +203,7 @@ class MeshSelectPrev(Operator): return {'FINISHED'} -# XXX This is hackish (going forth and back from Object mode...), to be redone once we have proper support of -# custom normals in BMesh/edit mode. -class MehsSetNormalsFromFaces(Operator): - """Set the custom vertex normals from the selected faces ones""" - bl_idname = "mesh.set_normals_from_faces" - bl_label = "Set Normals From Faces" - bl_options = {'REGISTER', 'UNDO'} - - @classmethod - def poll(cls, context): - return (context.mode == 'EDIT_MESH' and context.edit_object.data.polygons) - - def execute(self, context): - import mathutils - - bpy.ops.object.mode_set(mode='OBJECT') - obj = context.active_object - me = obj.data - - v2nors = {} - for p in me.polygons: - if not p.select: - continue - for lidx, vidx in zip(p.loop_indices, p.vertices): - assert(me.loops[lidx].vertex_index == vidx) - v2nors.setdefault(vidx, []).append(p.normal) - - for nors in v2nors.values(): - nors[:] = [sum(nors, mathutils.Vector((0, 0, 0))).normalized()] - - if not me.has_custom_normals: - me.create_normals_split() - me.calc_normals_split() - - normals = [] - for l in me.loops: - nor = v2nors.get(l.vertex_index, [None])[0] - if nor is None: - nor = l.normal - normals.append(nor.to_tuple()) - - me.normals_split_custom_set(normals) - - me.free_normals_split() - bpy.ops.object.mode_set(mode='EDIT') - - return {'FINISHED'} - - classes = ( - MehsSetNormalsFromFaces, MeshMirrorUV, MeshSelectNext, MeshSelectPrev, diff --git a/release/scripts/startup/bl_ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py index 942882a9053..a01c38870ce 100644 --- a/release/scripts/startup/bl_ui/properties_data_modifier.py +++ b/release/scripts/startup/bl_ui/properties_data_modifier.py @@ -1549,6 +1549,22 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel): if md.rest_source == 'BIND': layout.operator("object.correctivesmooth_bind", text="Unbind" if is_bind else "Bind") + def WEIGHTED_NORMAL(self, layout, ob, md): + layout.label("Weighting Mode:") + split = layout.split(align=True) + col = split.column(align=True) + col.prop(md, "mode", text="") + col.prop(md, "weight", text="Weight") + col.prop(md, "keep_sharp") + + col = split.column(align=True) + row = col.row(align=True) + row.prop_search(md, "vertex_group", ob, "vertex_groups", text="") + row.active = bool(md.vertex_group) + row.prop(md, "invert_vertex_group", text="", icon='ARROW_LEFTRIGHT') + col.prop(md, "thresh", text="Threshold") + col.prop(md, "face_influence") + classes = ( DATA_PT_modifiers, diff --git a/release/scripts/startup/bl_ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 19cba5af4ee..8343fa45f62 100644 --- a/release/scripts/startup/bl_ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -441,11 +441,59 @@ class VIEW3D_PT_tools_shading(View3DPanel, Panel): props.clear = True row.operator("mesh.mark_sharp", text="Sharp").use_verts = True + +class VIEW3D_PT_tools_normal(View3DPanel, Panel): + bl_category = "Shading / UVs" + bl_context = "mesh_edit" + bl_label = "Normal Tools" + + def draw(self, context): + layout = self.layout + toolsettings = context.tool_settings + col = layout.column(align=True) - col.label(text="Normals:") + col.label(text="Vertex Normals:") col.operator("mesh.normals_make_consistent", text="Recalculate") col.operator("mesh.flip_normals", text="Flip Direction") + + layout.separator() + layout.label(text="Split Normals:") + + col = layout.column(align=True) col.operator("mesh.set_normals_from_faces", text="Set From Faces") + col.operator("transform.rotate_normal", text="Rotate") + col.operator("mesh.point_normals", text="Point To...") + + row = layout.row(align=True) + row.operator("mesh.merge_loop_normals", text="Merge") + row.operator("mesh.split_loop_normals", text="Split") + + col = layout.column(align=True) + col.operator_menu_enum("mesh.average_loop_normals", "average_type") + + col = layout.column(align=True) + col.label(text="Normal Vector:") + col.prop(toolsettings, "normal_vector", text="") + + row = col.row(align=True) + row.operator("mesh.custom_normal_tools", text="Copy").mode = "Copy" + row.operator("mesh.custom_normal_tools", text="Paste").mode = "Paste" + + row = col.row(align=True) + row.operator("mesh.custom_normal_tools", text="Multiply").mode = "Multiply" + row.operator("mesh.custom_normal_tools", text="Add").mode = "Add" + + col.operator("mesh.custom_normal_tools", text="Reset").mode = "Reset" + + col = layout.column(align=True) + col.operator("mesh.smoothen_custom_normals", text="Smoothen") + + col = layout.column(align=True) + col.label(text="Face Strength:") + row = col.row(align=True) + row.prop(toolsettings, "face_strength", text="") + row.operator("mesh.mod_weighted_strength", text="", icon = "FACESEL").set = False + row.operator("mesh.mod_weighted_strength", text="", icon = "ZOOMIN").set = True class VIEW3D_PT_tools_uvs(View3DPanel, Panel): @@ -2073,6 +2121,7 @@ classes = ( VIEW3D_PT_tools_meshweight, VIEW3D_PT_tools_add_mesh_edit, VIEW3D_PT_tools_shading, + VIEW3D_PT_tools_normal, VIEW3D_PT_tools_uvs, VIEW3D_PT_tools_meshedit_options, VIEW3D_PT_tools_transform_curve, diff --git a/source/blender/blenkernel/BKE_editmesh.h b/source/blender/blenkernel/BKE_editmesh.h index 55a9db9b1e5..7c87eb40a60 100644 --- a/source/blender/blenkernel/BKE_editmesh.h +++ b/source/blender/blenkernel/BKE_editmesh.h @@ -93,6 +93,7 @@ void BKE_editmesh_update_linked_customdata(BMEditMesh *em); void BKE_editmesh_color_free(BMEditMesh *em); void BKE_editmesh_color_ensure(BMEditMesh *em, const char htype); float (*BKE_editmesh_vertexCos_get_orco(BMEditMesh *em, int *r_numVerts))[3]; +void BKE_editmesh_lnorspace_update(BMEditMesh *em); /* editderivedmesh.c */ /* should really be defined in editmesh.c, but they use 'EditDerivedBMesh' */ diff --git a/source/blender/blenkernel/intern/editderivedmesh.c b/source/blender/blenkernel/intern/editderivedmesh.c index ffa7fdc3ec9..c6ab304d333 100644 --- a/source/blender/blenkernel/intern/editderivedmesh.c +++ b/source/blender/blenkernel/intern/editderivedmesh.c @@ -214,7 +214,7 @@ static void emDM_calcLoopNormalsSpaceArray( cd_loop_clnors_offset = clnors_data ? -1 : CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); BM_loops_calc_normal_vcos(bm, vertexCos, vertexNos, polyNos, use_split_normals, split_angle, loopNos, - r_lnors_spacearr, clnors_data, cd_loop_clnors_offset); + r_lnors_spacearr, clnors_data, cd_loop_clnors_offset, false); #ifdef DEBUG_CLNORS if (r_lnors_spacearr) { int i; diff --git a/source/blender/blenkernel/intern/editmesh.c b/source/blender/blenkernel/intern/editmesh.c index fea3c24d322..e326d6f8e0c 100644 --- a/source/blender/blenkernel/intern/editmesh.c +++ b/source/blender/blenkernel/intern/editmesh.c @@ -264,3 +264,26 @@ float (*BKE_editmesh_vertexCos_get_orco(BMEditMesh *em, int *r_numVerts))[3] return orco; } + +void BKE_editmesh_lnorspace_update(BMEditMesh *em) +{ + BMesh *bm = em->bm; + + /* We need to create clnors data is none exist yet, otherwise there is no way to edit them. */ + /* Similar code to MESH_OT_customdata_custom_splitnormals_add operator, we want to keep same shading + * in case we were using autosmooth so far... */ + /* Note: there is a problem here, which is that is someone starts a normal editing operation on previously + * autosmooth-ed mesh, and cancel that operation, generated clnors data remain, with related sharp edges + * (and hence autosmooth is 'lost'). + * Not sure how critical this is, and how to fix that issue? */ + if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) { + Mesh *me = em->ob->data; + if (me->flag & ME_AUTOSMOOTH) { + BM_edges_sharp_from_angle_set(bm, me->smoothresh); + + me->drawflag |= ME_DRAWSHARP; + } + } + + BM_lnorspace_update(bm); +} diff --git a/source/blender/bmesh/bmesh_class.h b/source/blender/bmesh/bmesh_class.h index bec2c7a1f45..faba8e2b954 100644 --- a/source/blender/bmesh/bmesh_class.h +++ b/source/blender/bmesh/bmesh_class.h @@ -38,6 +38,8 @@ struct BMEdge; struct BMLoop; struct BMFace; +struct MLoopNorSpaceArray; + struct BLI_mempool; /* note: it is very important for BMHeader to start with two @@ -236,6 +238,9 @@ typedef struct BMesh { struct BLI_mempool *looplistpool; #endif + struct MLoopNorSpaceArray *lnor_spacearr; /* Stores MLoopNorSpaceArray for this BMesh */ + char spacearr_dirty; + /* should be copy of scene select mode */ /* stored in BMEditMesh too, this is a bit confusing, * make sure they're in sync! @@ -263,9 +268,36 @@ enum { BM_FACE = 8 }; +typedef struct BMLoopNorEditData { + int loop_index; + BMLoop *loop; + float mtx[3][3]; + float smtx[3][3]; + float niloc[3]; + float nloc[3]; + float *loc; + short *clnors_data; +} BMLoopNorEditData; + +typedef struct BMLoopNorEditDataArray { + BMLoopNorEditData *lnor_editdata; + /* This one has full amount of loops, used to map loop index to actual BMLoopNorEditData struct. */ + BMLoopNorEditData **lidx_to_lnor_editdata; + + int cd_custom_normal_offset; + int totloop; + void *funcdata; +} BMLoopNorEditDataArray; + #define BM_ALL (BM_VERT | BM_EDGE | BM_LOOP | BM_FACE) #define BM_ALL_NOLOOP (BM_VERT | BM_EDGE | BM_FACE) +enum { + BM_SPACEARR_DIRTY = 1 << 0, + BM_SPACEARR_DIRTY_ALL = 1 << 1, + BM_SPACEARR_BMO_SET = 1 << 2, +}; + /* args for _Generic */ #define _BM_GENERIC_TYPE_ELEM_NONCONST \ void *, BMVert *, BMEdge *, BMLoop *, BMFace *, \ diff --git a/source/blender/bmesh/intern/bmesh_mesh.c b/source/blender/bmesh/intern/bmesh_mesh.c index 20c49d70b02..99582089bde 100644 --- a/source/blender/bmesh/intern/bmesh_mesh.c +++ b/source/blender/bmesh/intern/bmesh_mesh.c @@ -30,6 +30,7 @@ #include "DNA_listBase.h" #include "DNA_object_types.h" +#include "DNA_scene_types.h" #include "BLI_linklist_stack.h" #include "BLI_listbase.h" @@ -260,6 +261,11 @@ void BM_mesh_data_free(BMesh *bm) BLI_freelistN(&bm->selected); + if (bm->lnor_spacearr) { + BKE_lnor_spacearr_free(bm->lnor_spacearr); + MEM_freeN(bm->lnor_spacearr); + } + BMO_error_clear(bm); } @@ -313,6 +319,10 @@ void BM_mesh_free(BMesh *bm) * Helpers for #BM_mesh_normals_update and #BM_verts_calc_normal_vcos */ +/* We use that existing internal API flag, assuming no other tool using it would run concurrently to clnors editing. */ +/* XXX Should we rather add a new internal flag? */ +#define BM_LNORSPACE_UPDATE _FLAG_MF + typedef struct BMEdgesCalcVectorsData { /* Read-only data. */ const float (*vcos)[3]; @@ -638,7 +648,8 @@ bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr) * Will use first clnors_data array, and fallback to cd_loop_clnors_offset (use NULL and -1 to not use clnors). */ static void bm_mesh_loops_calc_normals( BMesh *bm, const float (*vcos)[3], const float (*fnos)[3], float (*r_lnos)[3], - MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset) + MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], + const int cd_loop_clnors_offset, const bool do_rebuild) { BMIter fiter; BMFace *f_curr; @@ -694,6 +705,11 @@ static void bm_mesh_loops_calc_normals( l_curr = l_first = BM_FACE_FIRST_LOOP(f_curr); do { + if (do_rebuild && !BM_ELEM_API_FLAG_TEST(l_curr, BM_LNORSPACE_UPDATE) && + !(bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL)) + { + continue; + } /* A smooth edge, we have to check for cyclic smooth fan case. * If we find a new, never-processed cyclic smooth fan, we can do it now using that loop/edge as * 'entry point', otherwise we can skip it. */ @@ -894,7 +910,10 @@ static void bm_mesh_loops_calc_normals( clnors_avg[0] /= clnors_nbr; clnors_avg[1] /= clnors_nbr; /* Fix/update all clnors of this fan with computed average value. */ - printf("Invalid clnors in this fan!\n"); + + /* Prints continuously when merge custom normals, so commenting. */ + /* printf("Invalid clnors in this fan!\n"); */ + while ((clnor = BLI_SMALLSTACK_POP(clnors))) { //print_v2("org clnor", clnor); clnor[0] = (short)clnors_avg[0]; @@ -1009,7 +1028,8 @@ void BM_mesh_loop_normals_update( void BM_loops_calc_normal_vcos( BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*fnos)[3], const bool use_split_normals, const float split_angle, float (*r_lnos)[3], - MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset) + MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], + const int cd_loop_clnors_offset, const bool do_rebuild) { const bool has_clnors = clnors_data || (cd_loop_clnors_offset != -1); @@ -1019,7 +1039,8 @@ void BM_loops_calc_normal_vcos( bm_mesh_edges_sharp_tag(bm, vnos, fnos, r_lnos, has_clnors ? (float)M_PI : split_angle, false); /* Finish computing lnos by accumulating face normals in each fan of faces defined by sharp edges. */ - bm_mesh_loops_calc_normals(bm, vcos, fnos, r_lnos, r_lnors_spacearr, clnors_data, cd_loop_clnors_offset); + bm_mesh_loops_calc_normals( + bm, vcos, fnos, r_lnos, r_lnors_spacearr, clnors_data, cd_loop_clnors_offset, do_rebuild); } else { BLI_assert(!r_lnors_spacearr); @@ -1041,6 +1062,385 @@ void BM_edges_sharp_from_angle_set(BMesh *bm, const float split_angle) bm_mesh_edges_sharp_tag(bm, NULL, NULL, NULL, split_angle, true); } +void BM_lnorspacearr_store(BMesh *bm, float (*r_lnors)[3]) +{ + BLI_assert(bm->lnor_spacearr != NULL); + + if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) { + BM_data_layer_add(bm, &bm->ldata, CD_CUSTOMLOOPNORMAL); + } + + int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + + BM_loops_calc_normal_vcos( + bm, NULL, NULL, NULL, true, M_PI, r_lnors, bm->lnor_spacearr, NULL, cd_loop_clnors_offset, false); + bm->spacearr_dirty &= ~(BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL); +} + +/* will change later */ +#define CLEAR_SPACEARRAY_THRESHOLD(x) ((x) / 2) + +void BM_lnorspace_invalidate(BMesh *bm, const bool do_invalidate_all) +{ + if (bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) { + return; + } + if (do_invalidate_all || bm->totvertsel > CLEAR_SPACEARRAY_THRESHOLD(bm->totvert)) { + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + return; + } + if (bm->lnor_spacearr == NULL) { + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + return; + } + + BMVert *v; + BMLoop *l; + BMIter viter, liter; + /* Note: we could use temp tag of BMItem for that, but probably better not use it in such a low-level func? + * --mont29 */ + BLI_bitmap *done_verts = BLI_BITMAP_NEW(bm->totvert, __func__); + + BM_mesh_elem_index_ensure(bm, BM_VERT); + + /* When we affect a given vertex, we may affect following smooth fans: + * - all smooth fans of said vertex; + * - all smooth fans of all immediate loop-neighbors vertices; + * This can be simplified as 'all loops of selected vertices and their immediate neighbors' + * need to be tagged for update. + */ + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) { + BM_ELEM_API_FLAG_ENABLE(l, BM_LNORSPACE_UPDATE); + + /* Note that we only handle unselected neighbor vertices here, main loop will take care of + * selected ones. */ + if (!BM_elem_flag_test(l->prev->v, BM_ELEM_SELECT) && + !BLI_BITMAP_TEST(done_verts, BM_elem_index_get(l->prev->v))) + { + BMLoop *l_prev; + BMIter liter_prev; + BM_ITER_ELEM(l_prev, &liter_prev, l->prev->v, BM_LOOPS_OF_VERT) { + BM_ELEM_API_FLAG_ENABLE(l_prev, BM_LNORSPACE_UPDATE); + } + BLI_BITMAP_ENABLE(done_verts, BM_elem_index_get(l_prev->v)); + } + + if (!BM_elem_flag_test(l->next->v, BM_ELEM_SELECT) && + !BLI_BITMAP_TEST(done_verts, BM_elem_index_get(l->next->v))) + { + BMLoop *l_next; + BMIter liter_next; + BM_ITER_ELEM(l_next, &liter_next, l->next->v, BM_LOOPS_OF_VERT) { + BM_ELEM_API_FLAG_ENABLE(l_next, BM_LNORSPACE_UPDATE); + } + BLI_BITMAP_ENABLE(done_verts, BM_elem_index_get(l_next->v)); + } + } + + BLI_BITMAP_ENABLE(done_verts, BM_elem_index_get(v)); + } + } + + MEM_freeN(done_verts); + bm->spacearr_dirty |= BM_SPACEARR_DIRTY; +} + +void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor) +{ + BLI_assert(bm->lnor_spacearr != NULL); + + if (!(bm->spacearr_dirty & (BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL))) { + return; + } + BMFace *f; + BMLoop *l; + BMIter fiter, liter; + + float (*r_lnors)[3] = MEM_callocN(sizeof(*r_lnors) * bm->totloop, __func__); + float (*oldnors)[3] = preserve_clnor ? MEM_mallocN(sizeof(*oldnors) * bm->totloop, __func__) : NULL; + + int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + + BM_mesh_elem_index_ensure(bm, BM_LOOP); + + if (preserve_clnor) { + BLI_assert(bm->lnor_spacearr->lspacearr != NULL); + + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(l, &liter, f, BM_LOOPS_OF_FACE) { + if (BM_ELEM_API_FLAG_TEST(l, BM_LNORSPACE_UPDATE) || bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) { + short(*clnor)[2] = BM_ELEM_CD_GET_VOID_P(l, cd_loop_clnors_offset); + int l_index = BM_elem_index_get(l); + + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], *clnor, oldnors[l_index]); + } + } + } + } + + if (bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) { + BKE_lnor_spacearr_clear(bm->lnor_spacearr); + } + BM_loops_calc_normal_vcos( + bm, NULL, NULL, NULL, true, M_PI, r_lnors, bm->lnor_spacearr, NULL, cd_loop_clnors_offset, true); + MEM_freeN(r_lnors); + + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(l, &liter, f, BM_LOOPS_OF_FACE) { + if (BM_ELEM_API_FLAG_TEST(l, BM_LNORSPACE_UPDATE) || bm->spacearr_dirty & BM_SPACEARR_DIRTY_ALL) { + if (preserve_clnor) { + short(*clnor)[2] = BM_ELEM_CD_GET_VOID_P(l, cd_loop_clnors_offset); + int l_index = BM_elem_index_get(l); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], oldnors[l_index], *clnor); + } + BM_ELEM_API_FLAG_DISABLE(l, BM_LNORSPACE_UPDATE); + } + } + } + + MEM_SAFE_FREE(oldnors); + bm->spacearr_dirty &= ~(BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL); + +#ifndef NDEBUG + BM_lnorspace_err(bm); +#endif +} + +void BM_lnorspace_update(BMesh *bm) +{ + if (bm->lnor_spacearr == NULL) { + bm->lnor_spacearr = MEM_callocN(sizeof(*bm->lnor_spacearr), __func__); + } + if (bm->lnor_spacearr->lspacearr == NULL) { + float (*lnors)[3] = MEM_callocN(sizeof(*lnors) * bm->totloop, __func__); + + BM_lnorspacearr_store(bm, lnors); + + MEM_freeN(lnors); + } + else if(bm->spacearr_dirty & (BM_SPACEARR_DIRTY | BM_SPACEARR_DIRTY_ALL)){ + BM_lnorspace_rebuild(bm, false); + } +} + +/** + * Auxillary function only used by rebuild to detect if any spaces were not marked as invalid. + * Reports error if any of the lnor spaces change after rebuilding, meaning that all the possible + * lnor spaces to be rebuilt were not correctly marked. + */ +#ifndef NDEBUG +void BM_lnorspace_err(BMesh *bm) +{ + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + bool clear = true; + + MLoopNorSpaceArray *temp = MEM_callocN(sizeof(*temp), __func__); + temp->lspacearr = NULL; + + BKE_lnor_spacearr_init(temp, bm->totloop, MLNOR_SPACEARR_BMLOOP_PTR); + + int cd_loop_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + float (*lnors)[3] = MEM_callocN(sizeof(*lnors) * bm->totloop, __func__); + BM_loops_calc_normal_vcos(bm, NULL, NULL, NULL, true, M_PI, lnors, temp, NULL, cd_loop_clnors_offset, true); + + for (int i = 0; i < bm->totloop; i++) { + int j = 0; + j += compare_ff(temp->lspacearr[i]->ref_alpha, bm->lnor_spacearr->lspacearr[i]->ref_alpha, 1e-4f); + j += compare_ff(temp->lspacearr[i]->ref_beta, bm->lnor_spacearr->lspacearr[i]->ref_beta, 1e-4f); + j += compare_v3v3(temp->lspacearr[i]->vec_lnor, bm->lnor_spacearr->lspacearr[i]->vec_lnor, 1e-4f); + j += compare_v3v3(temp->lspacearr[i]->vec_ortho, bm->lnor_spacearr->lspacearr[i]->vec_ortho, 1e-4f); + j += compare_v3v3(temp->lspacearr[i]->vec_ref, bm->lnor_spacearr->lspacearr[i]->vec_ref, 1e-4f); + + if (j != 5) { + clear = false; + break; + } + } + BKE_lnor_spacearr_free(temp); + MEM_freeN(temp); + MEM_freeN(lnors); + BLI_assert(clear); + + bm->spacearr_dirty &= ~BM_SPACEARR_DIRTY_ALL; +} +#endif + +static void bm_loop_normal_mark_indiv_do_loop( + BMLoop *l, BLI_bitmap *loops, MLoopNorSpaceArray *lnor_spacearr, int *totloopsel) +{ + if (l != NULL) { + const int l_idx = BM_elem_index_get(l); + + if (!BLI_BITMAP_TEST(loops, BM_elem_index_get(l))) { + /* If vert and face selected share a loop, mark it for editing. */ + BLI_BITMAP_ENABLE(loops, l_idx); + (*totloopsel)++; + + /* Mark all loops in same loop normal space (aka smooth fan). */ + if ((lnor_spacearr->lspacearr[l_idx]->flags & MLNOR_SPACE_IS_SINGLE) == 0) { + for (LinkNode *node = lnor_spacearr->lspacearr[l_idx]->loops; node; node = node->next) { + const int lfan_idx = BM_elem_index_get((BMLoop *)node->link); + if (!BLI_BITMAP_TEST(loops, lfan_idx)) { + BLI_BITMAP_ENABLE(loops, lfan_idx); + (*totloopsel)++; + } + } + } + } + } +} + +/* Mark the individual clnors to be edited, if multiple selection methods are used. */ +static int bm_loop_normal_mark_indiv(BMesh *bm, BLI_bitmap *loops) +{ + BMEditSelection *ese, *ese_prev; + int totloopsel = 0; + + BM_mesh_elem_index_ensure(bm, BM_LOOP); + + BLI_assert(bm->lnor_spacearr != NULL); + BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR); + + /* Goes from last selected to the first selected element. */ + for (ese = bm->selected.last; ese; ese = ese->prev) { + if (ese->htype == BM_FACE) { + ese_prev = ese; + /* If current face is selected, then any verts to be edited must have been selected before it. */ + while ((ese_prev = ese_prev->prev)) { + if (ese_prev->htype == BM_VERT) { + bm_loop_normal_mark_indiv_do_loop( + BM_face_vert_share_loop((BMFace *)ese->ele, (BMVert *)ese_prev->ele), + loops, bm->lnor_spacearr, &totloopsel); + } + else if (ese_prev->htype == BM_EDGE) { + bm_loop_normal_mark_indiv_do_loop( + BM_face_vert_share_loop((BMFace *)ese->ele, ((BMEdge *)ese_prev->ele)->v1), + loops, bm->lnor_spacearr, &totloopsel); + + bm_loop_normal_mark_indiv_do_loop( + BM_face_vert_share_loop((BMFace *)ese->ele, ((BMEdge *)ese_prev->ele)->v2), + loops, bm->lnor_spacearr, &totloopsel); + } + } + } + } + + return totloopsel; +} + +static void loop_normal_editdata_init(BMesh *bm, BMLoopNorEditData *lnor_ed, BMVert *v, BMLoop *l, const int offset) +{ + BLI_assert(bm->lnor_spacearr != NULL); + BLI_assert(bm->lnor_spacearr->lspacearr != NULL); + + const int l_index = BM_elem_index_get(l); + short *clnors_data = BM_ELEM_CD_GET_VOID_P(l, offset); + + lnor_ed->loop_index = l_index; + lnor_ed->loop = l; + + float custom_normal[3]; + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], clnors_data, custom_normal); + + lnor_ed->clnors_data = clnors_data; + copy_v3_v3(lnor_ed->nloc, custom_normal); + copy_v3_v3(lnor_ed->niloc, custom_normal); + + if (v) { + lnor_ed->loc = v->co; + } + else { + lnor_ed->loc = NULL; + } +} + +BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init(BMesh *bm) +{ + BMLoop *l; + BMVert *v; + BMIter liter, viter; + + bool verts = (bm->selectmode & SCE_SELECT_VERTEX) != 0; + bool edges = (bm->selectmode & SCE_SELECT_EDGE) != 0; + bool faces = (bm->selectmode & SCE_SELECT_FACE) != 0; + int totloopsel = 0; + + BLI_assert(bm->spacearr_dirty == 0); + + BMLoopNorEditDataArray *lnors_ed_arr = MEM_mallocN(sizeof(*lnors_ed_arr), __func__); + lnors_ed_arr->lidx_to_lnor_editdata = MEM_callocN(sizeof(*lnors_ed_arr->lidx_to_lnor_editdata) * bm->totloop, __func__); + + if (!CustomData_has_layer(&bm->ldata, CD_CUSTOMLOOPNORMAL)) { + BM_data_layer_add(bm, &bm->ldata, CD_CUSTOMLOOPNORMAL); + } + const int cd_custom_normal_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + + BM_mesh_elem_index_ensure(bm, BM_LOOP); + + BLI_bitmap *loops = BLI_BITMAP_NEW(bm->totloop, __func__); + if (faces && (verts || edges)) { + /* More than one selection mode, check for individual normals to edit. */ + totloopsel = bm_loop_normal_mark_indiv(bm, loops); + } + + if (totloopsel) { + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata = MEM_mallocN(sizeof(*lnor_ed) * totloopsel, __func__); + + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) { + if (BLI_BITMAP_TEST(loops, BM_elem_index_get(l))) { + loop_normal_editdata_init(bm, lnor_ed, v, l, cd_custom_normal_offset); + lnors_ed_arr->lidx_to_lnor_editdata[BM_elem_index_get(l)] = lnor_ed; + lnor_ed++; + } + } + } + lnors_ed_arr->totloop = totloopsel; + } + else { /* If multiple selection modes are inactive OR no such loop is found, fall back to editing all loops. */ + totloopsel = BM_total_loop_select(bm); + BMLoopNorEditData *tld = lnors_ed_arr->lnor_editdata = MEM_mallocN(sizeof(*tld) * totloopsel, __func__); + + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) { + loop_normal_editdata_init(bm, tld, v, l, cd_custom_normal_offset); + lnors_ed_arr->lidx_to_lnor_editdata[BM_elem_index_get(l)] = tld; + tld++; + } + } + } + lnors_ed_arr->totloop = totloopsel; + } + + MEM_freeN(loops); + lnors_ed_arr->cd_custom_normal_offset = cd_custom_normal_offset; + return lnors_ed_arr; +} + +void BM_loop_normal_editdata_array_free(BMLoopNorEditDataArray *lnors_ed_arr) +{ + MEM_SAFE_FREE(lnors_ed_arr->lnor_editdata); + MEM_SAFE_FREE(lnors_ed_arr->lidx_to_lnor_editdata); + MEM_freeN(lnors_ed_arr); +} + +int BM_total_loop_select(BMesh *bm) +{ + int r_sel = 0; + BMVert *v; + BMIter viter; + + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + r_sel += BM_vert_face_count(v); + } + } + return r_sel; +} + static void UNUSED_FUNCTION(bm_mdisps_space_set)(Object *ob, BMesh *bm, int from, int to) { /* switch multires data out of tangent space */ @@ -1141,6 +1541,7 @@ void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag) /* compute normals, clear temp flags and flush selections */ if (type_flag & BMO_OPTYPE_FLAG_NORMALS_CALC) { + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; BM_mesh_normals_update(bm); } @@ -1157,6 +1558,9 @@ void bmesh_edit_end(BMesh *bm, BMOpTypeFlag type_flag) if ((type_flag & BMO_OPTYPE_FLAG_SELECT_VALIDATE) == 0) { bm->selected = select_history; } + if (type_flag & BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL) { + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + } } void BM_mesh_elem_index_ensure(BMesh *bm, const char htype) diff --git a/source/blender/bmesh/intern/bmesh_mesh.h b/source/blender/bmesh/intern/bmesh_mesh.h index 10f024423aa..51f58a7f44c 100644 --- a/source/blender/bmesh/intern/bmesh_mesh.h +++ b/source/blender/bmesh/intern/bmesh_mesh.h @@ -29,6 +29,7 @@ struct BMAllocTemplate; struct MLoopNorSpaceArray; +struct BMLoopNorEditDataArray; void BM_mesh_elem_toolflags_ensure(BMesh *bm); void BM_mesh_elem_toolflags_clear(BMesh *bm); @@ -50,9 +51,22 @@ void BM_verts_calc_normal_vcos(BMesh *bm, const float (*fnos)[3], const float (* void BM_loops_calc_normal_vcos( BMesh *bm, const float (*vcos)[3], const float (*vnos)[3], const float (*pnos)[3], const bool use_split_normals, const float split_angle, float (*r_lnos)[3], - struct MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], const int cd_loop_clnors_offset); + struct MLoopNorSpaceArray *r_lnors_spacearr, short (*clnors_data)[2], + const int cd_loop_clnors_offset, const bool do_rebuild); bool BM_loop_check_cyclic_smooth_fan(BMLoop *l_curr); +void BM_lnorspacearr_store(BMesh *bm, float (*r_lnors)[3]); +void BM_lnorspace_invalidate(BMesh *bm, const bool do_invalidate_all); +void BM_lnorspace_rebuild(BMesh *bm, bool preserve_clnor); +void BM_lnorspace_update(BMesh *bm); +#ifndef NDEBUG +void BM_lnorspace_err(BMesh *bm); +#endif + +/* Loop Generics */ +struct BMLoopNorEditDataArray *BM_loop_normal_editdata_array_init(BMesh *bm); +void BM_loop_normal_editdata_array_free(struct BMLoopNorEditDataArray *lnors_ed_arr); +int BM_total_loop_select(BMesh *bm); void BM_edges_sharp_from_angle_set(BMesh *bm, const float split_angle); diff --git a/source/blender/bmesh/intern/bmesh_operator_api.h b/source/blender/bmesh/intern/bmesh_operator_api.h index 30cd1df9c4e..de87da71e8d 100644 --- a/source/blender/bmesh/intern/bmesh_operator_api.h +++ b/source/blender/bmesh/intern/bmesh_operator_api.h @@ -244,6 +244,7 @@ typedef enum { BMO_OPTYPE_FLAG_NORMALS_CALC = (1 << 1), BMO_OPTYPE_FLAG_SELECT_FLUSH = (1 << 2), BMO_OPTYPE_FLAG_SELECT_VALIDATE = (1 << 3), + BMO_OPTYPE_FLAG_INVALIDATE_CLNOR_ALL = (1 << 4), } BMOpTypeFlag; typedef struct BMOperator { diff --git a/source/blender/editors/include/ED_screen.h b/source/blender/editors/include/ED_screen.h index af4abc60a23..ff51f6144d2 100644 --- a/source/blender/editors/include/ED_screen.h +++ b/source/blender/editors/include/ED_screen.h @@ -171,6 +171,7 @@ int ED_operator_object_active_editable_font(struct bContext *C); int ED_operator_editmesh(struct bContext *C); int ED_operator_editmesh_view3d(struct bContext *C); int ED_operator_editmesh_region_view3d(struct bContext *C); +int ED_operator_editmesh_auto_smooth(struct bContext *C); int ED_operator_editarmature(struct bContext *C); int ED_operator_editcurve(struct bContext *C); int ED_operator_editcurve_3d(struct bContext *C); diff --git a/source/blender/editors/include/ED_transform.h b/source/blender/editors/include/ED_transform.h index 9a0a7f8f1bb..d70c9a3a91f 100644 --- a/source/blender/editors/include/ED_transform.h +++ b/source/blender/editors/include/ED_transform.h @@ -88,6 +88,7 @@ enum TfmMode { TFM_VERT_SLIDE, TFM_SEQ_SLIDE, TFM_BONE_ENVELOPE_DIST, + TFM_NORMAL_ROTATION, }; /* TRANSFORM CONTEXTS */ @@ -150,6 +151,7 @@ int BIF_countTransformOrientation(const struct bContext *C); #define P_NO_TEXSPACE (1 << 11) #define P_CENTER (1 << 12) #define P_GPENCIL_EDIT (1 << 13) +#define P_CLNOR_INVALIDATE (1 << 14) void Transform_Properties(struct wmOperatorType *ot, int flags); diff --git a/source/blender/editors/mesh/editmesh_tools.c b/source/blender/editors/mesh/editmesh_tools.c index c979a73e964..79adba36302 100644 --- a/source/blender/editors/mesh/editmesh_tools.c +++ b/source/blender/editors/mesh/editmesh_tools.c @@ -41,11 +41,16 @@ #include "DNA_object_types.h" #include "DNA_scene_types.h" +#include "BLI_bitmap.h" +#include "BLI_heap.h" #include "BLI_listbase.h" +#include "BLI_linklist.h" +#include "BLI_linklist_stack.h" #include "BLI_noise.h" #include "BLI_math.h" #include "BLI_rand.h" #include "BLI_sort_utils.h" +#include "BLI_string.h" #include "BKE_material.h" #include "BKE_context.h" @@ -54,6 +59,7 @@ #include "BKE_report.h" #include "BKE_texture.h" #include "BKE_main.h" +#include "BKE_mesh.h" #include "BKE_editmesh.h" #include "BLT_translation.h" @@ -5987,3 +5993,1198 @@ void MESH_OT_mark_freestyle_face(wmOperatorType *ot) } #endif + +/* Initialize loop normal data */ +static int point_normals_init(bContext *C, wmOperator *op, const wmEvent *UNUSED(event)) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + BKE_editmesh_lnorspace_update(em); + BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm); + + lnors_ed_arr->funcdata = NULL; + op->customdata = lnors_ed_arr; + + return lnors_ed_arr->totloop; +} + +static void point_normals_apply(bContext *C, wmOperator *op, const wmEvent *UNUSED(event), float target[3]) +{ + Object *obedit = CTX_data_edit_object(C); + BMesh *bm = BKE_editmesh_from_object(obedit)->bm; + BMLoopNorEditDataArray *lnors_ed_arr = op->customdata; + + const bool do_point_away = RNA_boolean_get(op->ptr, "point_away"); + const bool do_spherize = RNA_boolean_get(op->ptr, "spherize"); + const bool do_align = RNA_boolean_get(op->ptr, "align"); + const float null_vec[3] = { 0.0f }; + float center[3]; + + if (do_align) { + BMVert *v; + BMIter viter; + int i = 0; + zero_v3(center); + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + add_v3_v3(center, v->co); + i++; + } + } + mul_v3_fl(center, 1.0f / (float)i); + } + + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (do_spherize) { + const float strength = RNA_float_get(op->ptr, "strength"); + float spherized_normal[3] = { 0.0f }; + + sub_v3_v3v3(spherized_normal, target, lnor_ed->loc); + sub_v3_v3(spherized_normal, obedit->loc); + mul_v3_fl(spherized_normal, strength); + mul_v3_fl(lnor_ed->nloc, 1.0f - strength); + sub_v3_v3(lnor_ed->nloc, spherized_normal); + } + else if (do_align) { + sub_v3_v3v3(lnor_ed->nloc, target, center); + sub_v3_v3(lnor_ed->nloc, obedit->loc); + } + else { + sub_v3_v3v3(lnor_ed->nloc, target, lnor_ed->loc); + sub_v3_v3(lnor_ed->nloc, obedit->loc); + } + + if (do_point_away) { + negate_v3(lnor_ed->nloc); + } + if (lnor_ed->loop_index != -1 && !compare_v3v3(lnor_ed->nloc, null_vec, 1e-4f)) { + normalize_v3(lnor_ed->nloc); + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + } +} + +static void point_normals_free(bContext *C, wmOperator *op) +{ + BMLoopNorEditDataArray *lnors_ed_arr = op->customdata; + BM_loop_normal_editdata_array_free(lnors_ed_arr); + op->customdata = NULL; + ED_area_headerprint(CTX_wm_area(C), NULL); +} + +static int point_normals_mouse(bContext *C, wmOperator *op, const wmEvent *event) +{ + View3D *v3d = CTX_wm_view3d(C); + Object *obedit = CTX_data_edit_object(C); + ARegion *ar = CTX_wm_region(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMVert *v; + BMIter viter; + int i = 0; + + float target[3], center[3]; + + RNA_float_get_array(op->ptr, "target_location", target); + + zero_v3(center); + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + add_v3_v3(center, v->co); + add_v3_v3(center, obedit->loc); + i++; + } + } + mul_v3_fl(center, 1.0f / (float)i); + + ED_view3d_win_to_3d_int(v3d, ar, center, event->mval, target); + + point_normals_apply(C, op, event, target); + EDBM_update_generic(em, true, false); + + if (event->type == LEFTMOUSE) { + RNA_float_set_array(op->ptr, "target_location", target); + point_normals_free(C, op); + return OPERATOR_FINISHED; + } + else if ((ISKEYBOARD(event->type) || event->type == RIGHTMOUSE) && event->type != MKEY) { + BMLoopNorEditDataArray *lnors_ed_arr = op->customdata; + + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + for (i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { /* Reset custom normal data. */ + if (lnor_ed->loop_index != -1) { + BKE_lnor_space_custom_normal_to_data( + bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->niloc, lnor_ed->clnors_data); + } + } + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + return OPERATOR_PASS_THROUGH; +} + +static int edbm_point_normals_modal(bContext *C, wmOperator *op, const wmEvent *event) +{ + View3D *v3d = CTX_wm_view3d(C); + Scene *scene = CTX_data_scene(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + float target[3]; + + bool handled = false; + PropertyRNA *prop_target = RNA_struct_find_property(op->ptr, "target_location"); + BMLoopNorEditDataArray *lnors_ed_arr = op->customdata; + + if (lnors_ed_arr->funcdata) { /* Executes and transfers control to point_normals_mouse. */ + int (*apply)(bContext *, wmOperator *, const wmEvent *); + apply = lnors_ed_arr->funcdata; + return apply(C, op, event); + } + + if (event->val == KM_PRESS) { + BMVert *v; + BMIter viter; + + if (event->type == LEFTMOUSE) { + ED_view3d_cursor3d_update(C, event->mval); + copy_v3_v3(target, ED_view3d_cursor3d_get(scene, v3d)); + RNA_property_float_set_array(op->ptr, prop_target, target); + + handled = true; + } + if (event->type == RIGHTMOUSE) { + view3d_operator_needs_opengl(C); + const bool retval = EDBM_select_pick(C, event->mval, false, false, false); + if (!retval) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + ED_object_editmode_calc_active_center(obedit, false, target); /* Point to newly selected active */ + add_v3_v3(target, obedit->loc); + RNA_property_float_set_array(op->ptr, prop_target, target); + handled = true; + } + else if (event->type == LKEY) { + switch (v3d->around) { + case V3D_AROUND_CENTER_BOUNDS: /* calculateCenterBound */ + { + float min[3], max[3]; + int i = 0; + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + if (i) { + minmax_v3v3_v3(min, max, v->co); + } + else { + copy_v3_v3(min, v->co); + copy_v3_v3(max, v->co); + } + i++; + } + } + mid_v3_v3v3(target, min, max); + add_v3_v3(target, obedit->loc); + RNA_property_float_set_array(op->ptr, prop_target, target); + break; + } + + case V3D_AROUND_CENTER_MEAN: + { + zero_v3(target); + int i = 0; + BM_ITER_MESH(v, &viter, bm, BM_VERTS_OF_MESH) { + if (BM_elem_flag_test(v, BM_ELEM_SELECT)) { + add_v3_v3(target, v->co); + add_v3_v3(target, obedit->loc); + i++; + } + } + mul_v3_fl(target, 1.0f / (float)i); + RNA_property_float_set_array(op->ptr, prop_target, target); + break; + } + + case V3D_AROUND_CURSOR: + copy_v3_v3(target, ED_view3d_cursor3d_get(scene, v3d)); + RNA_property_float_set_array(op->ptr, prop_target, target); + break; + + case V3D_AROUND_ACTIVE: + if (!ED_object_editmode_calc_active_center(obedit, false, target)) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + add_v3_v3(target, obedit->loc); + RNA_property_float_set_array(op->ptr, prop_target, target); + break; + + default: + BKE_report(op->reports, RPT_ERROR, "Does not support Individual Origin as pivot"); + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + handled = true; + } + else if (event->type == MKEY) { + lnors_ed_arr->funcdata = point_normals_mouse; + char header[UI_MAX_DRAW_STR]; + BLI_snprintf(header, sizeof(header), IFACE_("Left Click to Confirm, Right click to Cancel")); + + ED_area_headerprint(CTX_wm_area(C), header); + } + else if (event->type == OKEY) { + copy_v3_v3(target, obedit->loc); + RNA_property_float_set_array(op->ptr, prop_target, target); + handled = true; + } + else if (ISKEYBOARD(event->type) && event->type != RIGHTALTKEY) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + } + + if (handled || event->type == RIGHTMOUSE) { + RNA_boolean_set(op->ptr, "align", false); + } + + if (handled) { + point_normals_apply(C, op, event, target); + EDBM_update_generic(em, true, false); /* Recheck bools. */ + point_normals_free(C, op); + + return OPERATOR_FINISHED; + } + + return OPERATOR_PASS_THROUGH; +} + +static int edbm_point_normals_invoke(bContext *C, wmOperator *op, const wmEvent *event) +{ + if (!point_normals_init(C, op, event)) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + + WM_event_add_modal_handler(C, op); + + char header[UI_MAX_DRAW_STR]; + BLI_snprintf(header, sizeof(header), + IFACE_("L Key to use Pivot as target, M Key to point to mouse, O Key to point to object origin, " + "Left Click to point to new cursor location, Right Click on mesh to point to mesh")); + + ED_area_headerprint(CTX_wm_area(C), header); + + op->flag |= OP_IS_MODAL_GRAB_CURSOR; + return OPERATOR_RUNNING_MODAL; +} + +static int edbm_point_normals_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + + if (!point_normals_init(C, op, NULL)) { + point_normals_free(C, op); + return OPERATOR_CANCELLED; + } + + float target[3]; + RNA_float_get_array(op->ptr, "target_location", target); + + point_normals_apply(C, op, NULL, target); + + EDBM_update_generic(em, true, false); + point_normals_free(C, op); + + return OPERATOR_FINISHED; +} + +static bool point_normals_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + const bool spherize = RNA_boolean_get(ptr, "spherize"); + + /* Only show strength option if spherize is enabled. */ + if (STREQ(prop_id, "strength")) { + return spherize; + } + + /* Else, show it! */ + return true; +} + +static void edbm_point_normals_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, point_normals_draw_check_prop, '\0'); +} + +void MESH_OT_point_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Point normals to Target"; + ot->description = "Point selected normals to specified Target"; + ot->idname = "MESH_OT_point_normals"; + + /* api callbacks */ + ot->exec = edbm_point_normals_exec; + ot->invoke = edbm_point_normals_invoke; + ot->modal = edbm_point_normals_modal; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = edbm_point_normals_ui; + + /* flags */ + ot->flag = OPTYPE_BLOCKING | OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "point_away", false, "Point Away", "Point Away from target"); + + RNA_def_boolean(ot->srna, "align", false, "Align", "Align normal with mouse location"); + + RNA_def_float_vector(ot->srna, "target_location", 3, (float[3]){0.0f, 0.0f, 0.0f}, -FLT_MAX, FLT_MAX, + "Target", "Target location to which normals will point", -1000.0f, 1000.0f); + + RNA_def_boolean(ot->srna, "spherize", false, + "Spherize Normal", "Add normal vector of target to custom normal with given proportion"); + + RNA_def_float(ot->srna, "strength", 0.1, 0.0f, 1.0f, + "Strength", "Ratio of spherized normal to original normal", 0.0f, 1.0f); +} + +/********************** Split/Merge Loop Normals **********************/ + +static void custom_loops_tag(BMesh *bm, const bool do_edges) +{ + BMFace *f; + BMEdge *e; + BMIter fiter, eiter; + BMLoop *l_curr, *l_first; + + if (do_edges) { + int index_edge; + BM_ITER_MESH_INDEX(e, &eiter, bm, BM_EDGES_OF_MESH, index_edge) { + BMLoop *l_a, *l_b; + + BM_elem_index_set(e, index_edge); /* set_inline */ + BM_elem_flag_disable(e, BM_ELEM_TAG); + if (BM_edge_loop_pair(e, &l_a, &l_b)) { + if (BM_elem_flag_test(e, BM_ELEM_SMOOTH) && l_a->v != l_b->v) { + BM_elem_flag_enable(e, BM_ELEM_TAG); + } + } + } + bm->elem_index_dirty &= ~BM_EDGE; + } + + int index_face, index_loop = 0; + BM_ITER_MESH_INDEX(f, &fiter, bm, BM_FACES_OF_MESH, index_face) { + BM_elem_index_set(f, index_face); /* set_inline */ + l_curr = l_first = BM_FACE_FIRST_LOOP(f); + do { + BM_elem_index_set(l_curr, index_loop++); /* set_inline */ + BM_elem_flag_disable(l_curr, BM_ELEM_TAG); + } while ((l_curr = l_curr->next) != l_first); + } + bm->elem_index_dirty &= ~(BM_FACE | BM_LOOP); +} + +static void loop_normal_merge(bContext *C, BMLoopNorEditDataArray *lnors_ed_arr) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + + BLI_SMALLSTACK_DECLARE(clnors, short *); + + BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR); + + custom_loops_tag(bm, false); + + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (BM_elem_flag_test(lnor_ed->loop, BM_ELEM_TAG)) { + continue; + } + + MLoopNorSpace *lnor_space = bm->lnor_spacearr->lspacearr[lnor_ed->loop_index]; + + if ((lnor_space->flags & MLNOR_SPACE_IS_SINGLE) == 0) { + LinkNode *loops = lnor_space->loops; + float avg_normal[3] = {0.0f, 0.0f, 0.0f}; + short *clnors_data; + + for (; loops; loops = loops->next) { + BMLoop *l = loops->link; + const int loop_index = BM_elem_index_get(l); + + BMLoopNorEditData *lnor_ed_tmp = lnors_ed_arr->lidx_to_lnor_editdata[loop_index]; + BLI_assert(lnor_ed_tmp->loop_index == loop_index && lnor_ed_tmp->loop == l); + add_v3_v3(avg_normal, lnor_ed_tmp->nloc); + BLI_SMALLSTACK_PUSH(clnors, lnor_ed_tmp->clnors_data); + BM_elem_flag_enable(l, BM_ELEM_TAG); + } + if (len_squared_v3(avg_normal) < 1e-4f) { /* If avg normal is nearly 0, set clnor to default value. */ + while ((clnors_data = BLI_SMALLSTACK_POP(clnors))) { + copy_v2_v2_short(clnors_data, (short[2]){0, 0}); + } + } + else { + normalize_v3(avg_normal); /* Else set all clnors to this avg. */ + while ((clnors_data = BLI_SMALLSTACK_POP(clnors))) { + BKE_lnor_space_custom_normal_to_data(lnor_space, avg_normal, clnors_data); + } + } + } + } +} + +static void loop_normla_split(bContext *C) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMLoop *l, *l_curr, *l_first; + BMIter fiter; + + BLI_assert(bm->lnor_spacearr->data_type == MLNOR_SPACEARR_BMLOOP_PTR); + + custom_loops_tag(bm, true); + + const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + l_curr = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_curr->v, BM_ELEM_SELECT) && (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) || + (!BM_elem_flag_test(l_curr, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_curr)))) + { + if (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_curr->prev->e, BM_ELEM_TAG)) { + const int loop_index = BM_elem_index_get(l_curr); + short *clnors = BM_ELEM_CD_GET_VOID_P(l_curr, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors); + } + else { + BMVert *v_pivot = l_curr->v; + BMEdge *e_next; + const BMEdge *e_org = l_curr->e; + BMLoop *lfan_pivot, *lfan_pivot_next; + + lfan_pivot = l_curr; + e_next = lfan_pivot->e; + BLI_SMALLSTACK_DECLARE(loops, BMLoop *); + float avg_normal[3] = { 0.0f }; + + while (true) { + lfan_pivot_next = BM_vert_step_fan_loop(lfan_pivot, &e_next); + if (lfan_pivot_next) { + BLI_assert(lfan_pivot_next->v == v_pivot); + } + else { + e_next = (lfan_pivot->e == e_next) ? lfan_pivot->prev->e : lfan_pivot->e; + } + + BLI_SMALLSTACK_PUSH(loops, lfan_pivot); + add_v3_v3(avg_normal, lfan_pivot->f->no); + + if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) { + break; + } + lfan_pivot = lfan_pivot_next; + } + normalize_v3(avg_normal); + while ((l = BLI_SMALLSTACK_POP(loops))) { + const int l_index = BM_elem_index_get(l); + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], avg_normal, clnors); + } + } + } + } while ((l_curr = l_curr->next) != l_first); + } +} + +static int loop_normals_split_merge(bContext *C, const bool do_merge) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMEdge *e; + BMIter eiter; + + BKE_editmesh_lnorspace_update(em); + + BMLoopNorEditDataArray *lnors_ed_arr = do_merge ? BM_loop_normal_editdata_array_init(bm) : NULL; + + mesh_set_smooth_faces(em, do_merge); + + BM_ITER_MESH(e, &eiter, bm, BM_EDGES_OF_MESH) { + if (BM_elem_flag_test(e, BM_ELEM_SELECT)) { + BM_elem_flag_set(e, BM_ELEM_SMOOTH, do_merge); + } + } + if (do_merge == 0) { + Mesh *me = obedit->data; + me->drawflag |= ME_DRAWSHARP; + } + + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + BKE_editmesh_lnorspace_update(em); + + if (do_merge) { + loop_normal_merge(C, lnors_ed_arr); + } + else { + loop_normla_split(C); + } + + if (lnors_ed_arr) { + BM_loop_normal_editdata_array_free(lnors_ed_arr); + } + + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +static int edbm_merge_loop_normals_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return loop_normals_split_merge(C, true); +} + +void MESH_OT_merge_loop_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Merge Loop Normals"; + ot->description = "Merge loop normals of selected vertices"; + ot->idname = "MESH_OT_merge_loop_normals"; + + /* api callbacks */ + ot->exec = edbm_merge_loop_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +static int edbm_split_loop_normals_exec(bContext *C, wmOperator *UNUSED(op)) +{ + return loop_normals_split_merge(C, false); +} + +void MESH_OT_split_loop_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Split Loop Normals"; + ot->description = "Split loop normals of selected vertices"; + ot->idname = "MESH_OT_split_loop_normals"; + + /* api callbacks */ + ot->exec = edbm_split_loop_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + +/********************** Average Loop Normals **********************/ + +enum { + EDBM_CLNOR_AVERAGE_LOOP = 1, + EDBM_CLNOR_AVERAGE_FACE_AREA = 2, + EDBM_CLNOR_AVERAGE_ANGLE = 3, +}; + +static EnumPropertyItem average_method_items[] = { + {EDBM_CLNOR_AVERAGE_LOOP, "CUSTOM_NORMAL", 0, "Custom Normal", "Take Average of vert Normals"}, + {EDBM_CLNOR_AVERAGE_FACE_AREA, "FACE_AREA", 0, "Face Area", "Set all vert normals by Face Area"}, + {EDBM_CLNOR_AVERAGE_ANGLE, "CORNER_ANGLE", 0, "Corner Angle", "Set all vert normals by Corner Angle"}, + {0, NULL, 0, NULL, NULL} +}; + +static int edbm_average_loop_normals_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMLoop *l, *l_curr, *l_first; + BMIter fiter; + + bm->spacearr_dirty |= BM_SPACEARR_DIRTY_ALL; + BKE_editmesh_lnorspace_update(em); + + const int average_type = RNA_enum_get(op->ptr, "average_type"); + const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + const float absweight = (float) RNA_int_get(op->ptr, "weight"); + const float threshold = RNA_float_get(op->ptr, "threshold"); + + float weight = absweight / 50.0f; + if (absweight == 100.0f) { + weight = (float)SHRT_MAX; + } + else if (absweight == 1.0f) { + weight = 1 / (float)SHRT_MAX; + } + else if ((weight - 1) * 25 > 1) { + weight = (weight - 1) * 25; + } + + custom_loops_tag(bm, true); + + Heap *loop_weight = BLI_heap_new(); + + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + l_curr = l_first = BM_FACE_FIRST_LOOP(f); + do { + if (BM_elem_flag_test(l_curr->v, BM_ELEM_SELECT) && (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) || + (!BM_elem_flag_test(l_curr, BM_ELEM_TAG) && BM_loop_check_cyclic_smooth_fan(l_curr)))) + { + if (!BM_elem_flag_test(l_curr->e, BM_ELEM_TAG) && !BM_elem_flag_test(l_curr->prev->e, BM_ELEM_TAG)) { + const int loop_index = BM_elem_index_get(l_curr); + short *clnors = BM_ELEM_CD_GET_VOID_P(l_curr, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], f->no, clnors); + } + else { + BMVert *v_pivot = l_curr->v; + BMEdge *e_next; + const BMEdge *e_org = l_curr->e; + BMLoop *lfan_pivot, *lfan_pivot_next; + + lfan_pivot = l_curr; + e_next = lfan_pivot->e; + + while (true) { + lfan_pivot_next = BM_vert_step_fan_loop(lfan_pivot, &e_next); + if (lfan_pivot_next) { + BLI_assert(lfan_pivot_next->v == v_pivot); + } + else { + e_next = (lfan_pivot->e == e_next) ? lfan_pivot->prev->e : lfan_pivot->e; + } + + float val = 1.0f; + if (average_type == EDBM_CLNOR_AVERAGE_FACE_AREA) { + val = 1.0f / BM_face_calc_area(lfan_pivot->f); + } + else if (average_type == EDBM_CLNOR_AVERAGE_ANGLE) { + val = 1.0f / BM_loop_calc_face_angle(lfan_pivot); + } + + BLI_heap_insert(loop_weight, val, lfan_pivot); + + if (!BM_elem_flag_test(e_next, BM_ELEM_TAG) || (e_next == e_org)) { + break; + } + lfan_pivot = lfan_pivot_next; + } + + BLI_SMALLSTACK_DECLARE(loops, BMLoop *); + float wnor[3], avg_normal[3] = { 0.0f }, count = 0; + float val = BLI_heap_node_value(BLI_heap_top(loop_weight)); + + while (!BLI_heap_is_empty(loop_weight)) { + const float cur_val = BLI_heap_node_value(BLI_heap_top(loop_weight)); + if (!compare_ff(val, cur_val, threshold)) { + count++; + val = cur_val; + } + l = BLI_heap_pop_min(loop_weight); + BLI_SMALLSTACK_PUSH(loops, l); + + const float n_weight = pow(weight, count); + + if (average_type == EDBM_CLNOR_AVERAGE_LOOP) { + const int l_index = BM_elem_index_get(l); + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[l_index], clnors, wnor); + } + else { + copy_v3_v3(wnor, l->f->no); + } + mul_v3_fl(wnor, (1.0f / cur_val) * (1.0f / n_weight)); + add_v3_v3(avg_normal, wnor); + } + + normalize_v3(avg_normal); + + while ((l = BLI_SMALLSTACK_POP(loops))) { + const int l_index = BM_elem_index_get(l); + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[l_index], avg_normal, clnors); + } + } + } + } while ((l_curr = l_curr->next) != l_first); + } + + BLI_heap_free(loop_weight, NULL); + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +static bool average_loop_normals_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + const int average_type = RNA_enum_get(ptr, "average_type"); + + /* Only show weight/threshold options in loop average type. */ + if (STREQ(prop_id, "weight")) { + return (average_type == EDBM_CLNOR_AVERAGE_LOOP); + } + else if (STREQ(prop_id, "threshold")) { + return (average_type == EDBM_CLNOR_AVERAGE_LOOP); + } + + /* Else, show it! */ + return true; +} + +static void edbm_average_loop_normals_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, average_loop_normals_draw_check_prop, '\0'); +} + +void MESH_OT_average_loop_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Average"; + ot->description = "Average loop normals of selected vertices"; + ot->idname = "MESH_OT_average_loop_normals"; + + /* api callbacks */ + ot->exec = edbm_average_loop_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = edbm_average_loop_normals_ui; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "average_type", average_method_items, EDBM_CLNOR_AVERAGE_LOOP, "Type", "Averaging method"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); + + RNA_def_int(ot->srna, "weight", 50, 1, 100, "Weight", "Weight applied per face", 1, 100); + + RNA_def_float(ot->srna, "threshold", 0.01f, 0, 10, "Threshold", "Threshold value for different weights to be considered equal", 0, 5); +} + +/********************** Custom Normal Interface Tools **********************/ + +enum { + EDBM_CLNOR_TOOLS_COPY = 1, + EDBM_CLNOR_TOOLS_PASTE = 2, + EDBM_CLNOR_TOOLS_MULTIPLY = 3, + EDBM_CLNOR_TOOLS_ADD = 4, + EDBM_CLNOR_TOOLS_RESET = 5, +}; + +static EnumPropertyItem normal_vector_tool_items[] = { + {EDBM_CLNOR_TOOLS_COPY, "COPY", 0, "Copy Normal", "Copy normal to buffer"}, + {EDBM_CLNOR_TOOLS_PASTE, "PASTE", 0, "Paste Normal", "Paste normal from buffer"}, + {EDBM_CLNOR_TOOLS_ADD, "ADD", 0, "Add Normal", "Add normal vector with selection"}, + {EDBM_CLNOR_TOOLS_MULTIPLY, "MULTIPLY", 0, "Multiply Normal", "Multiply normal vector with selection"}, + {EDBM_CLNOR_TOOLS_RESET, "RESET", 0, "Reset Normal", "Reset buffer and/or normal of selected element"}, + {0, NULL, 0, NULL, NULL} +}; + +static int edbm_custom_normal_tools_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + Scene *scene = CTX_data_scene(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + + const int mode = RNA_enum_get(op->ptr, "mode"); + const bool absolute = RNA_boolean_get(op->ptr, "absolute"); + + BKE_editmesh_lnorspace_update(em); + BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm); + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + + float *normal_vector = scene->toolsettings->normal_vector; + + switch (mode) { + case EDBM_CLNOR_TOOLS_COPY: + if (bm->totfacesel != 1 && lnors_ed_arr->totloop != 1 && bm->totvertsel != 1) { + BKE_report(op->reports, RPT_ERROR, "Can only copy Split normal, Averaged vertex normal or Face normal"); + BM_loop_normal_editdata_array_free(lnors_ed_arr); + return OPERATOR_CANCELLED; + } + bool join = true; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (!compare_v3v3(lnors_ed_arr->lnor_editdata->nloc, lnor_ed->nloc, 1e-4f)) { + join = false; + } + } + if (lnors_ed_arr->totloop == 1) { + copy_v3_v3(scene->toolsettings->normal_vector, lnors_ed_arr->lnor_editdata->nloc); + } + else if (bm->totfacesel == 1) { + BMFace *f; + BMIter fiter; + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + copy_v3_v3(scene->toolsettings->normal_vector, f->no); + } + } + } + else if (join) { + copy_v3_v3(scene->toolsettings->normal_vector, lnors_ed_arr->lnor_editdata->nloc); + } + break; + + case EDBM_CLNOR_TOOLS_PASTE: + if (!absolute) { + normalize_v3(normal_vector); + } + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + if (absolute) { + float abs_normal[3]; + copy_v3_v3(abs_normal, lnor_ed->loc); + negate_v3(abs_normal); + add_v3_v3(abs_normal, normal_vector); + normalize_v3(abs_normal); + + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], abs_normal, lnor_ed->clnors_data); + } + else { + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], normal_vector, lnor_ed->clnors_data); + } + } + break; + + case EDBM_CLNOR_TOOLS_MULTIPLY: + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + mul_v3_v3(lnor_ed->nloc, normal_vector); + normalize_v3(lnor_ed->nloc); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + break; + + case EDBM_CLNOR_TOOLS_ADD: + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + add_v3_v3(lnor_ed->nloc, normal_vector); + normalize_v3(lnor_ed->nloc); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + break; + + case EDBM_CLNOR_TOOLS_RESET: + zero_v3(normal_vector); + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + zero_v3(lnor_ed->nloc); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->nloc, lnor_ed->clnors_data); + } + break; + + default: + BLI_assert(0); + break; + } + + BM_loop_normal_editdata_array_free(lnors_ed_arr); + + EDBM_update_generic(em, true, false); + return OPERATOR_FINISHED; +} + +static bool custom_normal_tools_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + const int mode = RNA_enum_get(ptr, "mode"); + + /* Only show absolute option in paste mode. */ + if (STREQ(prop_id, "absolute")) { + return (mode == EDBM_CLNOR_TOOLS_PASTE); + } + + /* Else, show it! */ + return true; +} + +static void edbm_custom_normal_tools_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, custom_normal_tools_draw_check_prop, '\0'); +} + +void MESH_OT_custom_normal_tools(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Custom Normal Vector tools"; + ot->description = "Normal tools using Normal Vector of Interface"; + ot->idname = "MESH_OT_custom_normal_tools"; + + /* api callbacks */ + ot->exec = edbm_custom_normal_tools_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = edbm_custom_normal_tools_ui; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_enum(ot->srna, "mode", normal_vector_tool_items, EDBM_CLNOR_TOOLS_COPY, + "Mode", "Mode of tools taking input from Interface"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); + + RNA_def_boolean(ot->srna, "absolute", false, "Absolute Coordinates", "Copy Absolute coordinates or Normal vector"); +} + +static int edbm_set_normals_from_faces_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMVert *v; + BMEdge *e; + BMLoop *l; + BMIter fiter, viter, eiter, liter; + + const bool keep_sharp = RNA_boolean_get(op->ptr, "keep_sharp"); + + BKE_editmesh_lnorspace_update(em); + + float(*vnors)[3] = MEM_callocN(sizeof(*vnors) * bm->totvert, __func__); + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + BM_ITER_ELEM(v, &viter, f, BM_VERTS_OF_FACE) { + const int v_index = BM_elem_index_get(v); + add_v3_v3(vnors[v_index], f->no); + } + } + } + for (int i = 0; i < bm->totvert; i++) { + if (!is_zero_v3(vnors[i])) { + normalize_v3(vnors[i]); + } + } + + BLI_bitmap *loop_set = BLI_BITMAP_NEW(bm->totloop, __func__); + const int cd_clnors_offset = CustomData_get_offset(&bm->ldata, CD_CUSTOMLOOPNORMAL); + + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + BM_ITER_ELEM(e, &eiter, f, BM_EDGES_OF_FACE) { + if (!keep_sharp || (BM_elem_flag_test(e, BM_ELEM_SMOOTH) && BM_elem_flag_test(e, BM_ELEM_SELECT))) { + BM_ITER_ELEM(v, &viter, e, BM_VERTS_OF_EDGE) { + l = BM_face_vert_share_loop(f, v); + const int loop_index = BM_elem_index_get(l); + const int v_index = BM_elem_index_get(l->v); + BLI_assert(l->f == f); + BLI_assert(l->v == v); + if (!is_zero_v3(vnors[v_index])) { + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], vnors[v_index], clnors); + + if (bm->lnor_spacearr->lspacearr[loop_index]->flags & MLNOR_SPACE_IS_SINGLE) { + BLI_BITMAP_ENABLE(loop_set, loop_index); + } + else { + LinkNode *loops = bm->lnor_spacearr->lspacearr[loop_index]->loops; + for (; loops; loops = loops->next) { + BLI_BITMAP_ENABLE(loop_set, BM_elem_index_get((BMLoop *)loops->link)); + } + } + } + } + } + } + } + + int v_index; + BM_ITER_MESH_INDEX(v, &viter, bm, BM_VERTS_OF_MESH, v_index) { + BM_ITER_ELEM(l, &liter, v, BM_LOOPS_OF_VERT) { + if (BLI_BITMAP_TEST(loop_set, BM_elem_index_get(l))) { + const int loop_index = BM_elem_index_get(l); + short *clnors = BM_ELEM_CD_GET_VOID_P(l, cd_clnors_offset); + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[loop_index], vnors[v_index], clnors); + } + } + } + + MEM_freeN(loop_set); + MEM_freeN(vnors); + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Set Normals from faces"; + ot->description = "Set the custom vertex normals from the selected faces ones"; + ot->idname = "MESH_OT_set_normals_from_faces"; + + /* api callbacks */ + ot->exec = edbm_set_normals_from_faces_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_boolean(ot->srna, "keep_sharp", 0, "Keep Sharp Edges", "Do not set sharp edges to face"); +} + +static int edbm_smoothen_normals_exec(bContext *C, wmOperator *op) +{ + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMLoop *l; + BMIter fiter, liter; + + BKE_editmesh_lnorspace_update(em); + BMLoopNorEditDataArray *lnors_ed_arr = BM_loop_normal_editdata_array_init(bm); + + float(*smooth_normal)[3] = MEM_callocN(sizeof(*smooth_normal) * lnors_ed_arr->totloop, __func__); + + BMLoopNorEditData *lnor_ed = lnors_ed_arr->lnor_editdata; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + l = lnor_ed->loop; + float loop_normal[3]; + + BM_ITER_ELEM(f, &fiter, l->v, BM_FACES_OF_VERT) { + BMLoop *l_other; + BM_ITER_ELEM(l_other, &liter, f, BM_LOOPS_OF_FACE) { + const int index_other = BM_elem_index_get(l_other); + short *clnors = BM_ELEM_CD_GET_VOID_P(l_other, lnors_ed_arr->cd_custom_normal_offset); + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[index_other], clnors, loop_normal); + add_v3_v3(smooth_normal[i], loop_normal); + } + } + normalize_v3(smooth_normal[i]); + } + + const float factor = RNA_float_get(op->ptr, "factor"); + + lnor_ed = lnors_ed_arr->lnor_editdata; + for (int i = 0; i < lnors_ed_arr->totloop; i++, lnor_ed++) { + float current_normal[3]; + + BKE_lnor_space_custom_data_to_normal(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], lnor_ed->clnors_data, current_normal); + + mul_v3_fl(current_normal, 1.0f - factor); + mul_v3_fl(smooth_normal[i], factor); + add_v3_v3(current_normal, smooth_normal[i]); + normalize_v3(current_normal); + + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[lnor_ed->loop_index], current_normal, lnor_ed->clnors_data); + } + + BM_loop_normal_editdata_array_free(lnors_ed_arr); + MEM_freeN(smooth_normal); + + EDBM_update_generic(em, true, false); + + return OPERATOR_FINISHED; +} + +void MESH_OT_smoothen_custom_normals(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Smoothen Custom Normal"; + ot->description = "Smoothen custom normal based on adjacent vertex normals"; + ot->idname = "MESH_OT_smoothen_custom_normals"; + + /* api callbacks */ + ot->exec = edbm_smoothen_normals_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + RNA_def_float(ot->srna, "factor", 0.5f, 0.0f, 1.0f, "Factor", + "Specifies weight of smooth vs original normal", 0.0f, 1.0f); +} + +/********************** Weighted Normal Modifier Face Strength **********************/ + +static int edbm_mod_weighted_strength_exec(bContext *C, wmOperator *op) +{ + Scene *scene = CTX_data_scene(C); + Object *obedit = CTX_data_edit_object(C); + BMEditMesh *em = BKE_editmesh_from_object(obedit); + BMesh *bm = em->bm; + BMFace *f; + BMIter fiter; + + BM_select_history_clear(bm); + + int cd_prop_int_offset = CustomData_get_offset(&bm->pdata, CD_PROP_INT); + if (cd_prop_int_offset == -1) { + BM_data_layer_add(bm, &bm->pdata, CD_PROP_INT); + cd_prop_int_offset = CustomData_get_offset(&bm->pdata, CD_PROP_INT); + } + + const int face_strength = scene->toolsettings->face_strength; + const bool set = RNA_boolean_get(op->ptr, "set"); + BM_mesh_elem_index_ensure(bm, BM_FACE); + + if (set) { + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + if (BM_elem_flag_test(f, BM_ELEM_SELECT)) { + int *strength = BM_ELEM_CD_GET_VOID_P(f, cd_prop_int_offset); + *strength = face_strength; + } + } + } + else { + BM_ITER_MESH(f, &fiter, bm, BM_FACES_OF_MESH) { + int *strength = BM_ELEM_CD_GET_VOID_P(f, cd_prop_int_offset); + if (*strength == face_strength) { + BM_face_select_set(bm, f, true); + BM_select_history_store(bm, f); + } + else { + BM_face_select_set(bm, f, false); + } + } + } + + EDBM_update_generic(em, false, false); + return OPERATOR_FINISHED; +} + +void MESH_OT_mod_weighted_strength(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Face Strength"; + ot->description = "Set/Get strength of face (used in Weighted Normal modifier)"; + ot->idname = "MESH_OT_mod_weighted_strength"; + + /* api callbacks */ + ot->exec = edbm_mod_weighted_strength_exec; + ot->poll = ED_operator_editmesh_auto_smooth; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; + + ot->prop = RNA_def_boolean(ot->srna, "set", 0, "Set value", "Set Value of faces"); + RNA_def_property_flag(ot->prop, PROP_HIDDEN); +} diff --git a/source/blender/editors/mesh/editmesh_undo.c b/source/blender/editors/mesh/editmesh_undo.c index 11667ed5710..ed9b26ba5b1 100644 --- a/source/blender/editors/mesh/editmesh_undo.c +++ b/source/blender/editors/mesh/editmesh_undo.c @@ -583,6 +583,8 @@ static void undoMesh_to_editbtMesh(void *um_v, void *em_v, void *obdata) bm->selectmode = um->selectmode; em->ob = ob; + bm->spacearr_dirty = BM_SPACEARR_DIRTY_ALL; + /* T35170: Restore the active key on the RealMesh. Otherwise 'fake' offset propagation happens * if the active is a basis for any other. */ if (key && (key->type == KEY_RELATIVE)) { diff --git a/source/blender/editors/mesh/editmesh_utils.c b/source/blender/editors/mesh/editmesh_utils.c index c4440fa190a..1c6d75bb928 100644 --- a/source/blender/editors/mesh/editmesh_utils.c +++ b/source/blender/editors/mesh/editmesh_utils.c @@ -1285,7 +1285,10 @@ void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_d /* in debug mode double check we didn't need to recalculate */ BLI_assert(BM_mesh_elem_table_check(em->bm) == true); } - + if (em->bm->spacearr_dirty & BM_SPACEARR_BMO_SET) { + BM_lnorspace_invalidate(em->bm, false); + em->bm->spacearr_dirty &= ~BM_SPACEARR_BMO_SET; + } /* don't keep stale derivedMesh data around, see: [#38872] */ BKE_editmesh_free_derivedmesh(em); diff --git a/source/blender/editors/mesh/mesh_intern.h b/source/blender/editors/mesh/mesh_intern.h index 300b21a052d..a5043744f13 100644 --- a/source/blender/editors/mesh/mesh_intern.h +++ b/source/blender/editors/mesh/mesh_intern.h @@ -229,6 +229,14 @@ void MESH_OT_duplicate(struct wmOperatorType *ot); void MESH_OT_merge(struct wmOperatorType *ot); void MESH_OT_remove_doubles(struct wmOperatorType *ot); void MESH_OT_poke(struct wmOperatorType *ot); +void MESH_OT_point_normals(struct wmOperatorType *ot); +void MESH_OT_merge_loop_normals(struct wmOperatorType *ot); +void MESH_OT_split_loop_normals(struct wmOperatorType *ot); +void MESH_OT_custom_normal_tools(struct wmOperatorType *ot); +void MESH_OT_set_normals_from_faces(struct wmOperatorType *ot); +void MESH_OT_average_loop_normals(struct wmOperatorType *ot); +void MESH_OT_smoothen_custom_normals(struct wmOperatorType *ot); +void MESH_OT_mod_weighted_strength(struct wmOperatorType *ot); #ifdef WITH_FREESTYLE void MESH_OT_mark_freestyle_edge(struct wmOperatorType *ot); diff --git a/source/blender/editors/mesh/mesh_ops.c b/source/blender/editors/mesh/mesh_ops.c index 697a92f36d1..6843a761133 100644 --- a/source/blender/editors/mesh/mesh_ops.c +++ b/source/blender/editors/mesh/mesh_ops.c @@ -192,6 +192,15 @@ void ED_operatortypes_mesh(void) WM_operatortype_append(MESH_OT_symmetrize); WM_operatortype_append(MESH_OT_symmetry_snap); + WM_operatortype_append(MESH_OT_point_normals); + WM_operatortype_append(MESH_OT_merge_loop_normals); + WM_operatortype_append(MESH_OT_split_loop_normals); + WM_operatortype_append(MESH_OT_custom_normal_tools); + WM_operatortype_append(MESH_OT_set_normals_from_faces); + WM_operatortype_append(MESH_OT_average_loop_normals); + WM_operatortype_append(MESH_OT_smoothen_custom_normals); + WM_operatortype_append(MESH_OT_mod_weighted_strength); + #ifdef WITH_GAMEENGINE WM_operatortype_append(MESH_OT_navmesh_make); WM_operatortype_append(MESH_OT_navmesh_face_copy); @@ -423,6 +432,8 @@ void ED_keymap_mesh(wmKeyConfig *keyconf) WM_keymap_add_item(keymap, "MESH_OT_split", YKEY, KM_PRESS, 0, 0); WM_keymap_add_item(keymap, "MESH_OT_vert_connect_path", JKEY, KM_PRESS, 0, 0); + WM_keymap_add_item(keymap, "MESH_OT_point_normals", LKEY, KM_PRESS, KM_ALT, 0); + /* Vertex Slide */ WM_keymap_add_item(keymap, "TRANSFORM_OT_vert_slide", VKEY, KM_PRESS, KM_SHIFT, 0); /* use KM_CLICK because same key is used for tweaks */ diff --git a/source/blender/editors/screen/screen_ops.c b/source/blender/editors/screen/screen_ops.c index 2e5f93ff521..d9746c6cab8 100644 --- a/source/blender/editors/screen/screen_ops.c +++ b/source/blender/editors/screen/screen_ops.c @@ -46,6 +46,7 @@ #include "DNA_gpencil_types.h" #include "DNA_scene_types.h" #include "DNA_meta_types.h" +#include "DNA_mesh_types.h" #include "DNA_mask_types.h" #include "DNA_node_types.h" #include "DNA_userdef_types.h" @@ -352,6 +353,15 @@ int ED_operator_editmesh_region_view3d(bContext *C) return 0; } +int ED_operator_editmesh_auto_smooth(bContext *C) +{ + Object *obedit = CTX_data_edit_object(C); + if (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH)) { + return NULL != BKE_editmesh_from_object(obedit); + } + return 0; +} + int ED_operator_editarmature(bContext *C) { Object *obedit = CTX_data_edit_object(C); diff --git a/source/blender/editors/space_outliner/outliner_draw.c b/source/blender/editors/space_outliner/outliner_draw.c index d8d6b09ac6b..10ffa8eb629 100644 --- a/source/blender/editors/space_outliner/outliner_draw.c +++ b/source/blender/editors/space_outliner/outliner_draw.c @@ -1181,6 +1181,7 @@ static void tselem_draw_icon(uiBlock *block, int xmax, float x, float y, TreeSto case eModifierType_DataTransfer: UI_icon_draw(x, y, ICON_MOD_DATA_TRANSFER); break; case eModifierType_NormalEdit: + case eModifierType_WeightedNormal: UI_icon_draw(x, y, ICON_MOD_NORMALEDIT); break; /* Default */ case eModifierType_None: diff --git a/source/blender/editors/transform/transform.c b/source/blender/editors/transform/transform.c index 6ca9485599c..95a78e818f0 100644 --- a/source/blender/editors/transform/transform.c +++ b/source/blender/editors/transform/transform.c @@ -41,6 +41,7 @@ #include "DNA_armature_types.h" #include "DNA_constraint_types.h" #include "DNA_mask_types.h" +#include "DNA_mesh_types.h" #include "DNA_movieclip_types.h" #include "DNA_scene_types.h" /* PET modes */ @@ -62,6 +63,7 @@ #include "BKE_particle.h" #include "BKE_unit.h" #include "BKE_mask.h" +#include "BKE_mesh.h" #include "BKE_report.h" #include "BIF_gl.h" @@ -86,6 +88,7 @@ #include "UI_resources.h" #include "RNA_access.h" +#include "RNA_define.h" #include "BLF_api.h" #include "BLT_translation.h" @@ -106,6 +109,7 @@ static void postInputRotation(TransInfo *t, float values[3]); static void ElementRotation(TransInfo *t, TransData *td, float mat[3][3], const short around); static void initSnapSpatial(TransInfo *t, float r_snap[3]); +static void StoreCustomlnorValue(TransInfo *t, BMesh *bm); /* Transform Callbacks */ static void initBend(TransInfo *t); @@ -131,6 +135,9 @@ static void applyToSphere(TransInfo *t, const int mval[2]); static void initRotation(TransInfo *t); static void applyRotation(TransInfo *t, const int mval[2]); +static void initNormalRotation(TransInfo *t); +static void applyNormalRotation(TransInfo *t, const int mval[2]); + static void initShrinkFatten(TransInfo *t); static void applyShrinkFatten(TransInfo *t, const int mval[2]); @@ -1465,6 +1472,20 @@ int transformEvent(TransInfo *t, const wmEvent *event) handled = true; } break; + case NKEY: + if (ELEM(t->mode, TFM_ROTATION)) { + if (t->obedit && t->obedit->type == OB_MESH) { + if (((Mesh *)(t->obedit->data))->flag & ME_AUTOSMOOTH) { + restoreTransObjects(t); + resetTransModal(t); + resetTransRestrictions(t); + initNormalRotation(t); + t->redraw = TREDRAW_HARD; + handled = true; + } + } + } + break; default: break; } @@ -2313,6 +2334,9 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve case TFM_SEQ_SLIDE: initSeqSlide(t); break; + case TFM_NORMAL_ROTATION: + initNormalRotation(t); + break; } if (t->state == TRANS_CANCEL) { @@ -2369,6 +2393,32 @@ bool initTransform(bContext *C, TransInfo *t, wmOperator *op, const wmEvent *eve t->flag |= T_AUTOVALUES; } + if ((prop = RNA_struct_find_property(op->ptr, "preserve_clnor"))) { + if (t->obedit && t->obedit->type == OB_MESH && (((Mesh *)(t->obedit->data))->flag & ME_AUTOSMOOTH)) { + BMEditMesh *em = BKE_editmesh_from_object(t->obedit); + bool all_select = false; + + /* Currently only used for 3 most frequent transform ops, can include more ops. */ + if (ELEM(t->mode, TFM_TRANSLATION, TFM_ROTATION, TFM_RESIZE)) { + if (em->bm->totvertsel == em->bm->totvert) { + /* No need to invalidate if whole mesh is selected. */ + all_select = true; + } + } + if (t->flag & T_MODAL) { + RNA_property_boolean_set(op->ptr, prop, false); + } + if (!all_select) { + const bool preserve_clnor = RNA_property_boolean_get(op->ptr, prop); + if (preserve_clnor) { + BKE_editmesh_lnorspace_update(em); + t->flag |= T_CLNOR_REBUILD; + } + BM_lnorspace_invalidate(em->bm, true); + } + } + } + t->context = NULL; return 1; @@ -2429,6 +2479,10 @@ int transformEnd(bContext *C, TransInfo *t) restoreTransObjects(t); // calls recalcData() } else { + if (t->flag & T_CLNOR_REBUILD) { + BMEditMesh *em = BKE_editmesh_from_object(t->obedit); + BM_lnorspace_rebuild(em->bm, true); + } exit_code = OPERATOR_FINISHED; } @@ -3724,6 +3778,28 @@ static void initRotation(TransInfo *t) copy_v3_v3(t->axis_orig, t->axis); } +/* Used by Transform Rotation and Transform Normal Rotation */ +static void headerRotation(TransInfo *t, char str[UI_MAX_DRAW_STR], float final) +{ + size_t ofs = 0; + + if (hasNumInput(&t->num)) { + char c[NUM_STR_REP_LEN]; + + outputNumInput(&(t->num), c, &t->scene->unit); + + ofs += BLI_snprintf(str + ofs, UI_MAX_DRAW_STR - ofs, IFACE_("Rot: %s %s %s"), &c[0], t->con.text, t->proptext); + } + else { + ofs += BLI_snprintf(str + ofs, UI_MAX_DRAW_STR - ofs, IFACE_("Rot: %.2f%s %s"), + RAD2DEGF(final), t->con.text, t->proptext); + } + + if (t->flag & T_PROP_EDIT_ALL) { + ofs += BLI_snprintf(str + ofs, UI_MAX_DRAW_STR - ofs, IFACE_(" Proportional size: %.2f"), t->prop_size); + } +} + /** * Applies values of rotation to `td->loc` and `td->ext->quat` * based on a rotation matrix (mat) and a pivot (center). @@ -3989,7 +4065,6 @@ static void applyRotationValue(TransInfo *t, float angle, float axis[3]) static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) { char str[UI_MAX_DRAW_STR]; - size_t ofs = 0; float final; @@ -4012,21 +4087,7 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2])) t->values[0] = final; - if (hasNumInput(&t->num)) { - char c[NUM_STR_REP_LEN]; - - outputNumInput(&(t->num), c, &t->scene->unit); - - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, IFACE_("Rot: %s %s %s"), &c[0], t->con.text, t->proptext); - } - else { - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, IFACE_("Rot: %.2f%s %s"), - RAD2DEGF(final), t->con.text, t->proptext); - } - - if (t->flag & T_PROP_EDIT_ALL) { - ofs += BLI_snprintf(str + ofs, sizeof(str) - ofs, IFACE_(" Proportional size: %.2f"), t->prop_size); - } + headerRotation(t, str, final); applyRotationValue(t, final, t->axis); @@ -4153,6 +4214,143 @@ static void applyTrackball(TransInfo *t, const int UNUSED(mval[2])) /* -------------------------------------------------------------------- */ +/* Transform (Normal Rotation) */ + +/** \name Transform Normal Rotation +* \{ */ + +static void StoreCustomlnorValue(TransInfo *t, BMesh *bm) +{ + float mtx[3][3], smtx[3][3]; + + BMLoopNorEditDataArray *ld = BM_loop_normal_editdata_array_init(bm); + + copy_m3_m4(mtx, t->obedit->obmat); + pseudoinverse_m3_m3(smtx, mtx, PSEUDOINVERSE_EPSILON); + + BMLoopNorEditData *tld = ld->lnor_editdata; + for (int i = 0; i < ld->totloop; i++, tld++) { + copy_m3_m3(tld->mtx, mtx); + copy_m3_m3(tld->smtx, smtx); + } + + t->custom.mode.data = ld; + t->custom.mode.free_cb = freeCustomNormalArray; +} + +void freeCustomNormalArray(TransInfo *t, TransCustomData *custom_data) +{ + BMLoopNorEditDataArray *ld = custom_data->data; + + if (t->state == TRANS_CANCEL) { + BMLoopNorEditData *tld = ld->lnor_editdata; + BMEditMesh *em = BKE_editmesh_from_object(t->obedit); + BMesh *bm = em->bm; + + for (int i = 0; i < ld->totloop; i++, tld++) { /* Restore custom loop normal on cancel */ + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[tld->loop_index], tld->niloc, tld->clnors_data); + } + } + + BM_loop_normal_editdata_array_free(ld); + + t->custom.mode.data = NULL; + t->custom.mode.free_cb = NULL; +} + +static void initNormalRotation(TransInfo *t) +{ + t->mode = TFM_NORMAL_ROTATION; + t->transform = applyNormalRotation; + + setInputPostFct(&t->mouse, postInputRotation); + initMouseInputMode(t, &t->mouse, INPUT_ANGLE); + + t->idx_max = 0; + t->num.idx_max = 0; + t->snap[0] = 0.0f; + t->snap[1] = DEG2RAD(5.0); + t->snap[2] = DEG2RAD(1.0); + + copy_v3_fl(t->num.val_inc, t->snap[2]); + t->num.unit_sys = t->scene->unit.system; + t->num.unit_use_radians = (t->scene->unit.system_rotation == USER_UNIT_ROT_RADIANS); + t->num.unit_type[0] = B_UNIT_ROTATION; + + BMEditMesh *em = BKE_editmesh_from_object(t->obedit); + BMesh *bm = em->bm; + + BKE_editmesh_lnorspace_update(em); + + StoreCustomlnorValue(t, bm); + + negate_v3_v3(t->axis, t->viewinv[2]); + normalize_v3(t->axis); + + copy_v3_v3(t->axis_orig, t->axis); +} + +/* Works by getting custom normal from clnor_data, transform, then store */ +static void applyNormalRotation(TransInfo *t, const int UNUSED(mval[2])) +{ + BMEditMesh *em = BKE_editmesh_from_object(t->obedit); + BMesh *bm = em->bm; + char str[UI_MAX_DRAW_STR]; + + if ((t->con.mode & CON_APPLY) && t->con.applyRot) { + t->con.applyRot(t, NULL, t->axis, NULL); + } + else { + /* reset axis if constraint is not set */ + copy_v3_v3(t->axis, t->axis_orig); + } + + BMLoopNorEditDataArray *ld = t->custom.mode.data; + BMLoopNorEditData *tld = ld->lnor_editdata; + + float axis[3]; + float mat[3][3]; + float angle = t->values[0]; + copy_v3_v3(axis, t->axis); + + snapGridIncrement(t, &angle); + + applySnapping(t, &angle); + + applyNumInput(&t->num, &angle); + + headerRotation(t, str, angle); + + axis_angle_normalized_to_mat3(mat, axis, angle); + + for (int i = 0; i < ld->totloop; i++, tld++) { + float center[3]; + float vec[3], totmat[3][3], smat[3][3]; + zero_v3(center); + + mul_m3_m3m3(totmat, mat, tld->mtx); + mul_m3_m3m3(smat, tld->smtx, totmat); + + sub_v3_v3v3(vec, tld->niloc, center); + mul_m3_v3(smat, vec); + + add_v3_v3v3(tld->nloc, vec, center); + + sub_v3_v3v3(vec, tld->nloc, tld->niloc); + add_v3_v3v3(tld->nloc, tld->niloc, vec); + + BKE_lnor_space_custom_normal_to_data(bm->lnor_spacearr->lspacearr[tld->loop_index], tld->nloc, tld->clnors_data); + } + + recalcData(t); + + ED_area_headerprint(t->sa, str); +} + +/** \} */ + + +/* -------------------------------------------------------------------- */ /* Transform (Translation) */ static void initSnapSpatial(TransInfo *t, float r_snap[3]) diff --git a/source/blender/editors/transform/transform.h b/source/blender/editors/transform/transform.h index 06a60456cdb..3cf0fda0a7a 100644 --- a/source/blender/editors/transform/transform.h +++ b/source/blender/editors/transform/transform.h @@ -536,6 +536,8 @@ typedef struct TransInfo { /** #TransInfo.center has been set, don't change it. */ #define T_OVERRIDE_CENTER (1 << 25) +#define T_CLNOR_REBUILD (1 << 26) + /* TransInfo->modifiers */ #define MOD_CONSTRAINT_SELECT 0x01 #define MOD_PRECISION 0x02 @@ -798,6 +800,8 @@ bool applyTransformOrientation(const struct bContext *C, float mat[3][3], char r int getTransformOrientation_ex(const struct bContext *C, float normal[3], float plane[3], const short around); int getTransformOrientation(const struct bContext *C, float normal[3], float plane[3]); +void freeCustomNormalArray(TransInfo *t, TransCustomData *custom_data); + void freeEdgeSlideTempFaces(EdgeSlideData *sld); void freeEdgeSlideVerts(TransInfo *t, TransCustomData *custom_data); void projectEdgeSlideData(TransInfo *t, bool is_final); diff --git a/source/blender/editors/transform/transform_generics.c b/source/blender/editors/transform/transform_generics.c index 277e01d1e2b..7578607195d 100644 --- a/source/blender/editors/transform/transform_generics.c +++ b/source/blender/editors/transform/transform_generics.c @@ -1072,6 +1072,9 @@ void resetTransModal(TransInfo *t) else if (t->mode == TFM_VERT_SLIDE) { freeVertSlideVerts(t, &t->custom.mode); } + else if (t->mode == TFM_NORMAL_ROTATION) { + freeCustomNormalArray(t, &t->custom.mode); + } } void resetTransRestrictions(TransInfo *t) diff --git a/source/blender/editors/transform/transform_ops.c b/source/blender/editors/transform/transform_ops.c index 46bd83b5d35..be465d12aad 100644 --- a/source/blender/editors/transform/transform_ops.c +++ b/source/blender/editors/transform/transform_ops.c @@ -81,6 +81,7 @@ static const char OP_VERT_SLIDE[] = "TRANSFORM_OT_vert_slide"; static const char OP_EDGE_CREASE[] = "TRANSFORM_OT_edge_crease"; static const char OP_EDGE_BWEIGHT[] = "TRANSFORM_OT_edge_bevelweight"; static const char OP_SEQ_SLIDE[] = "TRANSFORM_OT_seq_slide"; +static const char OP_NORMAL_ROTATION[] = "TRANSFORM_OT_rotate_normal"; static void TRANSFORM_OT_translate(struct wmOperatorType *ot); static void TRANSFORM_OT_rotate(struct wmOperatorType *ot); @@ -99,6 +100,7 @@ static void TRANSFORM_OT_vert_slide(struct wmOperatorType *ot); static void TRANSFORM_OT_edge_crease(struct wmOperatorType *ot); static void TRANSFORM_OT_edge_bevelweight(struct wmOperatorType *ot); static void TRANSFORM_OT_seq_slide(struct wmOperatorType *ot); +static void TRANSFORM_OT_rotate_normal(struct wmOperatorType *ot); static TransformModeItem transform_modes[] = { @@ -119,6 +121,7 @@ static TransformModeItem transform_modes[] = {OP_EDGE_CREASE, TFM_CREASE, TRANSFORM_OT_edge_crease}, {OP_EDGE_BWEIGHT, TFM_BWEIGHT, TRANSFORM_OT_edge_bevelweight}, {OP_SEQ_SLIDE, TFM_SEQ_SLIDE, TRANSFORM_OT_seq_slide}, + {OP_NORMAL_ROTATION, TFM_NORMAL_ROTATION, TRANSFORM_OT_rotate_normal}, {NULL, 0} }; @@ -495,6 +498,39 @@ static int transform_invoke(bContext *C, wmOperator *op, const wmEvent *event) } } +static bool transform_draw_check_prop(PointerRNA *ptr, PropertyRNA *prop) +{ + const char *prop_id = RNA_property_identifier(prop); + + /* Only show preserve_clnor option if requested (kinda hackish, we need to take that decision based on context... */ + if (STREQ(prop_id, "preserve_clnor")) { + return RNA_boolean_get(ptr, "show_preserve_clnor");; + } + + /* Else, show it! */ + return true; +} + +static void transform_ui(bContext *C, wmOperator *op) +{ + uiLayout *layout = op->layout; + wmWindowManager *wm = CTX_wm_manager(C); + PointerRNA ptr; + Object *obedit = CTX_data_edit_object(C); + + PropertyRNA *prop = RNA_struct_find_property(op->ptr, "show_preserve_clnor"); + if (prop) { + RNA_property_boolean_set( + op->ptr, prop, + (obedit && obedit->type == OB_MESH && (((Mesh *)(obedit->data))->flag & ME_AUTOSMOOTH))); + } + + RNA_pointer_create(&wm->id, op->type->srna, op->properties, &ptr); + + /* Main auto-draw call */ + uiDefAutoButsRNA(layout, &ptr, transform_draw_check_prop, '\0'); +} + void Transform_Properties(struct wmOperatorType *ot, int flags) { PropertyRNA *prop; @@ -580,6 +616,12 @@ void Transform_Properties(struct wmOperatorType *ot, int flags) prop = RNA_def_boolean(ot->srna, "use_accurate", 0, "Accurate", "Use accurate transformation"); RNA_def_property_flag(prop, PROP_HIDDEN); } + + if (flags & P_CLNOR_INVALIDATE) { + RNA_def_boolean(ot->srna, "preserve_clnor", false, "Preserve Normals", "Keep custom normals during transform"); + prop = RNA_def_boolean(ot->srna, "show_preserve_clnor", false, "Show Preserve Normals", ""); + RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE); + } } static void TRANSFORM_OT_translate(struct wmOperatorType *ot) @@ -596,10 +638,11 @@ static void TRANSFORM_OT_translate(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; RNA_def_float_vector_xyz(ot->srna, "value", 3, NULL, -FLT_MAX, FLT_MAX, "Vector", "", -FLT_MAX, FLT_MAX); - Transform_Properties(ot, P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_ALIGN_SNAP | P_OPTIONS | P_GPENCIL_EDIT); + Transform_Properties(ot, P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_ALIGN_SNAP | P_OPTIONS | P_GPENCIL_EDIT | P_CLNOR_INVALIDATE); } static void TRANSFORM_OT_resize(struct wmOperatorType *ot) @@ -616,12 +659,12 @@ static void TRANSFORM_OT_resize(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; RNA_def_float_vector(ot->srna, "value", 3, VecOne, -FLT_MAX, FLT_MAX, "Vector", "", -FLT_MAX, FLT_MAX); Transform_Properties( - ot, P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_GEO_SNAP | P_OPTIONS | P_GPENCIL_EDIT | P_CENTER); -} + ot, P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_GEO_SNAP | P_OPTIONS | P_GPENCIL_EDIT | P_CENTER | P_CLNOR_INVALIDATE);} static int skin_resize_poll(bContext *C) { @@ -647,6 +690,7 @@ static void TRANSFORM_OT_skin_resize(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = skin_resize_poll; + ot->ui = transform_ui; RNA_def_float_vector(ot->srna, "value", 3, VecOne, -FLT_MAX, FLT_MAX, "Vector", "", -FLT_MAX, FLT_MAX); @@ -667,12 +711,12 @@ static void TRANSFORM_OT_trackball(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; /* Maybe we could use float_vector_xyz here too? */ RNA_def_float_rotation(ot->srna, "value", 2, NULL, -FLT_MAX, FLT_MAX, "Angle", "", -FLT_MAX, FLT_MAX); - Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CENTER); -} + Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CENTER | P_CLNOR_INVALIDATE);} static void TRANSFORM_OT_rotate(struct wmOperatorType *ot) { @@ -688,12 +732,12 @@ static void TRANSFORM_OT_rotate(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; RNA_def_float_rotation(ot->srna, "value", 0, NULL, -FLT_MAX, FLT_MAX, "Angle", "", -M_PI * 2, M_PI * 2); Transform_Properties( - ot, P_AXIS | P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_GEO_SNAP | P_GPENCIL_EDIT | P_CENTER); -} + ot, P_AXIS | P_CONSTRAINT | P_PROPORTIONAL | P_MIRROR | P_GEO_SNAP | P_GPENCIL_EDIT | P_CENTER | P_CLNOR_INVALIDATE);} static void TRANSFORM_OT_tilt(struct wmOperatorType *ot) { @@ -712,6 +756,7 @@ static void TRANSFORM_OT_tilt(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_editcurve_3d; + ot->ui = transform_ui; RNA_def_float_rotation(ot->srna, "value", 0, NULL, -FLT_MAX, FLT_MAX, "Angle", "", -M_PI * 2, M_PI * 2); @@ -732,11 +777,11 @@ static void TRANSFORM_OT_bend(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_region_view3d_active; + ot->ui = transform_ui; RNA_def_float_rotation(ot->srna, "value", 1, NULL, -FLT_MAX, FLT_MAX, "Angle", "", -M_PI * 2, M_PI * 2); - Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CENTER); -} + Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CENTER | P_CLNOR_INVALIDATE);} static void TRANSFORM_OT_shear(struct wmOperatorType *ot) { @@ -752,10 +797,11 @@ static void TRANSFORM_OT_shear(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; RNA_def_float(ot->srna, "value", 0, -FLT_MAX, FLT_MAX, "Offset", "", -FLT_MAX, FLT_MAX); - Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT); + Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CLNOR_INVALIDATE); // XXX Shear axis? } @@ -773,11 +819,11 @@ static void TRANSFORM_OT_push_pull(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; RNA_def_float(ot->srna, "value", 0, -FLT_MAX, FLT_MAX, "Distance", "", -FLT_MAX, FLT_MAX); - Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_CENTER); -} + Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_CENTER | P_CLNOR_INVALIDATE);} static void TRANSFORM_OT_shrink_fatten(struct wmOperatorType *ot) { @@ -793,12 +839,13 @@ static void TRANSFORM_OT_shrink_fatten(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_editmesh; + ot->ui = transform_ui; RNA_def_float(ot->srna, "value", 0, -FLT_MAX, FLT_MAX, "Offset", "", -FLT_MAX, FLT_MAX); RNA_def_boolean(ot->srna, "use_even_offset", true, "Offset Even", "Scale the offset to give more even thickness"); - Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP); + Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_CLNOR_INVALIDATE); } static void TRANSFORM_OT_tosphere(struct wmOperatorType *ot) @@ -816,11 +863,11 @@ static void TRANSFORM_OT_tosphere(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; RNA_def_float_factor(ot->srna, "value", 0, 0, 1, "Factor", "", 0, 1); - Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CENTER); -} + Transform_Properties(ot, P_PROPORTIONAL | P_MIRROR | P_SNAP | P_GPENCIL_EDIT | P_CENTER | P_CLNOR_INVALIDATE);} static void TRANSFORM_OT_mirror(struct wmOperatorType *ot) { @@ -836,9 +883,9 @@ static void TRANSFORM_OT_mirror(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; - Transform_Properties(ot, P_CONSTRAINT | P_PROPORTIONAL | P_GPENCIL_EDIT | P_CENTER); -} + Transform_Properties(ot, P_CONSTRAINT | P_PROPORTIONAL | P_GPENCIL_EDIT | P_CENTER | P_CLNOR_INVALIDATE);} static void TRANSFORM_OT_edge_slide(struct wmOperatorType *ot) { @@ -856,6 +903,7 @@ static void TRANSFORM_OT_edge_slide(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_editmesh_region_view3d; + ot->ui = transform_ui; RNA_def_float_factor(ot->srna, "value", 0, -10.0f, 10.0f, "Factor", "", -1.0f, 1.0f); @@ -868,7 +916,7 @@ static void TRANSFORM_OT_edge_slide(struct wmOperatorType *ot) RNA_def_boolean(ot->srna, "use_clamp", true, "Clamp", "Clamp within the edge extents"); - Transform_Properties(ot, P_MIRROR | P_SNAP | P_CORRECT_UV); + Transform_Properties(ot, P_MIRROR | P_SNAP | P_CORRECT_UV | P_CLNOR_INVALIDATE); } static void TRANSFORM_OT_vert_slide(struct wmOperatorType *ot) @@ -885,6 +933,7 @@ static void TRANSFORM_OT_vert_slide(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_editmesh_region_view3d; + ot->ui = transform_ui; RNA_def_float_factor(ot->srna, "value", 0, -10.0f, 10.0f, "Factor", "", -1.0f, 1.0f); RNA_def_boolean(ot->srna, "use_even", false, "Even", @@ -894,7 +943,7 @@ static void TRANSFORM_OT_vert_slide(struct wmOperatorType *ot) RNA_def_boolean(ot->srna, "use_clamp", true, "Clamp", "Clamp within the edge extents"); - Transform_Properties(ot, P_MIRROR | P_SNAP | P_CORRECT_UV); + Transform_Properties(ot, P_MIRROR | P_SNAP | P_CORRECT_UV | P_CLNOR_INVALIDATE); } static void TRANSFORM_OT_edge_crease(struct wmOperatorType *ot) @@ -911,6 +960,7 @@ static void TRANSFORM_OT_edge_crease(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_editmesh; + ot->ui = transform_ui; RNA_def_float_factor(ot->srna, "value", 0, -1.0f, 1.0f, "Factor", "", -1.0f, 1.0f); @@ -952,6 +1002,7 @@ static void TRANSFORM_OT_edge_bevelweight(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_editmesh; + ot->ui = transform_ui; RNA_def_float_factor(ot->srna, "value", 0, -1.0f, 1.0f, "Factor", "", -1.0f, 1.0f); @@ -972,12 +1023,34 @@ static void TRANSFORM_OT_seq_slide(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_sequencer_active; + ot->ui = transform_ui; RNA_def_float_vector_xyz(ot->srna, "value", 2, NULL, -FLT_MAX, FLT_MAX, "Vector", "", -FLT_MAX, FLT_MAX); Transform_Properties(ot, P_SNAP); } +static void TRANSFORM_OT_rotate_normal(struct wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Normal Rotate"; + ot->description = "Rotate split normal of selected items"; + ot->idname = OP_NORMAL_ROTATION; + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING; + + /* api callbacks */ + ot->invoke = transform_invoke; + ot->exec = transform_exec; + ot->modal = transform_modal; + ot->cancel = transform_cancel; + ot->poll = ED_operator_editmesh_auto_smooth; + ot->ui = transform_ui; + + RNA_def_float_rotation(ot->srna, "value", 0, NULL, -FLT_MAX, FLT_MAX, "Angle", "", -M_PI * 2, M_PI * 2); + + Transform_Properties(ot, P_AXIS | P_CONSTRAINT | P_MIRROR); +} + static void TRANSFORM_OT_transform(struct wmOperatorType *ot) { PropertyRNA *prop; @@ -994,6 +1067,7 @@ static void TRANSFORM_OT_transform(struct wmOperatorType *ot) ot->modal = transform_modal; ot->cancel = transform_cancel; ot->poll = ED_operator_screenactive; + ot->ui = transform_ui; prop = RNA_def_enum(ot->srna, "mode", rna_enum_transform_mode_types, TFM_TRANSLATION, "Mode", ""); RNA_def_property_flag(prop, PROP_HIDDEN); diff --git a/source/blender/makesdna/DNA_modifier_types.h b/source/blender/makesdna/DNA_modifier_types.h index 766dd196562..6991d5edb6f 100644 --- a/source/blender/makesdna/DNA_modifier_types.h +++ b/source/blender/makesdna/DNA_modifier_types.h @@ -87,6 +87,7 @@ typedef enum ModifierType { eModifierType_CorrectiveSmooth = 51, eModifierType_MeshSequenceCache = 52, eModifierType_SurfaceDeform = 53, + eModifierType_WeightedNormal = 54, NUM_MODIFIER_TYPES } ModifierType; @@ -1619,6 +1620,29 @@ enum { MOD_SDEF_MODE_CENTROID = 2, }; +typedef struct WeightedNormalModifierData { + ModifierData modifier; + + char defgrp_name[64]; /* MAX_VGROUP_NAME */ + char mode, flag; + short weight; + float thresh; +} WeightedNormalModifierData; + +/* WeightedNormalModifierData.mode */ +enum { + MOD_WEIGHTEDNORMAL_MODE_FACE = 0, + MOD_WEIGHTEDNORMAL_MODE_ANGLE = 1, + MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE = 2, +}; + +/* WeightedNormalModifierData.flag */ +enum { + MOD_WEIGHTEDNORMAL_KEEP_SHARP = (1 << 0), + MOD_WEIGHTEDNORMAL_INVERT_VGROUP = (1 << 1), + MOD_WEIGHTEDNORMAL_FACE_INFLUENCE = (1 << 2), +}; + #define MOD_MESHSEQ_READ_ALL \ (MOD_MESHSEQ_READ_VERT | MOD_MESHSEQ_READ_POLY | MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR) diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 1cc5cbf8c42..6ebc06a6c33 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -1567,6 +1567,10 @@ typedef struct ToolSettings { struct CurvePaintSettings curve_paint_settings; struct MeshStatVis statvis; + + /* Normal Editing */ + float normal_vector[3]; + int face_strength; } ToolSettings; /* *************************************************************** */ @@ -2029,6 +2033,13 @@ enum { OB_DRAW_GROUPUSER_ALL = 2 }; +/* toolsettings->face_strength */ +enum { + FACE_STRENGTH_WEAK = 1, + FACE_STRENGTH_MEDIUM = 0, + FACE_STRENGTH_STRONG = 2, +}; + /* object_vgroup.c */ /* ToolSettings.vgroupsubset */ typedef enum eVGroupSelect { diff --git a/source/blender/makesrna/RNA_access.h b/source/blender/makesrna/RNA_access.h index e512aebfa71..7a582d2c944 100644 --- a/source/blender/makesrna/RNA_access.h +++ b/source/blender/makesrna/RNA_access.h @@ -697,6 +697,7 @@ extern StructRNA RNA_WaveModifier; extern StructRNA RNA_VertexWeightEditModifier; extern StructRNA RNA_VertexWeightMixModifier; extern StructRNA RNA_VertexWeightProximityModifier; +extern StructRNA RNA_WeightedNormalModifier; extern StructRNA RNA_Window; extern StructRNA RNA_WindowManager; extern StructRNA RNA_WipeSequence; diff --git a/source/blender/makesrna/intern/rna_modifier.c b/source/blender/makesrna/intern/rna_modifier.c index 2f92c47eed9..0cc5a33f0ca 100644 --- a/source/blender/makesrna/intern/rna_modifier.c +++ b/source/blender/makesrna/intern/rna_modifier.c @@ -68,6 +68,7 @@ const EnumPropertyItem rna_enum_object_modifier_type_items[] = { {eModifierType_MeshCache, "MESH_CACHE", ICON_MOD_MESHDEFORM, "Mesh Cache", ""}, {eModifierType_MeshSequenceCache, "MESH_SEQUENCE_CACHE", ICON_MOD_MESHDEFORM, "Mesh Sequence Cache", ""}, {eModifierType_NormalEdit, "NORMAL_EDIT", ICON_MOD_NORMALEDIT, "Normal Edit", ""}, + {eModifierType_WeightedNormal, "WEIGHTED_NORMAL", ICON_MOD_NORMALEDIT, "Weighted Normal", ""}, {eModifierType_UVProject, "UV_PROJECT", ICON_MOD_UVPROJECT, "UV Project", ""}, {eModifierType_UVWarp, "UV_WARP", ICON_MOD_UVPROJECT, "UV Warp", ""}, {eModifierType_WeightVGEdit, "VERTEX_WEIGHT_EDIT", ICON_MOD_VERTEX_WEIGHT, "Vertex Weight Edit", ""}, @@ -411,6 +412,8 @@ static StructRNA *rna_Modifier_refine(struct PointerRNA *ptr) return &RNA_MeshSequenceCacheModifier; case eModifierType_SurfaceDeform: return &RNA_SurfaceDeformModifier; + case eModifierType_WeightedNormal: + return &RNA_WeightedNormalModifier; /* Default */ case eModifierType_None: case eModifierType_ShapeKey: @@ -501,6 +504,7 @@ RNA_MOD_VGROUP_NAME_SET(WeightVGMix, defgrp_name_b); RNA_MOD_VGROUP_NAME_SET(WeightVGMix, mask_defgrp_name); RNA_MOD_VGROUP_NAME_SET(WeightVGProximity, defgrp_name); RNA_MOD_VGROUP_NAME_SET(WeightVGProximity, mask_defgrp_name); +RNA_MOD_VGROUP_NAME_SET(WeightedNormal, defgrp_name); RNA_MOD_VGROUP_NAME_SET(Wireframe, defgrp_name); static void rna_ExplodeModifier_vgroup_get(PointerRNA *ptr, char *value) @@ -4809,6 +4813,63 @@ static void rna_def_modifier_surfacedeform(BlenderRNA *brna) RNA_def_property_clear_flag(prop, PROP_EDITABLE); } +static void rna_def_modifier_weightednormal(BlenderRNA *brna) +{ + StructRNA *srna; + PropertyRNA *prop; + + static EnumPropertyItem prop_weighting_mode_items[] = { + {MOD_WEIGHTEDNORMAL_MODE_FACE, "FACE_AREA", 0, "Face Area", "Generate face area weighted normals"}, + {MOD_WEIGHTEDNORMAL_MODE_ANGLE, "CORNER_ANGLE", 0, "Corner Angle", "Generate corner angle weighted normals"}, + {MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE, "FACE_AREA_WITH_ANGLE", 0, "Face Area with Angle", + "Generated normals weighted by both Face Area and Angle"}, + {0, NULL, 0, NULL, NULL} + }; + + srna = RNA_def_struct(brna, "WeightedNormalModifier", "Modifier"); + RNA_def_struct_ui_text(srna, "WeightedNormal Modifier", ""); + RNA_def_struct_sdna(srna, "WeightedNormalModifierData"); + RNA_def_struct_ui_icon(srna, ICON_MOD_NORMALEDIT); + + prop = RNA_def_property(srna, "weight", PROP_INT, PROP_NONE); + RNA_def_property_range(prop, 1, 100); + RNA_def_property_ui_range(prop, 1, 100, 1, -1); + RNA_def_property_ui_text(prop, "Weight", "Weights given to Face Normal for each mode"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, prop_weighting_mode_items); + RNA_def_property_ui_text(prop, "Weighting Mode", "Weighted vertex normal mode to use"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "thresh", PROP_FLOAT, PROP_NONE); + RNA_def_property_range(prop, 0, 10); + RNA_def_property_ui_range(prop, 0, 10, 1, 2); + RNA_def_property_ui_text(prop, "Threshold", "Threshold value for different weights to be considered equal"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "keep_sharp", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_WEIGHTEDNORMAL_KEEP_SHARP); + RNA_def_property_ui_text(prop, "Keep Sharp Edges", "Do not edit normals of sharp edges"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "vertex_group", PROP_STRING, PROP_NONE); + RNA_def_property_string_sdna(prop, NULL, "defgrp_name"); + RNA_def_property_ui_text(prop, "Vertex Group", "Vertex group name for modifying the selected areas"); + RNA_def_property_string_funcs(prop, NULL, NULL, "rna_WeightedNormalModifier_defgrp_name_set"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "invert_vertex_group", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_WEIGHTEDNORMAL_INVERT_VGROUP); + RNA_def_property_ui_text(prop, "Invert", "Invert vertex group influence"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); + + prop = RNA_def_property(srna, "face_influence", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flag", MOD_WEIGHTEDNORMAL_FACE_INFLUENCE); + RNA_def_property_ui_text(prop, "Face Influence", "Use influence of face for weighting"); + RNA_def_property_update(prop, 0, "rna_Modifier_update"); +} + void RNA_def_modifier(BlenderRNA *brna) { StructRNA *srna; @@ -4927,6 +4988,7 @@ void RNA_def_modifier(BlenderRNA *brna) rna_def_modifier_normaledit(brna); rna_def_modifier_meshseqcache(brna); rna_def_modifier_surfacedeform(brna); + rna_def_modifier_weightednormal(brna); } #endif diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index e6ba459a406..70fe38cb60d 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -2502,6 +2502,13 @@ static void rna_def_tool_settings(BlenderRNA *brna) {0, NULL, 0, NULL, NULL} }; + static EnumPropertyItem mod_weighted_strength[] = { + {FACE_STRENGTH_WEAK, "Weak", 0, "Weak", ""}, + {FACE_STRENGTH_MEDIUM, "Medium", 0, "Medium", ""}, + {FACE_STRENGTH_STRONG, "Strong", 0, "Strong", ""}, + {0, NULL, 0, NULL, NULL}, + }; + static const EnumPropertyItem draw_groupuser_items[] = { {OB_DRAW_GROUPUSER_NONE, "NONE", 0, "None", ""}, {OB_DRAW_GROUPUSER_ACTIVE, "ACTIVE", 0, "Active", "Show vertices with no weights in the active group"}, @@ -2890,6 +2897,14 @@ static void rna_def_tool_settings(BlenderRNA *brna) RNA_def_property_boolean_sdna(prop, NULL, "edge_mode_live_unwrap", 1); RNA_def_property_ui_text(prop, "Live Unwrap", "Changing edges seam re-calculates UV unwrap"); + prop = RNA_def_property(srna, "normal_vector", PROP_FLOAT, PROP_XYZ); + RNA_def_property_ui_text(prop, "Normal Vector", "Normal Vector used to copy, add or multiply"); + RNA_def_property_ui_range(prop, -10000.0, 10000.0, 1, 3); + + prop = RNA_def_property(srna, "face_strength", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_items(prop, mod_weighted_strength); + RNA_def_property_ui_text(prop, "Face Strength", "Set strength of face to specified value"); + /* etch-a-ton */ prop = RNA_def_property(srna, "use_bone_sketching", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "bone_sketching", BONE_SKETCHING); diff --git a/source/blender/modifiers/CMakeLists.txt b/source/blender/modifiers/CMakeLists.txt index a70612447d3..495aeb5d28d 100644 --- a/source/blender/modifiers/CMakeLists.txt +++ b/source/blender/modifiers/CMakeLists.txt @@ -100,6 +100,7 @@ set(SRC intern/MOD_uvproject.c intern/MOD_warp.c intern/MOD_wave.c + intern/MOD_weighted_normal.c intern/MOD_weightvg_util.c intern/MOD_weightvgedit.c intern/MOD_weightvgmix.c diff --git a/source/blender/modifiers/MOD_modifiertypes.h b/source/blender/modifiers/MOD_modifiertypes.h index bf121af2bd1..3511b0edbec 100644 --- a/source/blender/modifiers/MOD_modifiertypes.h +++ b/source/blender/modifiers/MOD_modifiertypes.h @@ -86,6 +86,7 @@ extern ModifierTypeInfo modifierType_NormalEdit; extern ModifierTypeInfo modifierType_CorrectiveSmooth; extern ModifierTypeInfo modifierType_MeshSequenceCache; extern ModifierTypeInfo modifierType_SurfaceDeform; +extern ModifierTypeInfo modifierType_WeightedNormal; /* MOD_util.c */ void modifier_type_init(ModifierTypeInfo *types[]); diff --git a/source/blender/modifiers/intern/MOD_util.c b/source/blender/modifiers/intern/MOD_util.c index 5b19bcf4817..9bb12d96f04 100644 --- a/source/blender/modifiers/intern/MOD_util.c +++ b/source/blender/modifiers/intern/MOD_util.c @@ -288,5 +288,6 @@ void modifier_type_init(ModifierTypeInfo *types[]) INIT_TYPE(CorrectiveSmooth); INIT_TYPE(MeshSequenceCache); INIT_TYPE(SurfaceDeform); + INIT_TYPE(WeightedNormal); #undef INIT_TYPE } diff --git a/source/blender/modifiers/intern/MOD_weighted_normal.c b/source/blender/modifiers/intern/MOD_weighted_normal.c new file mode 100644 index 00000000000..2bed6eb6d8a --- /dev/null +++ b/source/blender/modifiers/intern/MOD_weighted_normal.c @@ -0,0 +1,622 @@ +/* + * ***** BEGIN GPL LICENSE BLOCK ***** + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * ***** END GPL LICENSE BLOCK ***** + * + */ + +/** \file blender/modifiers/intern/MOD_weighted_normal.c + * \ingroup modifiers + */ + +#include "limits.h" + +#include "MEM_guardedalloc.h" + +#include "DNA_mesh_types.h" +#include "DNA_object_types.h" +#include "DNA_scene_types.h" + +#include "BKE_cdderivedmesh.h" +#include "BKE_mesh.h" + +#include "BLI_math.h" +#include "BLI_stack.h" + +#include "bmesh_class.h" + +#include "MOD_modifiertypes.h" +#include "MOD_util.h" + +#define INDEX_UNSET INT_MIN +#define INDEX_INVALID -1 +#define IS_EDGE_SHARP(_e2l) (ELEM((_e2l)[1], INDEX_UNSET, INDEX_INVALID)) + +typedef struct pair { + float val; /* contains mode based value (face area/ corner angle) */ + int index; /* index value per poly or per loop */ +} pair; + +/* Sorting function used in modifier, sorts in non increasing order */ +static int sort_by_val(const void *p1, const void *p2) +{ + pair *r1 = (pair *)p1; + pair *r2 = (pair *)p2; + if (r1->val < r2->val) + return 1; + else if (r1->val > r2->val) + return -1; + else + return 0; +} + +/* Sorts by index in increasing order */ +static int sort_by_index(const void *p1, const void *p2) +{ + pair *r1 = (pair *)p1; + pair *r2 = (pair *)p2; + if (r1->index > r2->index) + return 1; + else if (r1->index < r2->index) + return -1; + else + return 0; +} + +static bool check_strength(int strength, int *cur_strength, float *cur_val, int *vertcount, float (*custom_normal)[3]) +{ + if ((strength == FACE_STRENGTH_STRONG && *cur_strength != FACE_STRENGTH_STRONG) || + (strength == FACE_STRENGTH_MEDIUM && *cur_strength == FACE_STRENGTH_WEAK)) + { + *cur_strength = strength; + *cur_val = 0.0f; + *vertcount = 0; + zero_v3(*custom_normal); + } + else if (strength != *cur_strength) { + return false; + } + return true; +} + +static void apply_weights_sharp_loops(WeightedNormalModifierData *wnmd, int *loop_index, int size, pair *mode_pair, + float(*loop_normal)[3], int *loops_to_poly, float(*polynors)[3], int weight, int *strength) +{ + for (int i = 0; i < size - 1; i++) { + for (int j = 0; j < size - i - 1; j++) { + if (wnmd->mode == MOD_WEIGHTEDNORMAL_MODE_FACE + && mode_pair[loops_to_poly[loop_index[j]]].val < mode_pair[loops_to_poly[loop_index[j + 1]]].val) { + int temp = loop_index[j]; + loop_index[j] = loop_index[j + 1]; + loop_index[j + 1] = temp; + } + else if ((wnmd->mode == MOD_WEIGHTEDNORMAL_MODE_ANGLE || wnmd->mode == MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE) + && mode_pair[loop_index[j]].val < mode_pair[loop_index[j + 1]].val) { + int temp = loop_index[j]; + loop_index[j] = loop_index[j + 1]; + loop_index[j + 1] = temp; + } + } + } + float cur_val = 0, custom_normal[3] = { 0.0f }; + int vertcount = 0, cur_strength = FACE_STRENGTH_WEAK; + const bool face_influence = (wnmd->flag & MOD_WEIGHTEDNORMAL_FACE_INFLUENCE) != 0; + + for (int i = 0; i < size; i++) { + float wnor[3]; + int j, mp_index; + bool do_loop = true; + + if (wnmd->mode == MOD_WEIGHTEDNORMAL_MODE_FACE) { + j = loops_to_poly[loop_index[i]]; + mp_index = mode_pair[j].index; + } + else { + j = loop_index[i]; + mp_index = loops_to_poly[j]; + } + if (face_influence && strength) { + do_loop = check_strength(strength[mp_index], &cur_strength, &cur_val, &vertcount, &custom_normal); + } + if (do_loop) { + if (!cur_val) { + cur_val = mode_pair[j].val; + } + if (!compare_ff(cur_val, mode_pair[j].val, wnmd->thresh)) { + vertcount++; + cur_val = mode_pair[j].val; + } + float n_weight = pow(weight, vertcount); + copy_v3_v3(wnor, polynors[mp_index]); + + mul_v3_fl(wnor, mode_pair[j].val * (1.0f / n_weight)); + add_v3_v3(custom_normal, wnor); + } + } + normalize_v3(custom_normal); + + for (int i = 0; i < size; i++) { + copy_v3_v3(loop_normal[loop_index[i]], custom_normal); + } +} + +/* Modified version of loop_split_worker_do which sets custom_normals without considering smoothness of faces or loop normal space array + * Used only to work on sharp edges */ +static void loop_split_worker( + WeightedNormalModifierData *wnmd, pair *mode_pair, MLoop *ml_curr, MLoop *ml_prev, + int ml_curr_index, int ml_prev_index, int *e2l_prev, int mp_index, + float (*loop_normal)[3], int *loops_to_poly, float (*polynors)[3], + MEdge *medge, MLoop *mloop, MPoly *mpoly, int (*edge_to_loops)[2], int weight, int *strength) +{ + if (e2l_prev) { + int *e2lfan_curr = e2l_prev; + const MLoop *mlfan_curr = ml_prev; + int mlfan_curr_index = ml_prev_index; + int mlfan_vert_index = ml_curr_index; + int mpfan_curr_index = mp_index; + + BLI_Stack *loop_index = BLI_stack_new(sizeof(int), __func__); + + while (true) { + const unsigned int mv_pivot_index = ml_curr->v; + const MEdge *me_curr = &medge[mlfan_curr->e]; + const MEdge *me_org = &medge[ml_curr->e]; + + BLI_stack_push(loop_index, &mlfan_vert_index); + + if (IS_EDGE_SHARP(e2lfan_curr) || (me_curr == me_org)) { + break; + } + + BKE_mesh_loop_manifold_fan_around_vert_next( + mloop, mpoly, loops_to_poly, e2lfan_curr, mv_pivot_index, + &mlfan_curr, &mlfan_curr_index, &mlfan_vert_index, &mpfan_curr_index); + + e2lfan_curr = edge_to_loops[mlfan_curr->e]; + } + + int *index = MEM_mallocN(sizeof(*index) * BLI_stack_count(loop_index), __func__); + int cur = 0; + while (!BLI_stack_is_empty(loop_index)) { + BLI_stack_pop(loop_index, &index[cur]); + cur++; + } + apply_weights_sharp_loops(wnmd, index, cur, mode_pair, loop_normal, loops_to_poly, polynors, weight, strength); + MEM_freeN(index); + BLI_stack_free(loop_index); + } + else { + copy_v3_v3(loop_normal[ml_curr_index], polynors[loops_to_poly[ml_curr_index]]); + } +} + +static void apply_weights_vertex_normal( + WeightedNormalModifierData *wnmd, Object *UNUSED(ob), DerivedMesh *UNUSED(dm), short(*clnors)[2], + MVert *mvert, const int numVerts, MEdge *medge, const int numEdges, MLoop *mloop, const int numLoops, + MPoly *mpoly, const int numPoly, float(*polynors)[3], MDeformVert *dvert, int defgrp_index, + const bool use_invert_vgroup, const float weight, short mode, pair *mode_pair, int *strength) +{ + float(*custom_normal)[3] = MEM_callocN(sizeof(*custom_normal) * numVerts, __func__); + int *vertcount = MEM_callocN(sizeof(*vertcount) * numVerts, __func__); /* Count number of loops using this vertex so far. */ + float *cur_val = MEM_callocN(sizeof(*cur_val) * numVerts, __func__); /* Current max val for this vertex. */ + int *cur_strength = MEM_mallocN(sizeof(*cur_strength) * numVerts, __func__); /* Current max strength encountered for this vertex. */ + int *loops_to_poly = MEM_mallocN(sizeof(*loops_to_poly) * numLoops, __func__); + + for (int mp_index = 0; mp_index < numPoly; mp_index++) { + int ml_index = mpoly[mp_index].loopstart; + const int ml_index_end = ml_index + mpoly[mp_index].totloop; + + for (; ml_index < ml_index_end; ml_index++) { + loops_to_poly[ml_index] = mp_index; + } + } + for (int i = 0; i < numVerts; i++) { + cur_strength[i] = FACE_STRENGTH_WEAK; + } + + const bool keep_sharp = (wnmd->flag & MOD_WEIGHTEDNORMAL_KEEP_SHARP) != 0; + const bool face_influence = (wnmd->flag & MOD_WEIGHTEDNORMAL_FACE_INFLUENCE) != 0; + const bool has_vgroup = dvert != NULL; + + if (mode == MOD_WEIGHTEDNORMAL_MODE_FACE) { + for (int i = 0; i < numPoly; i++) { /* Iterate through each pair in descending order. */ + int ml_index = mpoly[mode_pair[i].index].loopstart; + const int ml_index_end = ml_index + mpoly[mode_pair[i].index].totloop; + + for (; ml_index < ml_index_end; ml_index++) { + int mv_index = mloop[ml_index].v; + const bool vert_of_group = has_vgroup && dvert[mv_index].dw != NULL && dvert[mv_index].dw->def_nr == defgrp_index; + + if ((vert_of_group ^ use_invert_vgroup) || !dvert) { + float wnor[3]; + bool do_loop = true; + + if (face_influence && strength) { + do_loop = check_strength(strength[mode_pair[i].index], &cur_strength[mv_index], + &cur_val[mv_index], &vertcount[mv_index], &custom_normal[mv_index]); + } + if (do_loop) { + if (!cur_val[mv_index]) { /* If cur_val is 0 init it to present value. */ + cur_val[mv_index] = mode_pair[i].val; + } + if (!compare_ff(cur_val[mv_index], mode_pair[i].val, wnmd->thresh)) { + vertcount[mv_index]++; /* cur_val and present value differ more than threshold, update. */ + cur_val[mv_index] = mode_pair[i].val; + } + float n_weight = pow(weight, vertcount[mv_index]); /* Exponentially divided weight for each normal. */ + + copy_v3_v3(wnor, polynors[mode_pair[i].index]); + mul_v3_fl(wnor, mode_pair[i].val * (1.0f / n_weight)); + add_v3_v3(custom_normal[mv_index], wnor); + } + } + } + } + } + else if (ELEM(mode, MOD_WEIGHTEDNORMAL_MODE_ANGLE, MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE)) { + for (int i = 0; i < numLoops; i++) { + float wnor[3]; + int ml_index = mode_pair[i].index; + int mv_index = mloop[ml_index].v; + const bool vert_of_group = has_vgroup && dvert[mv_index].dw != NULL && dvert[mv_index].dw->def_nr == defgrp_index; + + if ((vert_of_group ^ use_invert_vgroup) || !dvert) { + bool do_loop = true; + + if (face_influence && strength) { + do_loop = check_strength(strength[loops_to_poly[ml_index]], &cur_strength[mv_index], + &cur_val[mv_index], &vertcount[mv_index], &custom_normal[mv_index]); + } + if (do_loop) { + if (!cur_val[mv_index]) { + cur_val[mv_index] = mode_pair[i].val; + } + if (!compare_ff(cur_val[mv_index], mode_pair[i].val, wnmd->thresh)) { + vertcount[mv_index]++; + cur_val[mv_index] = mode_pair[i].val; + } + float n_weight = pow(weight, vertcount[mv_index]); + + copy_v3_v3(wnor, polynors[loops_to_poly[ml_index]]); + mul_v3_fl(wnor, mode_pair[i].val * (1.0f / n_weight)); + add_v3_v3(custom_normal[mv_index], wnor); + } + } + } + } + for (int mv_index = 0; mv_index < numVerts; mv_index++) { + normalize_v3(custom_normal[mv_index]); + } + + if (!keep_sharp && !has_vgroup) { + BKE_mesh_normals_loop_custom_from_vertices_set(mvert, custom_normal, numVerts, medge, numEdges, + mloop, numLoops, mpoly, polynors, numPoly, clnors); + } + else { + float(*loop_normal)[3] = MEM_callocN(sizeof(*loop_normal) * numLoops, "__func__"); + + BKE_mesh_normals_loop_split(mvert, numVerts, medge, numEdges, mloop, loop_normal, numLoops, mpoly, polynors, + numPoly, true, (float)M_PI, NULL, clnors, loops_to_poly); + + for (int mp_index = 0; mp_index < numPoly; mp_index++) { + int ml_index = mpoly[mp_index].loopstart; + const int ml_index_end = ml_index + mpoly[mp_index].totloop; + + for (int i = ml_index; i < ml_index_end; i++) { + if (!is_zero_v3(custom_normal[mloop[i].v])) { + copy_v3_v3(loop_normal[i], custom_normal[mloop[i].v]); + } + } + } + + if (keep_sharp) { + int (*edge_to_loops)[2] = MEM_callocN(sizeof(*edge_to_loops) * numEdges, __func__); + + if (wnmd->mode == MOD_WEIGHTEDNORMAL_MODE_FACE) { + qsort(mode_pair, numPoly, sizeof(*mode_pair), sort_by_index); + } + else { + qsort(mode_pair, numLoops, sizeof(*mode_pair), sort_by_index); + } + MPoly *mp; + int mp_index; + for (mp = mpoly, mp_index = 0; mp_index < numPoly; mp++, mp_index++) { + int ml_curr_index = mp->loopstart; + const int ml_last_index = (ml_curr_index + mp->totloop) - 1; + + MLoop *ml_curr = &mloop[ml_curr_index]; + + for (; ml_curr_index <= ml_last_index; ml_curr++, ml_curr_index++) { + int *e2l = edge_to_loops[ml_curr->e]; + + if ((e2l[0] | e2l[1]) == 0) { + e2l[0] = ml_curr_index; + e2l[1] = INDEX_UNSET; /* Not considering smoothness of faces, UNSET if first loop encountered on this edge. */ + } + else if (e2l[1] == INDEX_UNSET) { + if ((medge[ml_curr->e].flag & ME_SHARP) || ml_curr->v == mloop[e2l[0]].v) { + e2l[1] = INDEX_INVALID; + } + else { + e2l[1] = ml_curr_index; + } + } + else if (!IS_EDGE_SHARP(e2l)) { + e2l[1] = INDEX_INVALID; + } + } + } + + for (mp = mpoly, mp_index = 0; mp_index < numPoly; mp++, mp_index++) { + const int ml_last_index = (mp->loopstart + mp->totloop) - 1; + int ml_curr_index = mp->loopstart; + int ml_prev_index = ml_last_index; + + MLoop *ml_curr = &mloop[ml_curr_index]; + MLoop *ml_prev = &mloop[ml_prev_index]; + + for (; ml_curr_index <= ml_last_index; ml_curr++, ml_curr_index++) { + int *e2l_curr = edge_to_loops[ml_curr->e]; + int *e2l_prev = edge_to_loops[ml_prev->e]; + + if (IS_EDGE_SHARP(e2l_curr)) { + if (IS_EDGE_SHARP(e2l_curr) && IS_EDGE_SHARP(e2l_prev)) { + loop_split_worker(wnmd, mode_pair, ml_curr, ml_prev, ml_curr_index, -1, NULL, + mp_index, loop_normal, loops_to_poly, polynors, medge, mloop, mpoly, + edge_to_loops, weight, strength); + } + else { + loop_split_worker(wnmd, mode_pair, ml_curr, ml_prev, ml_curr_index, ml_prev_index, e2l_prev, + mp_index, loop_normal, loops_to_poly, polynors, medge, mloop, mpoly, + edge_to_loops, weight, strength); + } + } + ml_prev = ml_curr; + ml_prev_index = ml_curr_index; + } + } + MEM_freeN(edge_to_loops); + } + BKE_mesh_normals_loop_custom_set(mvert, numVerts, medge, numEdges, + mloop, loop_normal, numLoops, mpoly, polynors, numPoly, clnors); + MEM_freeN(loop_normal); + } + + MEM_freeN(loops_to_poly); + MEM_freeN(cur_strength); + MEM_freeN(vertcount); + MEM_freeN(cur_val); + MEM_freeN(custom_normal); +} + +static void wn_face_area( + WeightedNormalModifierData *wnmd, Object *ob, DerivedMesh *dm, short (*clnors)[2], + MVert *mvert, const int numVerts, MEdge *medge, const int numEdges, MLoop *mloop, const int numLoops, + MPoly *mpoly, const int numPoly, float (*polynors)[3], + MDeformVert *dvert, int defgrp_index, const bool use_invert_vgroup, const float weight, int *strength) +{ + pair *face_area = MEM_mallocN(sizeof(*face_area) * numPoly, __func__); + + for (int mp_index = 0; mp_index < numPoly; mp_index++) { + face_area[mp_index].val = BKE_mesh_calc_poly_area(&mpoly[mp_index], &mloop[mpoly[mp_index].loopstart], mvert); + face_area[mp_index].index = mp_index; + } + + qsort(face_area, numPoly, sizeof(*face_area), sort_by_val); + apply_weights_vertex_normal(wnmd, ob, dm, clnors, mvert, numVerts, medge, numEdges, mloop, numLoops, + mpoly, numPoly, polynors, dvert, defgrp_index, use_invert_vgroup, + weight, MOD_WEIGHTEDNORMAL_MODE_FACE, face_area, strength); + + MEM_freeN(face_area); +} + +static void wn_corner_angle( + WeightedNormalModifierData *wnmd, Object *ob, DerivedMesh *dm, short(*clnors)[2], + MVert *mvert, const int numVerts, MEdge *medge, const int numEdges, MLoop *mloop, const int numLoops, + MPoly *mpoly, const int numPoly, float (*polynors)[3], + MDeformVert *dvert, int defgrp_index, const bool use_invert_vgroup, const float weight, int *strength) +{ + pair *corner_angle = MEM_mallocN(sizeof(*corner_angle) * numLoops, __func__); + + for (int mp_index = 0; mp_index < numPoly; mp_index++) { + int l_start = mpoly[mp_index].loopstart; + float *index_angle = MEM_mallocN(sizeof(*index_angle) * mpoly[mp_index].totloop, __func__); + BKE_mesh_calc_poly_angles(&mpoly[mp_index], &mloop[l_start], mvert, index_angle); + + for (int i = l_start; i < l_start + mpoly[mp_index].totloop; i++) { + corner_angle[i].val = (float)M_PI - index_angle[i - l_start]; + corner_angle[i].index = i; + } + MEM_freeN(index_angle); + } + + qsort(corner_angle, numLoops, sizeof(*corner_angle), sort_by_val); + apply_weights_vertex_normal(wnmd, ob, dm, clnors, mvert, numVerts, medge, numEdges, mloop, numLoops, + mpoly, numPoly, polynors, dvert, defgrp_index, use_invert_vgroup, + weight, MOD_WEIGHTEDNORMAL_MODE_ANGLE, corner_angle, strength); + + MEM_freeN(corner_angle); +} + +static void wn_face_with_angle( + WeightedNormalModifierData *wnmd, Object *ob, DerivedMesh *dm, short(*clnors)[2], + MVert *mvert, const int numVerts, MEdge *medge, const int numEdges, MLoop *mloop, const int numLoops, + MPoly *mpoly, const int numPoly, float(*polynors)[3], + MDeformVert *dvert, int defgrp_index, const bool use_invert_vgroup, const float weight, int *strength) +{ + pair *combined = MEM_mallocN(sizeof(*combined) * numLoops, __func__); + + for (int mp_index = 0; mp_index < numPoly; mp_index++) { + int l_start = mpoly[mp_index].loopstart; + float face_area = BKE_mesh_calc_poly_area(&mpoly[mp_index], &mloop[l_start], mvert); + float *index_angle = MEM_mallocN(sizeof(*index_angle) * mpoly[mp_index].totloop, __func__); + + BKE_mesh_calc_poly_angles(&mpoly[mp_index], &mloop[l_start], mvert, index_angle); + + for (int i = l_start; i < l_start + mpoly[mp_index].totloop; i++) { + combined[i].val = ((float)M_PI - index_angle[i - l_start]) * face_area; /* In this case val is product of corner angle and face area. */ + combined[i].index = i; + } + MEM_freeN(index_angle); + } + + qsort(combined, numLoops, sizeof(*combined), sort_by_val); + apply_weights_vertex_normal(wnmd, ob, dm, clnors, mvert, numVerts, medge, numEdges, mloop, numLoops, + mpoly, numPoly, polynors, dvert, defgrp_index, use_invert_vgroup, + weight, MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE, combined, strength); + + MEM_freeN(combined); +} + +static DerivedMesh *applyModifier(ModifierData *md, Object *ob, DerivedMesh *dm, ModifierApplyFlag UNUSED(flag)) +{ + WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md; + + Mesh *me = ob->data; + + if (!(me->flag & ME_AUTOSMOOTH)) { + modifier_setError((ModifierData *)wnmd, "Enable 'Auto Smooth' option in mesh settings"); + return dm; + } + + const int numPoly = dm->getNumPolys(dm); + const int numVerts = dm->getNumVerts(dm); + const int numEdges = dm->getNumEdges(dm); + const int numLoops = dm->getNumLoops(dm); + MPoly *mpoly = dm->getPolyArray(dm); + MVert *mvert = dm->getVertArray(dm); + MEdge *medge = dm->getEdgeArray(dm); + MLoop *mloop = dm->getLoopArray(dm); + + MDeformVert *dvert; + int defgrp_index; + + const bool use_invert_vgroup = (wnmd->flag & MOD_WEIGHTEDNORMAL_INVERT_VGROUP) != 0; + bool free_polynors = false; + + float weight = ((float)wnmd->weight) / 50.0f; + + if (wnmd->weight == 100) { + weight = (float)SHRT_MAX; + } + else if (wnmd->weight == 1) { + weight = 1 / (float)SHRT_MAX; + } + else if ((weight - 1) * 25 > 1) { + weight = (weight - 1) * 25; + } + + float (*polynors)[3] = dm->getPolyDataArray(dm, CD_NORMAL); + + if (!polynors) { + polynors = MEM_mallocN(sizeof(*polynors) * numPoly, __func__); + BKE_mesh_calc_normals_poly(mvert, NULL, numVerts, mloop, mpoly, numLoops, numPoly, polynors, false); + free_polynors = true; + } + + short (*clnors)[2]; + + clnors = CustomData_duplicate_referenced_layer(&dm->loopData, CD_CUSTOMLOOPNORMAL, numLoops); + if (!clnors) { + DM_add_loop_layer(dm, CD_CUSTOMLOOPNORMAL, CD_CALLOC, NULL); + clnors = dm->getLoopDataArray(dm, CD_CUSTOMLOOPNORMAL); + } + int *strength = CustomData_get_layer(&dm->polyData, CD_PROP_INT); + + modifier_get_vgroup(ob, dm, wnmd->defgrp_name, &dvert, &defgrp_index); + + switch (wnmd->mode) { + case MOD_WEIGHTEDNORMAL_MODE_FACE: + wn_face_area(wnmd, ob, dm, clnors, mvert, numVerts, medge, numEdges, mloop, numLoops, + mpoly, numPoly, polynors, dvert, defgrp_index, use_invert_vgroup, weight, strength); + break; + case MOD_WEIGHTEDNORMAL_MODE_ANGLE: + wn_corner_angle(wnmd, ob, dm, clnors, mvert, numVerts, medge, numEdges, mloop, numLoops, + mpoly, numPoly, polynors, dvert, defgrp_index, use_invert_vgroup, weight, strength); + break; + case MOD_WEIGHTEDNORMAL_MODE_FACE_ANGLE: + wn_face_with_angle(wnmd, ob, dm, clnors, mvert, numVerts, medge, numEdges, mloop, numLoops, + mpoly, numPoly, polynors, dvert, defgrp_index, use_invert_vgroup, weight, strength); + break; + } + + if (free_polynors) { + MEM_freeN(polynors); + } + + return dm; +} + +static void copyData(ModifierData *md, ModifierData *target) +{ + modifier_copyData_generic(md, target); +} + +static void initData(ModifierData *md) +{ + WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md; + wnmd->mode = MOD_WEIGHTEDNORMAL_MODE_FACE; + wnmd->weight = 50; + wnmd->thresh = 1e-2f; + wnmd->flag = 0; +} + +static CustomDataMask requiredDataMask(Object *UNUSED(ob), ModifierData *md) +{ + WeightedNormalModifierData *wnmd = (WeightedNormalModifierData *)md; + CustomDataMask dataMask = CD_CUSTOMLOOPNORMAL; + + if (wnmd->defgrp_name[0]) { + dataMask |= CD_MASK_MDEFORMVERT; + } + + return dataMask; +} + +static bool dependsOnNormals(ModifierData *UNUSED(md)) +{ + return true; +} + +ModifierTypeInfo modifierType_WeightedNormal = { + /* name */ "Weighted Normal", + /* structName */ "WeightedNormalModifierData", + /* structSize */ sizeof(WeightedNormalModifierData), + /* type */ eModifierTypeType_Constructive, + /* flags */ eModifierTypeFlag_AcceptsMesh | + eModifierTypeFlag_SupportsEditmode | + eModifierTypeFlag_EnableInEditmode, + + /* copyData */ copyData, + /* deformVerts */ NULL, + /* deformMatrices */ NULL, + /* deformVertsEM */ NULL, + /* deformMatricesEM */ NULL, + /* applyModifier */ applyModifier, + /* applyModifierEM */ NULL, + /* initData */ initData, + /* requiredDataMask */ requiredDataMask, + /* freeData */ NULL, + /* isDisabled */ NULL, + /* updateDepgraph */ NULL, + /* updateDepsgraph */ NULL, + /* dependsOnTime */ NULL, + /* dependsOnNormals */ dependsOnNormals, + /* foreachObjectLink */ NULL, + /* foreachIDLink */ NULL, + /* foreachTexLink */ NULL, +}; diff --git a/source/tools b/source/tools -Subproject 7695e14cfc5820ac66546e0e515914d85ab81af +Subproject 88a1758d2d2e862cc69c08b5b40a4e75f71592d |