Multires: Subdivide Simple and Subdivide Linear

This introduces two alternative subdivision modes that generates
displacement on the grids that look as Simple subdivisions but while
using the Catmull-Clark subdivision type in the modifier. This way,
Simple and Catmull-Clark subdivision can be combined when creating new
levels if needed, for example, to sculpt hard surface objects.

Subdivide simple smooths the sculpted data when creating a new
subdivision level. Subdivide linear also preserves the sharpness
in the sculpted data.

Reviewed By: sergey

Differential Revision: https://developer.blender.org/D7415
This commit is contained in:
Pablo Dobarro 2020-04-30 15:15:19 +02:00
parent f28875a998
commit 134619fabb
Notes: blender-bot 2023-02-13 16:16:21 +01:00
Referenced by issue #95666, Crash when attempting multires linear subdivide
9 changed files with 248 additions and 38 deletions

View File

@ -696,7 +696,15 @@ class DATA_PT_modifiers(ModifierButtonsPanel, Panel):
col = split.column()
col.enabled = ob.mode != 'EDIT'
col.operator("object.multires_subdivide", text="Subdivide")
op = col.operator("object.multires_subdivide", text="Subdivide")
op.mode = 'CATMULL_CLARK'
op = col.operator("object.multires_subdivide", text="Subdivide Simple")
op.mode = 'SIMPLE'
op = col.operator("object.multires_subdivide", text="Subdivide Linear")
op.mode = 'LINEAR'
col.operator("object.multires_higher_levels_delete", text="Delete Higher")
col.operator("object.multires_unsubdivide", text="Unsubdivide")
col.operator("object.multires_reshape", text="Reshape")

View File

@ -180,13 +180,25 @@ bool multiresModifier_reshapeFromCCG(const int tot_level,
struct SubdivCCG *subdiv_ccg);
/* Subdivide multires displacement once. */
void multiresModifier_subdivide(struct Object *object, struct MultiresModifierData *mmd);
typedef enum eMultiresSubdivideModeType {
MULTIRES_SUBDIVIDE_CATMULL_CLARK,
MULTIRES_SUBDIVIDE_SIMPLE,
MULTIRES_SUBDIVIDE_LINEAR,
} eMultiresSubdivideModeType;
void multiresModifier_subdivide(struct Object *object,
struct MultiresModifierData *mmd,
const eMultiresSubdivideModeType mode);
void multires_subdivide_create_tangent_displacement_linear_grids(struct Object *object,
struct MultiresModifierData *mmd);
/* Subdivide displacement to the given level.
* If level is lower than the current top level nothing happens. */
void multiresModifier_subdivide_to_level(struct Object *object,
struct MultiresModifierData *mmd,
const int top_level);
const int top_level,
const eMultiresSubdivideModeType mode);
/* Subdivision integration, defined in multires_subdiv.c */

View File

@ -175,6 +175,7 @@ set(SRC
intern/multires_reshape_apply_base.c
intern/multires_reshape_ccg.c
intern/multires_reshape_smooth.c
intern/multires_reshape_subdivide.c
intern/multires_reshape_util.c
intern/multires_reshape_vertcos.c
intern/multires_subdiv.c

View File

@ -2234,7 +2234,14 @@ void multiresModifier_sync_levels_ex(Object *ob_dst,
}
if (mmd_src->totlvl > mmd_dst->totlvl) {
multiresModifier_subdivide_to_level(ob_dst, mmd_dst, mmd_src->totlvl);
if (mmd_dst->simple) {
multiresModifier_subdivide_to_level(
ob_dst, mmd_dst, mmd_src->totlvl, MULTIRES_SUBDIVIDE_SIMPLE);
}
else {
multiresModifier_subdivide_to_level(
ob_dst, mmd_dst, mmd_src->totlvl, MULTIRES_SUBDIVIDE_CATMULL_CLARK);
}
}
else {
multires_del_higher(mmd_dst, ob_dst, mmd_src->totlvl);

View File

@ -28,8 +28,6 @@
#include "DNA_modifier_types.h"
#include "DNA_scene_types.h"
#include "BLI_math_vector.h"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
@ -37,6 +35,8 @@
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_subdiv.h"
#include "BKE_subsurf.h"
#include "BLI_math_vector.h"
#include "DEG_depsgraph_query.h"
@ -171,15 +171,18 @@ bool multiresModifier_reshapeFromCCG(const int tot_level,
/** \name Subdivision
* \{ */
void multiresModifier_subdivide(Object *object, MultiresModifierData *mmd)
void multiresModifier_subdivide(Object *object,
MultiresModifierData *mmd,
const eMultiresSubdivideModeType mode)
{
const int top_level = mmd->totlvl + 1;
multiresModifier_subdivide_to_level(object, mmd, top_level);
multiresModifier_subdivide_to_level(object, mmd, top_level, mode);
}
void multiresModifier_subdivide_to_level(struct Object *object,
struct MultiresModifierData *mmd,
const int top_level)
const int top_level,
const eMultiresSubdivideModeType mode)
{
if (top_level <= mmd->totlvl) {
return;
@ -196,7 +199,12 @@ void multiresModifier_subdivide_to_level(struct Object *object,
}
if (!has_mdisps || top_level == 1) {
multires_reshape_ensure_grids(coarse_mesh, top_level);
multires_set_tot_level(object, mmd, top_level);
if (ELEM(mode, MULTIRES_SUBDIVIDE_LINEAR, MULTIRES_SUBDIVIDE_SIMPLE)) {
multires_subdivide_create_tangent_displacement_linear_grids(object, mmd);
}
else {
multires_set_tot_level(object, mmd, top_level);
}
return;
}
@ -205,16 +213,22 @@ void multiresModifier_subdivide_to_level(struct Object *object,
if (!multires_reshape_context_create_from_subdivide(&reshape_context, object, mmd, top_level)) {
return;
}
multires_reshape_store_original_grids(&reshape_context);
multires_reshape_ensure_grids(coarse_mesh, reshape_context.top.level);
multires_reshape_assign_final_coords_from_orig_mdisps(&reshape_context);
/* Free original grids which makes it so smoothing with details thinks all the details were
* added against base mesh's limit surface. This is similar behavior to as if we've done all
* displacement in sculpt mode at the old top level and then propagated to the new top level. */
multires_reshape_free_original_grids(&reshape_context);
if (ELEM(mode, MULTIRES_SUBDIVIDE_LINEAR, MULTIRES_SUBDIVIDE_SIMPLE)) {
multires_reshape_smooth_object_grids(&reshape_context, mode);
}
else {
/* Free original grids which makes it so smoothing with details thinks all the details were
* added against base mesh's limit surface. This is similar behavior to as if we've done all
* displacement in sculpt mode at the old top level and then propagated to the new top level.*/
multires_reshape_free_original_grids(&reshape_context);
multires_reshape_smooth_object_grids_with_details(&reshape_context);
multires_reshape_smooth_object_grids_with_details(&reshape_context);
}
multires_reshape_object_grids_to_tangent_displacement(&reshape_context);
multires_reshape_context_free(&reshape_context);

View File

@ -25,6 +25,7 @@
#define __BKE_INTERN_MULTIRES_RESHAPE_H__
#include "BLI_sys_types.h"
#include "BKE_multires.h"
struct Depsgraph;
struct GridPaintMask;
@ -289,7 +290,8 @@ void multires_reshape_smooth_object_grids_with_details(
*
* Makes it so surface on top level looks smooth. Details are not preserved
*/
void multires_reshape_smooth_object_grids(const MultiresReshapeContext *reshape_context);
void multires_reshape_smooth_object_grids(const MultiresReshapeContext *reshape_context,
const enum eMultiresSubdivideModeType mode);
/* --------------------------------------------------------------------
* Displacement, space conversion.
@ -324,5 +326,4 @@ void multires_reshape_apply_base_refine_from_base(MultiresReshapeContext *reshap
*
* NOTE: Will re-evaluate all leading modifiers, so it's not cheap. */
void multires_reshape_apply_base_refine_from_deform(MultiresReshapeContext *reshape_context);
#endif /* __BKE_INTERN_MULTIRES_RESHAPE_H__ */

View File

@ -119,6 +119,14 @@ typedef struct MultiresReshapeSmoothContext {
Subdiv *reshape_subdiv;
SurfaceGrid *base_surface_grids;
/* Defines how displacement is interpolated on the higher levels (for example, whether
* displacement is smoothed in Catmull-Clark mode or interpolated linearly preserving sharp edges
* of the current sculpt level).
*
* NOTE: Uses same enumerator type as Subdivide operator, since the values are the same and
* decoupling type just adds extra headache to convert one enumerator to another. */
eMultiresSubdivideModeType smoothing_type;
} MultiresReshapeSmoothContext;
/** \} */
@ -412,15 +420,17 @@ static int get_reshape_level_resolution(const MultiresReshapeContext *reshape_co
static char get_effective_edge_crease_char(
const MultiresReshapeSmoothContext *reshape_smooth_context, const MEdge *base_edge)
{
const MultiresReshapeContext *reshape_context = reshape_smooth_context->reshape_context;
if (reshape_context->subdiv->settings.is_simple) {
if (ELEM(reshape_smooth_context->smoothing_type,
MULTIRES_SUBDIVIDE_LINEAR,
MULTIRES_SUBDIVIDE_SIMPLE)) {
return 255;
}
return base_edge->crease;
}
static void context_init(MultiresReshapeSmoothContext *reshape_smooth_context,
const MultiresReshapeContext *reshape_context)
const MultiresReshapeContext *reshape_context,
const eMultiresSubdivideModeType mode)
{
reshape_smooth_context->reshape_context = reshape_context;
@ -440,6 +450,8 @@ static void context_init(MultiresReshapeSmoothContext *reshape_smooth_context,
reshape_smooth_context->non_loose_base_edge_map = NULL;
reshape_smooth_context->reshape_subdiv = NULL;
reshape_smooth_context->base_surface_grids = NULL;
reshape_smooth_context->smoothing_type = mode;
}
static void context_free_geometry(MultiresReshapeSmoothContext *reshape_smooth_context)
@ -474,12 +486,14 @@ static void context_free(MultiresReshapeSmoothContext *reshape_smooth_context)
static bool foreach_topology_info(const SubdivForeachContext *foreach_context,
const int num_vertices,
const int UNUSED(num_edges),
const int num_edges,
const int num_loops,
const int num_polygons)
{
MultiresReshapeSmoothContext *reshape_smooth_context = foreach_context->user_data;
const int max_edges = reshape_smooth_context->geometry.max_edges;
const int max_edges = reshape_smooth_context->smoothing_type == MULTIRES_SUBDIVIDE_LINEAR ?
num_edges :
reshape_smooth_context->geometry.max_edges;
/* NOTE: Calloc so the counters are re-set to 0 "for free". */
reshape_smooth_context->geometry.num_vertices = num_vertices;
@ -672,6 +686,22 @@ static void foreach_vertex_of_loose_edge(const struct SubdivForeachContext *fore
}
}
static void store_edge(MultiresReshapeSmoothContext *reshape_smooth_context,
const int subdiv_v1,
const int subdiv_v2,
const char crease)
{
/* This is a bit overhead to use atomics in such a simple function called from many threads,
* but this allows to save quite measurable amount of memory. */
const int edge_index = atomic_fetch_and_add_z(&reshape_smooth_context->geometry.num_edges, 1);
BLI_assert(edge_index < reshape_smooth_context->geometry.max_edges);
Edge *edge = &reshape_smooth_context->geometry.edges[edge_index];
edge->v1 = subdiv_v1;
edge->v2 = subdiv_v2;
edge->sharpness = BKE_subdiv_edge_crease_to_sharpness_char(crease);
}
static void foreach_edge(const struct SubdivForeachContext *foreach_context,
void *UNUSED(tls),
const int coarse_edge_index,
@ -682,8 +712,15 @@ static void foreach_edge(const struct SubdivForeachContext *foreach_context,
MultiresReshapeSmoothContext *reshape_smooth_context = foreach_context->user_data;
const MultiresReshapeContext *reshape_context = reshape_smooth_context->reshape_context;
/* Ignore all inner face edges as they have sharpness of zero. */
if (coarse_edge_index == ORIGINDEX_NONE) {
if (reshape_smooth_context->smoothing_type == MULTIRES_SUBDIVIDE_LINEAR) {
store_edge(reshape_smooth_context, subdiv_v1, subdiv_v2, (char)255);
return;
}
/* Ignore all inner face edges as they have sharpness of zero when using Catmull-Clark mode. In
* simple mode, all edges have maximum sharpness, so they can't be skipped. */
if (coarse_edge_index == ORIGINDEX_NONE &&
reshape_smooth_context->smoothing_type != MULTIRES_SUBDIVIDE_SIMPLE) {
return;
}
/* Ignore all loose edges as well, as they are not communicated to the OpenSubdiv. */
@ -697,16 +734,7 @@ static void foreach_edge(const struct SubdivForeachContext *foreach_context,
if (crease == 0) {
return;
}
/* This is a bit overhead to use atomics in such a simple function called from many threads,
* but this allows to save quite measurable amount of memory. */
const int edge_index = atomic_fetch_and_add_z(&reshape_smooth_context->geometry.num_edges, 1);
BLI_assert(edge_index < reshape_smooth_context->geometry.max_edges);
Edge *edge = &reshape_smooth_context->geometry.edges[edge_index];
edge->v1 = subdiv_v1;
edge->v2 = subdiv_v2;
edge->sharpness = BKE_subdiv_edge_crease_to_sharpness_char(crease);
store_edge(reshape_smooth_context, subdiv_v1, subdiv_v2, crease);
}
static void geometry_init_loose_information(MultiresReshapeSmoothContext *reshape_smooth_context)
@ -1212,7 +1240,12 @@ void multires_reshape_smooth_object_grids_with_details(
}
MultiresReshapeSmoothContext reshape_smooth_context;
context_init(&reshape_smooth_context, reshape_context);
if (reshape_context->mmd->simple) {
context_init(&reshape_smooth_context, reshape_context, MULTIRES_SUBDIVIDE_SIMPLE);
}
else {
context_init(&reshape_smooth_context, reshape_context, MULTIRES_SUBDIVIDE_CATMULL_CLARK);
}
geometry_create(&reshape_smooth_context);
@ -1228,7 +1261,8 @@ void multires_reshape_smooth_object_grids_with_details(
context_free(&reshape_smooth_context);
}
void multires_reshape_smooth_object_grids(const MultiresReshapeContext *reshape_context)
void multires_reshape_smooth_object_grids(const MultiresReshapeContext *reshape_context,
const eMultiresSubdivideModeType mode)
{
const int level_difference = (reshape_context->top.level - reshape_context->reshape.level);
if (level_difference == 0) {
@ -1237,7 +1271,7 @@ void multires_reshape_smooth_object_grids(const MultiresReshapeContext *reshape_
}
MultiresReshapeSmoothContext reshape_smooth_context;
context_init(&reshape_smooth_context, reshape_context);
context_init(&reshape_smooth_context, reshape_context, mode);
geometry_create(&reshape_smooth_context);

View File

@ -0,0 +1,106 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2020 Blender Foundation.
* All rights reserved.
*/
/** \file
* \ingroup bke
*/
#include "MEM_guardedalloc.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_scene_types.h"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_multires.h"
#include "BKE_subdiv.h"
#include "BKE_subsurf.h"
#include "BLI_math_vector.h"
#include "DEG_depsgraph_query.h"
#include "multires_reshape.h"
void multires_subdivide_create_object_space_linear_grids(Mesh *mesh)
{
MDisps *mdisps = CustomData_get_layer(&mesh->ldata, CD_MDISPS);
const int totpoly = mesh->totpoly;
for (int p = 0; p < totpoly; p++) {
MPoly *poly = &mesh->mpoly[p];
float poly_center[3];
BKE_mesh_calc_poly_center(poly, &mesh->mloop[poly->loopstart], mesh->mvert, poly_center);
for (int l = 0; l < poly->totloop; l++) {
const int loop_index = poly->loopstart + l;
float(*disps)[3] = mdisps[loop_index].disps;
mdisps[loop_index].totdisp = 4;
mdisps[loop_index].level = 1;
int prev_loop_index = l - 1 >= 0 ? loop_index - 1 : loop_index + poly->totloop - 1;
int next_loop_index = l + 1 < poly->totloop ? loop_index + 1 : poly->loopstart;
MLoop *loop = &mesh->mloop[loop_index];
MLoop *loop_next = &mesh->mloop[next_loop_index];
MLoop *loop_prev = &mesh->mloop[prev_loop_index];
copy_v3_v3(disps[0], poly_center);
mid_v3_v3v3(disps[1], mesh->mvert[loop->v].co, mesh->mvert[loop_next->v].co);
mid_v3_v3v3(disps[2], mesh->mvert[loop->v].co, mesh->mvert[loop_prev->v].co);
copy_v3_v3(disps[3], mesh->mvert[loop->v].co);
}
}
}
void multires_subdivide_create_tangent_displacement_linear_grids(Object *object,
MultiresModifierData *mmd)
{
Mesh *coarse_mesh = object->data;
multires_force_sculpt_rebuild(object);
MultiresReshapeContext reshape_context;
const int new_top_level = mmd->totlvl + 1;
const bool has_mdisps = CustomData_has_layer(&coarse_mesh->ldata, CD_MDISPS);
if (!has_mdisps) {
CustomData_add_layer(&coarse_mesh->ldata, CD_MDISPS, CD_CALLOC, NULL, coarse_mesh->totloop);
}
if (new_top_level == 1) {
/* No MDISPS. Create new grids for level 1 using the edges mid point and poly centers. */
multires_reshape_ensure_grids(coarse_mesh, 1);
multires_subdivide_create_object_space_linear_grids(coarse_mesh);
}
/* Convert the new grids to tangent displacement. */
multires_set_tot_level(object, mmd, new_top_level);
if (!multires_reshape_context_create_from_subdivide(
&reshape_context, object, mmd, new_top_level)) {
return;
}
multires_reshape_object_grids_to_tangent_displacement(&reshape_context);
multires_reshape_context_free(&reshape_context);
}

View File

@ -1433,6 +1433,25 @@ void OBJECT_OT_multires_higher_levels_delete(wmOperatorType *ot)
/** \name Multires Subdivide Operator
* \{ */
static EnumPropertyItem prop_multires_subdivide_mode_type[] = {
{MULTIRES_SUBDIVIDE_CATMULL_CLARK,
"CATMULL_CLARK",
0,
"Catmull-Clark",
"Create a new level using Catmull-Clark subdivisions"},
{MULTIRES_SUBDIVIDE_SIMPLE,
"SIMPLE",
0,
"Simple",
"Create a new level using simple subdivisions"},
{MULTIRES_SUBDIVIDE_LINEAR,
"LINEAR",
0,
"Linear",
"Create a new level using linear interpolation of the sculpted displacement"},
{0, NULL, 0, NULL, NULL},
};
static int multires_subdivide_exec(bContext *C, wmOperator *op)
{
Object *object = ED_object_active_context(C);
@ -1443,7 +1462,9 @@ static int multires_subdivide_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
multiresModifier_subdivide(object, mmd);
const eMultiresSubdivideModeType subdivide_mode = (eMultiresSubdivideModeType)(
RNA_enum_get(op->ptr, "mode"));
multiresModifier_subdivide(object, mmd, subdivide_mode);
ED_object_iter_other(
CTX_data_main(C), object, true, ED_object_multires_update_totlevels_cb, &mmd->totlvl);
@ -1482,6 +1503,12 @@ void OBJECT_OT_multires_subdivide(wmOperatorType *ot)
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
edit_modifier_properties(ot);
RNA_def_enum(ot->srna,
"mode",
prop_multires_subdivide_mode_type,
MULTIRES_SUBDIVIDE_CATMULL_CLARK,
"Subdivision Mode",
"How the mesh is going to be subdivided to create a new level");
}
/** \} */