Voxel Remesh: Fix poles and preserve volume
This commit fixes most of the issues we currently have in the voxel remesher. Mesh volume is preserved when doing multiple iterations, so the sculpt won't shrink and smooth each time you run the remesher. Mesh topology is much better, fixing most issues related to mask extraction and other topology based operations. Reviewed By: jbakker Differential Revision: https://developer.blender.org/D5863
This commit is contained in:
		@@ -476,7 +476,9 @@ class DATA_PT_remesh(MeshButtonsPanel, Panel):
 | 
			
		||||
        col = layout.column()
 | 
			
		||||
        if (mesh.remesh_mode == 'VOXEL'):
 | 
			
		||||
            col.prop(mesh, "remesh_voxel_size")
 | 
			
		||||
            col.prop(mesh, "remesh_fix_poles")
 | 
			
		||||
            col.prop(mesh, "remesh_smooth_normals")
 | 
			
		||||
            col.prop(mesh, "remesh_preserve_volume")
 | 
			
		||||
            col.prop(mesh, "remesh_preserve_paint_mask")
 | 
			
		||||
            col.operator("object.voxel_remesh", text="Voxel Remesh")
 | 
			
		||||
        else:
 | 
			
		||||
 
 | 
			
		||||
@@ -1234,7 +1234,9 @@ class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel):
 | 
			
		||||
        col = layout.column()
 | 
			
		||||
        mesh = context.active_object.data
 | 
			
		||||
        col.prop(mesh, "remesh_voxel_size")
 | 
			
		||||
        col.prop(mesh, "remesh_fix_poles")
 | 
			
		||||
        col.prop(mesh, "remesh_smooth_normals")
 | 
			
		||||
        col.prop(mesh, "remesh_preserve_volume")
 | 
			
		||||
        col.prop(mesh, "remesh_preserve_paint_mask")
 | 
			
		||||
        col.operator("object.voxel_remesh", text="Remesh")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,8 @@ struct Mesh *BKE_mesh_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLeve
 | 
			
		||||
                                                              double adaptivity,
 | 
			
		||||
                                                              bool relax_disoriented_triangles);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
struct Mesh *BKE_mesh_remesh_voxel_fix_poles(struct Mesh *mesh);
 | 
			
		||||
struct Mesh *BKE_mesh_remesh_voxel_to_mesh_nomain(struct Mesh *mesh, float voxel_size);
 | 
			
		||||
struct Mesh *BKE_mesh_remesh_quadriflow_to_mesh_nomain(struct Mesh *mesh,
 | 
			
		||||
                                                       int target_faces,
 | 
			
		||||
 
 | 
			
		||||
@@ -115,6 +115,11 @@ void BKE_shrinkwrap_mesh_nearest_surface_deform(struct bContext *C,
 | 
			
		||||
                                                struct Object *ob_source,
 | 
			
		||||
                                                struct Object *ob_target);
 | 
			
		||||
 | 
			
		||||
/* Used in object_remesh.c to preserve the details and volume in the voxel remesher */
 | 
			
		||||
void BKE_shrinkwrap_remesh_target_project(struct Mesh *src_me,
 | 
			
		||||
                                          struct Mesh *target_me,
 | 
			
		||||
                                          struct Object *ob_target);
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * This function casts a ray in the given BVHTree.
 | 
			
		||||
 * but it takes into consideration the space_transform, that is:
 | 
			
		||||
 
 | 
			
		||||
@@ -38,6 +38,7 @@
 | 
			
		||||
#include "DNA_mesh_types.h"
 | 
			
		||||
#include "DNA_meshdata_types.h"
 | 
			
		||||
 | 
			
		||||
#include "BKE_editmesh.h"
 | 
			
		||||
#include "BKE_mesh.h"
 | 
			
		||||
#include "BKE_mesh_runtime.h"
 | 
			
		||||
#include "BKE_library.h"
 | 
			
		||||
@@ -45,6 +46,8 @@
 | 
			
		||||
#include "BKE_bvhutils.h"
 | 
			
		||||
#include "BKE_mesh_remesh_voxel.h" /* own include */
 | 
			
		||||
 | 
			
		||||
#include "bmesh_tools.h"
 | 
			
		||||
 | 
			
		||||
#ifdef WITH_OPENVDB
 | 
			
		||||
#  include "openvdb_capi.h"
 | 
			
		||||
#endif
 | 
			
		||||
@@ -348,3 +351,106 @@ void BKE_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
 | 
			
		||||
  }
 | 
			
		||||
  free_bvhtree_from_mesh(&bvhtree);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Mesh *BKE_mesh_remesh_voxel_fix_poles(struct Mesh *mesh)
 | 
			
		||||
{
 | 
			
		||||
  const BMAllocTemplate allocsize = BMALLOC_TEMPLATE_FROM_ME(mesh);
 | 
			
		||||
  BMesh *bm;
 | 
			
		||||
  bm = BM_mesh_create(&allocsize,
 | 
			
		||||
                      &((struct BMeshCreateParams){
 | 
			
		||||
                          .use_toolflags = true,
 | 
			
		||||
                      }));
 | 
			
		||||
 | 
			
		||||
  BM_mesh_bm_from_me(bm,
 | 
			
		||||
                     mesh,
 | 
			
		||||
                     (&(struct BMeshFromMeshParams){
 | 
			
		||||
                         .calc_face_normal = true,
 | 
			
		||||
                     }));
 | 
			
		||||
 | 
			
		||||
  BMVert *v;
 | 
			
		||||
  BMEdge *ed, *ed_next;
 | 
			
		||||
  BMFace *f, *f_next;
 | 
			
		||||
  BMIter iter_a, iter_b;
 | 
			
		||||
 | 
			
		||||
  /* Merge 3 edge poles vertices that exist in the same face */
 | 
			
		||||
  BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
 | 
			
		||||
  BM_ITER_MESH_MUTABLE (f, f_next, &iter_a, bm, BM_FACES_OF_MESH) {
 | 
			
		||||
    BMVert *v1, *v2;
 | 
			
		||||
    v1 = NULL;
 | 
			
		||||
    v2 = NULL;
 | 
			
		||||
    BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
 | 
			
		||||
      if (BM_vert_edge_count(v) == 3) {
 | 
			
		||||
        if (v1) {
 | 
			
		||||
          v2 = v;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          v1 = v;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (v1 && v2 && (v1 != v2) && !BM_edge_exists(v1, v2)) {
 | 
			
		||||
      BM_face_kill(bm, f);
 | 
			
		||||
      BMEdge *e = BM_edge_create(bm, v1, v2, NULL, BM_CREATE_NOP);
 | 
			
		||||
      BM_elem_flag_set(e, BM_ELEM_TAG, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BM_ITER_MESH_MUTABLE (ed, ed_next, &iter_a, bm, BM_EDGES_OF_MESH) {
 | 
			
		||||
    if (BM_elem_flag_test(ed, BM_ELEM_TAG)) {
 | 
			
		||||
      float co[3];
 | 
			
		||||
      mid_v3_v3v3(co, ed->v1->co, ed->v2->co);
 | 
			
		||||
      BMVert *vc = BM_edge_collapse(bm, ed, ed->v1, true, true);
 | 
			
		||||
      copy_v3_v3(vc->co, co);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* Delete faces with a 3 edge pole in all their vertices */
 | 
			
		||||
  BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_TAG, false);
 | 
			
		||||
  BM_ITER_MESH (f, &iter_a, bm, BM_FACES_OF_MESH) {
 | 
			
		||||
    bool dissolve = true;
 | 
			
		||||
    BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
 | 
			
		||||
      if (BM_vert_edge_count(v) != 3) {
 | 
			
		||||
        dissolve = false;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (dissolve) {
 | 
			
		||||
      BM_ITER_ELEM (v, &iter_b, f, BM_VERTS_OF_FACE) {
 | 
			
		||||
        BM_elem_flag_set(v, BM_ELEM_TAG, true);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  BM_mesh_delete_hflag_context(bm, BM_ELEM_TAG, DEL_VERTS);
 | 
			
		||||
 | 
			
		||||
  BM_ITER_MESH (ed, &iter_a, bm, BM_EDGES_OF_MESH) {
 | 
			
		||||
    if (BM_edge_face_count(ed) != 2) {
 | 
			
		||||
      BM_elem_flag_set(ed, BM_ELEM_TAG, true);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  BM_mesh_edgenet(bm, false, true);
 | 
			
		||||
 | 
			
		||||
  /* Smooth the result */
 | 
			
		||||
  for (int i = 0; i < 4; i++) {
 | 
			
		||||
    BM_ITER_MESH (v, &iter_a, bm, BM_VERTS_OF_MESH) {
 | 
			
		||||
      float co[3];
 | 
			
		||||
      zero_v3(co);
 | 
			
		||||
      BM_ITER_ELEM (ed, &iter_b, v, BM_EDGES_OF_VERT) {
 | 
			
		||||
        BMVert *vert = BM_edge_other_vert(ed, v);
 | 
			
		||||
        add_v3_v3(co, vert->co);
 | 
			
		||||
      }
 | 
			
		||||
      mul_v3_fl(co, 1.0f / (float)BM_vert_edge_count(v));
 | 
			
		||||
      mid_v3_v3v3(v->co, v->co, co);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BM_mesh_elem_hflag_disable_all(bm, BM_VERT | BM_EDGE | BM_FACE, BM_ELEM_SELECT, false);
 | 
			
		||||
 | 
			
		||||
  Mesh *result = BKE_mesh_from_bmesh_nomain(bm,
 | 
			
		||||
                                            (&(struct BMeshToMeshParams){
 | 
			
		||||
                                                .calc_object_remap = false,
 | 
			
		||||
                                            }),
 | 
			
		||||
                                            mesh);
 | 
			
		||||
 | 
			
		||||
  BKE_id_free(NULL, mesh);
 | 
			
		||||
  BM_mesh_free(bm);
 | 
			
		||||
  return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1506,3 +1506,37 @@ void BKE_shrinkwrap_mesh_nearest_surface_deform(struct bContext *C,
 | 
			
		||||
 | 
			
		||||
  MEM_freeN(vertexCos);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BKE_shrinkwrap_remesh_target_project(Mesh *src_me, Mesh *target_me, Object *ob_target)
 | 
			
		||||
{
 | 
			
		||||
  ShrinkwrapModifierData ssmd = {0};
 | 
			
		||||
  int totvert;
 | 
			
		||||
 | 
			
		||||
  ssmd.target = ob_target;
 | 
			
		||||
  ssmd.shrinkType = MOD_SHRINKWRAP_TARGET_PROJECT;
 | 
			
		||||
  ssmd.shrinkMode = MOD_SHRINKWRAP_ON_SURFACE;
 | 
			
		||||
  ssmd.keepDist = 0.0f;
 | 
			
		||||
 | 
			
		||||
  float(*vertexCos)[3] = BKE_mesh_vert_coords_alloc(src_me, &totvert);
 | 
			
		||||
 | 
			
		||||
  ShrinkwrapCalcData calc = NULL_ShrinkwrapCalcData;
 | 
			
		||||
 | 
			
		||||
  calc.smd = &ssmd;
 | 
			
		||||
  calc.numVerts = src_me->totvert;
 | 
			
		||||
  calc.vertexCos = vertexCos;
 | 
			
		||||
  calc.vgroup = -1;
 | 
			
		||||
  calc.target = target_me;
 | 
			
		||||
  calc.keepDist = ssmd.keepDist;
 | 
			
		||||
  BLI_SPACE_TRANSFORM_SETUP(&calc.local2target, ob_target, ob_target);
 | 
			
		||||
 | 
			
		||||
  ShrinkwrapTreeData tree;
 | 
			
		||||
  if (BKE_shrinkwrap_init_tree(&tree, calc.target, ssmd.shrinkType, ssmd.shrinkMode, false)) {
 | 
			
		||||
    calc.tree = &tree;
 | 
			
		||||
    TIMEIT_BENCH(shrinkwrap_calc_nearest_surface_point(&calc), deform_surface);
 | 
			
		||||
    BKE_shrinkwrap_free_tree(&tree);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BKE_mesh_vert_coords_apply(src_me, vertexCos);
 | 
			
		||||
 | 
			
		||||
  MEM_freeN(vertexCos);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,10 +42,13 @@
 | 
			
		||||
#include "BKE_global.h"
 | 
			
		||||
#include "BKE_main.h"
 | 
			
		||||
#include "BKE_mesh.h"
 | 
			
		||||
#include "BKE_mesh_runtime.h"
 | 
			
		||||
#include "BKE_library.h"
 | 
			
		||||
#include "BKE_object.h"
 | 
			
		||||
#include "BKE_paint.h"
 | 
			
		||||
#include "BKE_report.h"
 | 
			
		||||
#include "BKE_scene.h"
 | 
			
		||||
#include "BKE_shrinkwrap.h"
 | 
			
		||||
#include "BKE_customdata.h"
 | 
			
		||||
#include "BKE_mesh_remesh_voxel.h"
 | 
			
		||||
 | 
			
		||||
@@ -112,23 +115,22 @@ static int voxel_remesh_exec(bContext *C, wmOperator *op)
 | 
			
		||||
    return OPERATOR_CANCELLED;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Mesh *obj_mesh_copy = NULL;
 | 
			
		||||
  if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
 | 
			
		||||
    obj_mesh_copy = BKE_mesh_new_nomain_from_template(mesh, mesh->totvert, 0, 0, 0, 0);
 | 
			
		||||
    CustomData_copy(
 | 
			
		||||
        &mesh->vdata, &obj_mesh_copy->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, mesh->totvert);
 | 
			
		||||
    for (int i = 0; i < mesh->totvert; i++) {
 | 
			
		||||
      copy_v3_v3(obj_mesh_copy->mvert[i].co, mesh->mvert[i].co);
 | 
			
		||||
  if (mesh->flag & ME_REMESH_FIX_POLES) {
 | 
			
		||||
    new_mesh = BKE_mesh_remesh_voxel_fix_poles(new_mesh);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (mesh->flag & ME_REMESH_REPROJECT_VOLUME) {
 | 
			
		||||
    BKE_mesh_runtime_clear_geometry(mesh);
 | 
			
		||||
    BKE_shrinkwrap_remesh_target_project(new_mesh, mesh, ob);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
 | 
			
		||||
    BKE_mesh_runtime_clear_geometry(mesh);
 | 
			
		||||
    BKE_remesh_reproject_paint_mask(new_mesh, mesh);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
 | 
			
		||||
 | 
			
		||||
  if (mesh->flag & ME_REMESH_REPROJECT_PAINT_MASK) {
 | 
			
		||||
    BKE_remesh_reproject_paint_mask(mesh, obj_mesh_copy);
 | 
			
		||||
    BKE_mesh_free(obj_mesh_copy);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (mesh->flag & ME_REMESH_SMOOTH_NORMALS) {
 | 
			
		||||
    BKE_mesh_smooth_flag_set(ob->data, true);
 | 
			
		||||
  }
 | 
			
		||||
@@ -264,23 +266,13 @@ static void quadriflow_start_job(void *customdata, short *stop, short *do_update
 | 
			
		||||
    ED_sculpt_undo_geometry_begin(ob);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Mesh *obj_mesh_copy = NULL;
 | 
			
		||||
  if (qj->preserve_paint_mask) {
 | 
			
		||||
    obj_mesh_copy = BKE_mesh_new_nomain_from_template(mesh, mesh->totvert, 0, 0, 0, 0);
 | 
			
		||||
    CustomData_copy(
 | 
			
		||||
        &mesh->vdata, &obj_mesh_copy->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, mesh->totvert);
 | 
			
		||||
    for (int i = 0; i < mesh->totvert; i++) {
 | 
			
		||||
      copy_v3_v3(obj_mesh_copy->mvert[i].co, mesh->mvert[i].co);
 | 
			
		||||
    }
 | 
			
		||||
    BKE_mesh_runtime_clear_geometry(mesh);
 | 
			
		||||
    BKE_remesh_reproject_paint_mask(new_mesh, mesh);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
 | 
			
		||||
 | 
			
		||||
  if (qj->preserve_paint_mask) {
 | 
			
		||||
    BKE_remesh_reproject_paint_mask(mesh, obj_mesh_copy);
 | 
			
		||||
    BKE_mesh_free(obj_mesh_copy);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (qj->smooth_normals) {
 | 
			
		||||
    BKE_mesh_smooth_flag_set(ob->data, true);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@
 | 
			
		||||
    .smoothresh = DEG2RADF(30), \
 | 
			
		||||
    .texflag = ME_AUTOSPACE, \
 | 
			
		||||
    .remesh_voxel_size = 0.1f, \
 | 
			
		||||
    .flag = ME_REMESH_FIX_POLES | ME_REMESH_REPROJECT_VOLUME, \
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
/** \} */
 | 
			
		||||
 
 | 
			
		||||
@@ -251,6 +251,8 @@ enum {
 | 
			
		||||
  ME_SCULPT_DYNAMIC_TOPOLOGY = 1 << 10,
 | 
			
		||||
  ME_REMESH_SMOOTH_NORMALS = 1 << 11,
 | 
			
		||||
  ME_REMESH_REPROJECT_PAINT_MASK = 1 << 12,
 | 
			
		||||
  ME_REMESH_FIX_POLES = 1 << 13,
 | 
			
		||||
  ME_REMESH_REPROJECT_VOLUME = 1 << 14,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* me->cd_flag */
 | 
			
		||||
 
 | 
			
		||||
@@ -3000,6 +3000,19 @@ static void rna_def_mesh(BlenderRNA *brna)
 | 
			
		||||
  RNA_def_property_ui_text(prop, "Smooth Normals", "Smooth the normals of the remesher result");
 | 
			
		||||
  RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
 | 
			
		||||
 | 
			
		||||
  prop = RNA_def_property(srna, "remesh_fix_poles", PROP_BOOLEAN, PROP_NONE);
 | 
			
		||||
  RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_FIX_POLES);
 | 
			
		||||
  RNA_def_property_ui_text(prop, "Fix Poles", "Produces less poles and a better topology flow");
 | 
			
		||||
  RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
 | 
			
		||||
 | 
			
		||||
  prop = RNA_def_property(srna, "remesh_preserve_volume", PROP_BOOLEAN, PROP_NONE);
 | 
			
		||||
  RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_REPROJECT_VOLUME);
 | 
			
		||||
  RNA_def_property_ui_text(
 | 
			
		||||
      prop,
 | 
			
		||||
      "Preserve Volume",
 | 
			
		||||
      "Projects the mesh to preserve the volume and details of the original mesh");
 | 
			
		||||
  RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
 | 
			
		||||
 | 
			
		||||
  prop = RNA_def_property(srna, "remesh_preserve_paint_mask", PROP_BOOLEAN, PROP_NONE);
 | 
			
		||||
  RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_REPROJECT_PAINT_MASK);
 | 
			
		||||
  RNA_def_property_boolean_default(prop, false);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user