/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2005 Blender Foundation. All rights reserved. */ /** \file * \ingroup modifiers */ #include #include "MEM_guardedalloc.h" #include "BLI_utildefines.h" #include "BLT_translation.h" #include "DNA_defaults.h" #include "DNA_mesh_types.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DNA_screen_types.h" #include "BKE_cdderivedmesh.h" #include "BKE_context.h" #include "BKE_mesh.h" #include "BKE_modifier.h" #include "BKE_multires.h" #include "BKE_paint.h" #include "BKE_screen.h" #include "BKE_subdiv.h" #include "BKE_subdiv_ccg.h" #include "BKE_subdiv_deform.h" #include "BKE_subdiv_mesh.h" #include "BKE_subsurf.h" #include "UI_interface.h" #include "UI_resources.h" #include "RNA_access.h" #include "RNA_prototypes.h" #include "WM_types.h" /* For subdivide operator UI. */ #include "DEG_depsgraph_query.h" #include "MOD_modifiertypes.h" #include "MOD_ui_common.h" typedef struct MultiresRuntimeData { /* Cached subdivision surface descriptor, with topology and settings. */ struct Subdiv *subdiv; } MultiresRuntimeData; static void initData(ModifierData *md) { MultiresModifierData *mmd = (MultiresModifierData *)md; BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(mmd, modifier)); MEMCPY_STRUCT_AFTER(mmd, DNA_struct_default_get(MultiresModifierData), modifier); /* Open subdivision panels by default. */ md->ui_expand_flag = UI_PANEL_DATA_EXPAND_ROOT | UI_SUBPANEL_DATA_EXPAND_1; } static void requiredDataMask(Object *UNUSED(ob), ModifierData *md, CustomData_MeshMasks *r_cddata_masks) { MultiresModifierData *mmd = (MultiresModifierData *)md; if (mmd->flags & eMultiresModifierFlag_UseCustomNormals) { r_cddata_masks->lmask |= CD_MASK_NORMAL; r_cddata_masks->lmask |= CD_MASK_CUSTOMLOOPNORMAL; } } static bool dependsOnNormals(ModifierData *md) { MultiresModifierData *mmd = (MultiresModifierData *)md; if (mmd->flags & eMultiresModifierFlag_UseCustomNormals) { return true; } return false; } static void copyData(const ModifierData *md_src, ModifierData *md_dst, const int flag) { BKE_modifier_copydata_generic(md_src, md_dst, flag); } static void freeRuntimeData(void *runtime_data_v) { if (runtime_data_v == NULL) { return; } MultiresRuntimeData *runtime_data = (MultiresRuntimeData *)runtime_data_v; if (runtime_data->subdiv != NULL) { BKE_subdiv_free(runtime_data->subdiv); } MEM_freeN(runtime_data); } static void freeData(ModifierData *md) { MultiresModifierData *mmd = (MultiresModifierData *)md; freeRuntimeData(mmd->modifier.runtime); } static MultiresRuntimeData *multires_ensure_runtime(MultiresModifierData *mmd) { MultiresRuntimeData *runtime_data = (MultiresRuntimeData *)mmd->modifier.runtime; if (runtime_data == NULL) { runtime_data = MEM_callocN(sizeof(*runtime_data), "subsurf runtime"); mmd->modifier.runtime = runtime_data; } return runtime_data; } /* Main goal of this function is to give usable subdivision surface descriptor * which matches settings and topology. */ static Subdiv *subdiv_descriptor_ensure(MultiresModifierData *mmd, const SubdivSettings *subdiv_settings, const Mesh *mesh) { MultiresRuntimeData *runtime_data = (MultiresRuntimeData *)mmd->modifier.runtime; Subdiv *subdiv = BKE_subdiv_update_from_mesh(runtime_data->subdiv, subdiv_settings, mesh); runtime_data->subdiv = subdiv; return subdiv; } /* Subdivide into fully qualified mesh. */ static Mesh *multires_as_mesh(MultiresModifierData *mmd, const ModifierEvalContext *ctx, Mesh *mesh, Subdiv *subdiv) { Mesh *result = mesh; const bool use_render_params = (ctx->flag & MOD_APPLY_RENDER); const bool ignore_simplify = (ctx->flag & MOD_APPLY_IGNORE_SIMPLIFY); const bool ignore_control_edges = (ctx->flag & MOD_APPLY_TO_BASE_MESH); const Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); Object *object = ctx->object; SubdivToMeshSettings mesh_settings; BKE_multires_subdiv_mesh_settings_init(&mesh_settings, scene, object, mmd, use_render_params, ignore_simplify, ignore_control_edges); if (mesh_settings.resolution < 3) { return result; } BKE_subdiv_displacement_attach_from_multires(subdiv, mesh, mmd); result = BKE_subdiv_to_mesh(subdiv, &mesh_settings, mesh); return result; } /* Subdivide into CCG. */ static void multires_ccg_settings_init(SubdivToCCGSettings *settings, const MultiresModifierData *mmd, const ModifierEvalContext *ctx, Mesh *mesh) { const bool has_mask = CustomData_has_layer(&mesh->ldata, CD_GRID_PAINT_MASK); const bool use_render_params = (ctx->flag & MOD_APPLY_RENDER); const bool ignore_simplify = (ctx->flag & MOD_APPLY_IGNORE_SIMPLIFY); const Scene *scene = DEG_get_evaluated_scene(ctx->depsgraph); Object *object = ctx->object; const int level = multires_get_level(scene, object, mmd, use_render_params, ignore_simplify); settings->resolution = (1 << level) + 1; settings->need_normal = true; settings->need_mask = has_mask; } static Mesh *multires_as_ccg(MultiresModifierData *mmd, const ModifierEvalContext *ctx, Mesh *mesh, Subdiv *subdiv) { Mesh *result = mesh; SubdivToCCGSettings ccg_settings; multires_ccg_settings_init(&ccg_settings, mmd, ctx, mesh); if (ccg_settings.resolution < 3) { return result; } BKE_subdiv_displacement_attach_from_multires(subdiv, mesh, mmd); result = BKE_subdiv_to_ccg_mesh(subdiv, &ccg_settings, mesh); /* NOTE: CCG becomes an owner of Subdiv descriptor, so can not share * this pointer. Not sure if it's needed, but might have a second look * on the ownership model here. */ MultiresRuntimeData *runtime_data = mmd->modifier.runtime; runtime_data->subdiv = NULL; return result; } static Mesh *modifyMesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh) { Mesh *result = mesh; #if !defined(WITH_OPENSUBDIV) BKE_modifier_set_error(ctx->object, md, "Disabled, built without OpenSubdiv"); return result; #endif MultiresModifierData *mmd = (MultiresModifierData *)md; SubdivSettings subdiv_settings; BKE_multires_subdiv_settings_init(&subdiv_settings, mmd); if (subdiv_settings.level == 0) { return result; } MultiresRuntimeData *runtime_data = multires_ensure_runtime(mmd); Subdiv *subdiv = subdiv_descriptor_ensure(mmd, &subdiv_settings, mesh); if (subdiv == NULL) { /* Happens on bad topology, ut also on empty input mesh. */ return result; } const bool use_clnors = mmd->flags & eMultiresModifierFlag_UseCustomNormals && mesh->flag & ME_AUTOSMOOTH && CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL); /* NOTE: Orco needs final coordinates on CPU side, which are expected to be * accessible via MVert. For this reason we do not evaluate multires to * grids when orco is requested. */ const bool for_orco = (ctx->flag & MOD_APPLY_ORCO) != 0; /* Needed when rendering or baking will in sculpt mode. */ const bool for_render = (ctx->flag & MOD_APPLY_RENDER) != 0; const bool sculpt_base_mesh = mmd->flags & eMultiresModifierFlag_UseSculptBaseMesh; if ((ctx->object->mode & OB_MODE_SCULPT) && !for_orco && !for_render && !sculpt_base_mesh) { /* NOTE: CCG takes ownership over Subdiv. */ result = multires_as_ccg(mmd, ctx, mesh, subdiv); result->runtime.subdiv_ccg_tot_level = mmd->totlvl; /* TODO(sergey): Usually it is sculpt stroke's update variants which * takes care of this, but is possible that we need this before the * stroke: i.e. when exiting blender right after stroke is done. * Annoying and not so much black-boxed as far as sculpting goes, and * surely there is a better way of solving this. */ if (ctx->object->sculpt != NULL) { SculptSession *sculpt_session = ctx->object->sculpt; sculpt_session->subdiv_ccg = result->runtime.subdiv_ccg; sculpt_session->multires.active = true; sculpt_session->multires.modifier = mmd; sculpt_session->multires.level = mmd->sculptlvl; sculpt_session->totvert = mesh->totvert; sculpt_session->totpoly = mesh->totpoly; sculpt_session->mvert = NULL; sculpt_session->mpoly = NULL; sculpt_session->mloop = NULL; } // BKE_subdiv_stats_print(&subdiv->stats); } else { if (use_clnors) { /* If custom normals are present and the option is turned on calculate the split * normals and clear flag so the normals get interpolated to the result mesh. */ BKE_mesh_calc_normals_split(mesh); CustomData_clear_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY); } result = multires_as_mesh(mmd, ctx, mesh, subdiv); if (use_clnors) { float(*lnors)[3] = CustomData_get_layer(&result->ldata, CD_NORMAL); BLI_assert(lnors != NULL); BKE_mesh_set_custom_normals(result, lnors); CustomData_set_layer_flag(&mesh->ldata, CD_NORMAL, CD_FLAG_TEMPORARY); CustomData_set_layer_flag(&result->ldata, CD_NORMAL, CD_FLAG_TEMPORARY); } // BKE_subdiv_stats_print(&subdiv->stats); if (subdiv != runtime_data->subdiv) { BKE_subdiv_free(subdiv); } } return result; } static void deformMatrices(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh, float (*vertex_cos)[3], float (*deform_matrices)[3][3], int verts_num) { #if !defined(WITH_OPENSUBDIV) BKE_modifier_set_error(ctx->object, md, "Disabled, built without OpenSubdiv"); return; #endif /* Subsurf does not require extra space mapping, keep matrices as is. */ (void)deform_matrices; MultiresModifierData *mmd = (MultiresModifierData *)md; SubdivSettings subdiv_settings; BKE_multires_subdiv_settings_init(&subdiv_settings, mmd); if (subdiv_settings.level == 0) { return; } SubdivToCCGSettings ccg_settings; multires_ccg_settings_init(&ccg_settings, mmd, ctx, mesh); if (ccg_settings.resolution < 3) { return; } MultiresRuntimeData *runtime_data = multires_ensure_runtime(mmd); Subdiv *subdiv = subdiv_descriptor_ensure(mmd, &subdiv_settings, mesh); if (subdiv == NULL) { /* Happens on bad topology, ut also on empty input mesh. */ return; } BKE_subdiv_displacement_attach_from_multires(subdiv, mesh, mmd); BKE_subdiv_deform_coarse_vertices(subdiv, mesh, vertex_cos, verts_num); if (subdiv != runtime_data->subdiv) { BKE_subdiv_free(subdiv); } } static void panel_draw(const bContext *C, Panel *panel) { uiLayout *col; uiLayout *layout = panel->layout; PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); uiLayoutSetPropSep(layout, true); col = uiLayoutColumn(layout, true); uiItemR(col, ptr, "levels", 0, IFACE_("Level Viewport"), ICON_NONE); uiItemR(col, ptr, "sculpt_levels", 0, IFACE_("Sculpt"), ICON_NONE); uiItemR(col, ptr, "render_levels", 0, IFACE_("Render"), ICON_NONE); const bool is_sculpt_mode = CTX_data_active_object(C)->mode & OB_MODE_SCULPT; uiBlock *block = uiLayoutGetBlock(panel->layout); UI_block_lock_set(block, !is_sculpt_mode, IFACE_("Sculpt Base Mesh")); uiItemR(col, ptr, "use_sculpt_base_mesh", 0, IFACE_("Sculpt Base Mesh"), ICON_NONE); UI_block_lock_clear(block); uiItemR(layout, ptr, "show_only_control_edges", 0, NULL, ICON_NONE); modifier_panel_end(layout, ptr); } static void subdivisions_panel_draw(const bContext *UNUSED(C), Panel *panel) { uiLayout *row; uiLayout *layout = panel->layout; PointerRNA ob_ptr; PointerRNA *ptr = modifier_panel_get_property_pointers(panel, &ob_ptr); uiLayoutSetEnabled(layout, RNA_enum_get(&ob_ptr, "mode") != OB_MODE_EDIT); MultiresModifierData *mmd = (MultiresModifierData *)ptr->data; /** * Changing some of the properties can not be done once there is an * actual displacement stored for this multi-resolution modifier. * This check will disallow changes for those properties. * This check is a bit stupid but it should be sufficient for the usual * multi-resolution usage. It might become less strict and only disallow * modifications if there is CD_MDISPS layer, or if there is actual * non-zero displacement, but such checks will be too slow to be done * on every redraw. */ PointerRNA op_ptr; uiItemFullO(layout, "OBJECT_OT_multires_subdivide", IFACE_("Subdivide"), ICON_NONE, NULL, WM_OP_EXEC_DEFAULT, 0, &op_ptr); RNA_enum_set(&op_ptr, "mode", MULTIRES_SUBDIVIDE_CATMULL_CLARK); RNA_string_set(&op_ptr, "modifier", ((ModifierData *)mmd)->name); row = uiLayoutRow(layout, false); uiItemFullO(row, "OBJECT_OT_multires_subdivide", IFACE_("Simple"), ICON_NONE, NULL, WM_OP_EXEC_DEFAULT, 0, &op_ptr); RNA_enum_set(&op_ptr, "mode", MULTIRES_SUBDIVIDE_SIMPLE); RNA_string_set(&op_ptr, "modifier", ((ModifierData *)mmd)->name); uiItemFullO(row, "OBJECT_OT_multires_subdivide", IFACE_("Linear"), ICON_NONE, NULL, WM_OP_EXEC_DEFAULT, 0, &op_ptr); RNA_enum_set(&op_ptr, "mode", MULTIRES_SUBDIVIDE_LINEAR); RNA_string_set(&op_ptr, "modifier", ((ModifierData *)mmd)->name); uiItemS(layout); uiItemO(layout, IFACE_("Unsubdivide"), ICON_NONE, "OBJECT_OT_multires_unsubdivide"); uiItemO(layout, IFACE_("Delete Higher"), ICON_NONE, "OBJECT_OT_multires_higher_levels_delete"); } static void shape_panel_draw(const bContext *UNUSED(C), Panel *panel) { uiLayout *row; uiLayout *layout = panel->layout; PointerRNA ob_ptr; modifier_panel_get_property_pointers(panel, &ob_ptr); uiLayoutSetEnabled(layout, RNA_enum_get(&ob_ptr, "mode") != OB_MODE_EDIT); row = uiLayoutRow(layout, false); uiItemO(row, IFACE_("Reshape"), ICON_NONE, "OBJECT_OT_multires_reshape"); uiItemO(row, IFACE_("Apply Base"), ICON_NONE, "OBJECT_OT_multires_base_apply"); } static void generate_panel_draw(const bContext *UNUSED(C), Panel *panel) { uiLayout *col, *row; uiLayout *layout = panel->layout; PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); MultiresModifierData *mmd = (MultiresModifierData *)ptr->data; bool is_external = RNA_boolean_get(ptr, "is_external"); if (mmd->totlvl == 0) { uiItemO( layout, IFACE_("Rebuild Subdivisions"), ICON_NONE, "OBJECT_OT_multires_rebuild_subdiv"); } col = uiLayoutColumn(layout, false); row = uiLayoutRow(col, false); if (is_external) { uiItemO(row, IFACE_("Pack External"), ICON_NONE, "OBJECT_OT_multires_external_pack"); uiLayoutSetPropSep(col, true); row = uiLayoutRow(col, false); uiItemR(row, ptr, "filepath", 0, NULL, ICON_NONE); } else { uiItemO(col, IFACE_("Save External..."), ICON_NONE, "OBJECT_OT_multires_external_save"); } } static void advanced_panel_draw(const bContext *UNUSED(C), Panel *panel) { uiLayout *col; uiLayout *layout = panel->layout; PointerRNA *ptr = modifier_panel_get_property_pointers(panel, NULL); bool has_displacement = RNA_int_get(ptr, "total_levels") != 0; uiLayoutSetPropSep(layout, true); uiLayoutSetActive(layout, !has_displacement); uiItemR(layout, ptr, "quality", 0, NULL, ICON_NONE); col = uiLayoutColumn(layout, false); uiLayoutSetActive(col, true); uiItemR(col, ptr, "uv_smooth", 0, NULL, ICON_NONE); uiItemR(col, ptr, "boundary_smooth", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "use_creases", 0, NULL, ICON_NONE); uiItemR(layout, ptr, "use_custom_normals", 0, NULL, ICON_NONE); } static void panelRegister(ARegionType *region_type) { PanelType *panel_type = modifier_panel_register(region_type, eModifierType_Multires, panel_draw); modifier_subpanel_register( region_type, "subdivide", "Subdivision", NULL, subdivisions_panel_draw, panel_type); modifier_subpanel_register(region_type, "shape", "Shape", NULL, shape_panel_draw, panel_type); modifier_subpanel_register( region_type, "generate", "Generate", NULL, generate_panel_draw, panel_type); modifier_subpanel_register( region_type, "advanced", "Advanced", NULL, advanced_panel_draw, panel_type); } ModifierTypeInfo modifierType_Multires = { /* name */ "Multires", /* structName */ "MultiresModifierData", /* structSize */ sizeof(MultiresModifierData), /* srna */ &RNA_MultiresModifier, /* type */ eModifierTypeType_Constructive, /* flags */ eModifierTypeFlag_AcceptsMesh | eModifierTypeFlag_SupportsMapping | eModifierTypeFlag_RequiresOriginalData, /* icon */ ICON_MOD_MULTIRES, /* copyData */ copyData, /* deformVerts */ NULL, /* deformMatrices */ deformMatrices, /* deformVertsEM */ NULL, /* deformMatricesEM */ NULL, /* modifyMesh */ modifyMesh, /* modifyGeometrySet */ NULL, /* initData */ initData, /* requiredDataMask */ requiredDataMask, /* freeData */ freeData, /* isDisabled */ NULL, /* ensureDepsgraphNodes */ NULL, /* updateDepsgraph */ NULL, /* dependsOnTime */ NULL, /* dependsOnNormals */ dependsOnNormals, /* foreachIDLink */ NULL, /* foreachTexLink */ NULL, /* freeRuntimeData */ freeRuntimeData, /* panelRegister */ panelRegister, /* blendWrite */ NULL, /* blendRead */ NULL, };