OpenVDB: Voxel Remesher
authorPablo Dobarro <pablodp606@gmail.com>
Wed, 14 Aug 2019 16:37:46 +0000 (18:37 +0200)
committerPablo Dobarro <pablodp606@gmail.com>
Wed, 14 Aug 2019 16:58:19 +0000 (18:58 +0200)
The voxel remesher introduces a new workflow for sculpting without any of the limitations of Dyntopo (no geometry errors or performance penalty when blocking shapes). It is also useful for simulations and 3D printing.

This commit includes:
- Voxel remesh operator, voxel size mesh property and general remesh flags.
- Paint mask reprojection.
- Geometry undo/redo for sculpt mode. This should support remesh operations as well as future tools that modify the topology of the sculpt in a single step, like trimming tools or mesh insert brushes.
- UI changes in the sculpt topbar and the mesh properties pannel.

Reviewed By: brecht

Differential Revision: https://developer.blender.org/D5407

17 files changed:
release/scripts/startup/bl_ui/properties_data_mesh.py
release/scripts/startup/bl_ui/space_view3d_toolbar.py
source/blender/blenkernel/BKE_remesh.h [new file with mode: 0644]
source/blender/blenkernel/CMakeLists.txt
source/blender/blenkernel/intern/mesh.c
source/blender/blenkernel/intern/remesh.c [new file with mode: 0644]
source/blender/blenloader/intern/versioning_280.c
source/blender/editors/include/ED_sculpt.h
source/blender/editors/object/CMakeLists.txt
source/blender/editors/object/object_intern.h
source/blender/editors/object/object_ops.c
source/blender/editors/object/object_remesh.c [new file with mode: 0644]
source/blender/editors/sculpt_paint/sculpt.c
source/blender/editors/sculpt_paint/sculpt_intern.h
source/blender/editors/sculpt_paint/sculpt_undo.c
source/blender/makesdna/DNA_mesh_types.h
source/blender/makesrna/intern/rna_mesh.c

index 63e4d44eada3a70c738ff156f13534ce2cce8994..47c90199031756dcbf7a2cb02c5d94287b51f3d8 100644 (file)
@@ -459,6 +459,22 @@ class DATA_PT_vertex_colors(MeshButtonsPanel, Panel):
         col.operator("mesh.vertex_color_add", icon='ADD', text="")
         col.operator("mesh.vertex_color_remove", icon='REMOVE', text="")
 
+class DATA_PT_remesh(MeshButtonsPanel, Panel):
+    bl_label = "Remesh"
+    bl_options = {'DEFAULT_CLOSED'}
+    COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_EEVEE', 'BLENDER_WORKBENCH'}
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        col = layout.column()
+
+        mesh = context.mesh
+        col.prop(mesh, "remesh_voxel_size")
+        col.prop(mesh, "remesh_smooth_normals")
+        col.prop(mesh, "remesh_preserve_paint_mask")
+        col.operator("object.voxel_remesh", text="Voxel Remesh")
+
 
 class DATA_PT_customdata(MeshButtonsPanel, Panel):
     bl_label = "Geometry Data"
@@ -512,6 +528,7 @@ classes = (
     DATA_PT_normals,
     DATA_PT_normals_auto_smooth,
     DATA_PT_texture_space,
+    DATA_PT_remesh,
     DATA_PT_customdata,
     DATA_PT_custom_props_mesh,
 )
index cd18d85724213598d283ce487db84fbb6fdf1a45..5246d8fa864a7ee6843d9c146fb41b6952880e36 100644 (file)
@@ -1139,8 +1139,38 @@ class VIEW3D_PT_sculpt_dyntopo_remesh(Panel, View3DPaintPanel):
             col = flow.column()
             col.operator("sculpt.detail_flood_fill")
 
-# TODO, move to space_view3d.py
+class VIEW3D_PT_sculpt_voxel_remesh(Panel, View3DPaintPanel):
+    bl_context = ".sculpt_mode"  # dot on purpose (access from topbar)
+    bl_label = "Remesh"
+    bl_options = {'DEFAULT_CLOSED'}
+    bl_ui_units_x = 12
 
+    @classmethod
+    def poll(cls, context):
+        return (context.sculpt_object and context.tool_settings.sculpt)
+
+    def draw_header(self, context):
+        is_popover = self.is_popover
+        layout = self.layout
+        layout.operator(
+            "object.voxel_remesh",
+            text="",
+            emboss=is_popover,
+        )
+
+    def draw(self, context):
+        layout = self.layout
+        layout.use_property_split = True
+        layout.use_property_decorate = False
+
+        col = layout.column()
+        mesh = context.active_object.data
+        col.prop(mesh, "remesh_voxel_size")
+        col.prop(mesh, "remesh_smooth_normals")
+        col.prop(mesh, "remesh_preserve_paint_mask")
+        col.operator("object.voxel_remesh", text="Remesh")
+
+# TODO, move to space_view3d.py
 
 class VIEW3D_PT_sculpt_options(Panel, View3DPaintPanel):
     bl_context = ".sculpt_mode"  # dot on purpose (access from topbar)
@@ -2152,6 +2182,7 @@ classes = (
     VIEW3D_PT_sculpt_options,
     VIEW3D_PT_sculpt_options_unified,
     VIEW3D_PT_sculpt_options_gravity,
+    VIEW3D_PT_sculpt_voxel_remesh,
     VIEW3D_PT_tools_weightpaint_symmetry,
     VIEW3D_PT_tools_weightpaint_symmetry_for_topbar,
     VIEW3D_PT_tools_weightpaint_options,
diff --git a/source/blender/blenkernel/BKE_remesh.h b/source/blender/blenkernel/BKE_remesh.h
new file mode 100644 (file)
index 0000000..fd99af3
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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) 2019 by Blender Foundation
+ * All rights reserved.
+ */
+#ifndef __BKE_REMESH_H__
+#define __BKE_REMESH_H__
+
+/** \file
+ * \ingroup bke
+ */
+
+#include "DNA_mesh_types.h"
+
+#ifdef WITH_OPENVDB
+#  include "openvdb_capi.h"
+#endif
+
+/* OpenVDB Voxel Remesher */
+#ifdef WITH_OPENVDB
+struct OpenVDBLevelSet *BKE_remesh_voxel_ovdb_mesh_to_level_set_create(
+    Mesh *mesh, struct OpenVDBTransform *transform);
+Mesh *BKE_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLevelSet *level_set,
+                                                  double isovalue,
+                                                  double adaptivity,
+                                                  bool relax_disoriented_triangles);
+#endif
+Mesh *BKE_remesh_voxel_to_mesh_nomain(Mesh *mesh, float voxel_size);
+
+/* Data reprojection functions */
+void BKE_remesh_reproject_paint_mask(Mesh *target, Mesh *source);
+
+#endif /* __BKE_REMESH_H__ */
index d9bd87d97b57d8e49ca44b5b9e6192f10ba2d6d5..1f125b93b3f016d88100da17da0093154afe279e 100644 (file)
@@ -178,6 +178,7 @@ set(SRC
   intern/pbvh.c
   intern/pbvh_bmesh.c
   intern/pointcache.c
+  intern/remesh.c
   intern/report.c
   intern/rigidbody.c
   intern/scene.c
@@ -317,6 +318,7 @@ set(SRC
   BKE_particle.h
   BKE_pbvh.h
   BKE_pointcache.h
+  BKE_remesh.h
   BKE_report.h
   BKE_rigidbody.h
   BKE_scene.h
index 7e755e54eaa7d8bf999a9029923bbe72252032d3..4f39d34574e47f615132492c8663a073895dee0e 100644 (file)
@@ -536,6 +536,7 @@ void BKE_mesh_init(Mesh *me)
   me->size[0] = me->size[1] = me->size[2] = 1.0;
   me->smoothresh = DEG2RADF(30);
   me->texflag = ME_AUTOSPACE;
+  me->remesh_voxel_size = 0.1f;
 
   CustomData_reset(&me->vdata);
   CustomData_reset(&me->edata);
diff --git a/source/blender/blenkernel/intern/remesh.c b/source/blender/blenkernel/intern/remesh.c
new file mode 100644 (file)
index 0000000..4dd38c9
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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) 2019 by Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <time.h>
+#include <float.h>
+#include <ctype.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_object_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_library.h"
+#include "BKE_customdata.h"
+#include "BKE_bvhutils.h"
+#include "BKE_remesh.h"
+
+#ifdef WITH_OPENVDB
+#  include "openvdb_capi.h"
+#endif
+
+#ifdef WITH_OPENVDB
+struct OpenVDBLevelSet *BKE_remesh_voxel_ovdb_mesh_to_level_set_create(
+    Mesh *mesh, struct OpenVDBTransform *transform)
+{
+  BKE_mesh_runtime_looptri_recalc(mesh);
+  const MLoopTri *looptri = BKE_mesh_runtime_looptri_ensure(mesh);
+  MVertTri *verttri = MEM_callocN(sizeof(*verttri) * BKE_mesh_runtime_looptri_len(mesh),
+                                  "remesh_looptri");
+  BKE_mesh_runtime_verttri_from_looptri(
+      verttri, mesh->mloop, looptri, BKE_mesh_runtime_looptri_len(mesh));
+
+  unsigned int totfaces = BKE_mesh_runtime_looptri_len(mesh);
+  unsigned int totverts = mesh->totvert;
+  float *verts = (float *)MEM_malloc_arrayN(totverts * 3, sizeof(float), "remesh_input_verts");
+  unsigned int *faces = (unsigned int *)MEM_malloc_arrayN(
+      totfaces * 3, sizeof(unsigned int), "remesh_intput_faces");
+
+  for (unsigned int i = 0; i < totverts; i++) {
+    MVert *mvert = &mesh->mvert[i];
+    verts[i * 3] = mvert->co[0];
+    verts[i * 3 + 1] = mvert->co[1];
+    verts[i * 3 + 2] = mvert->co[2];
+  }
+
+  for (unsigned int i = 0; i < totfaces; i++) {
+    MVertTri *vt = &verttri[i];
+    faces[i * 3] = vt->tri[0];
+    faces[i * 3 + 1] = vt->tri[1];
+    faces[i * 3 + 2] = vt->tri[2];
+  }
+
+  struct OpenVDBLevelSet *level_set = OpenVDBLevelSet_create(false, NULL);
+  OpenVDBLevelSet_mesh_to_level_set(level_set, verts, faces, totverts, totfaces, transform);
+
+  MEM_freeN(verts);
+  MEM_freeN(faces);
+  MEM_freeN(verttri);
+
+  return level_set;
+}
+
+Mesh *BKE_remesh_voxel_ovdb_volume_to_mesh_nomain(struct OpenVDBLevelSet *level_set,
+                                                  double isovalue,
+                                                  double adaptivity,
+                                                  bool relax_disoriented_triangles)
+{
+#  ifdef WITH_OPENVDB
+  struct OpenVDBVolumeToMeshData output_mesh;
+  OpenVDBLevelSet_volume_to_mesh(
+      level_set, &output_mesh, isovalue, adaptivity, relax_disoriented_triangles);
+#  endif
+
+  Mesh *mesh = BKE_mesh_new_nomain(
+      output_mesh.totvertices, 0, output_mesh.totquads + output_mesh.tottriangles, 0, 0);
+  int q = output_mesh.totquads;
+
+  for (int i = 0; i < output_mesh.totvertices; i++) {
+    float vco[3] = {output_mesh.vertices[i * 3],
+                    output_mesh.vertices[i * 3 + 1],
+                    output_mesh.vertices[i * 3 + 2]};
+    copy_v3_v3(mesh->mvert[i].co, vco);
+  }
+
+  for (int i = 0; i < output_mesh.totquads; i++) {
+    mesh->mface[i].v4 = output_mesh.quads[i * 4];
+    mesh->mface[i].v3 = output_mesh.quads[i * 4 + 1];
+    mesh->mface[i].v2 = output_mesh.quads[i * 4 + 2];
+    mesh->mface[i].v1 = output_mesh.quads[i * 4 + 3];
+  }
+
+  for (int i = 0; i < output_mesh.tottriangles; i++) {
+    mesh->mface[i + q].v4 = 0;
+    mesh->mface[i + q].v3 = output_mesh.triangles[i * 3];
+    mesh->mface[i + q].v2 = output_mesh.triangles[i * 3 + 1];
+    mesh->mface[i + q].v1 = output_mesh.triangles[i * 3 + 2];
+  }
+
+  BKE_mesh_calc_edges_tessface(mesh);
+  BKE_mesh_convert_mfaces_to_mpolys(mesh);
+  BKE_mesh_tessface_clear(mesh);
+  BKE_mesh_calc_normals(mesh);
+
+  MEM_freeN(output_mesh.quads);
+  MEM_freeN(output_mesh.vertices);
+
+  if (output_mesh.tottriangles > 0) {
+    MEM_freeN(output_mesh.triangles);
+  }
+
+  return mesh;
+}
+#endif
+
+Mesh *BKE_remesh_voxel_to_mesh_nomain(Mesh *mesh, float voxel_size)
+{
+  Mesh *new_mesh = NULL;
+#ifdef WITH_OPENVDB
+  struct OpenVDBLevelSet *level_set;
+  struct OpenVDBTransform *xform = OpenVDBTransform_create();
+  OpenVDBTransform_create_linear_transform(xform, (double)voxel_size);
+  level_set = BKE_remesh_voxel_ovdb_mesh_to_level_set_create(mesh, xform);
+  new_mesh = BKE_remesh_voxel_ovdb_volume_to_mesh_nomain(level_set, 0.0, 0.0, false);
+  OpenVDBLevelSet_free(level_set);
+  OpenVDBTransform_free(xform);
+#endif
+  return new_mesh;
+}
+
+void BKE_remesh_reproject_paint_mask(Mesh *target, Mesh *source)
+{
+  BVHTreeFromMesh bvhtree = {
+      .nearest_callback = NULL,
+  };
+  BKE_bvhtree_from_mesh_get(&bvhtree, source, BVHTREE_FROM_VERTS, 2);
+  MVert *target_verts = CustomData_get_layer(&target->vdata, CD_MVERT);
+
+  float *target_mask;
+  if (CustomData_has_layer(&target->vdata, CD_PAINT_MASK)) {
+    target_mask = CustomData_get_layer(&target->vdata, CD_PAINT_MASK);
+  }
+  else {
+    target_mask = CustomData_add_layer(
+        &target->vdata, CD_PAINT_MASK, CD_CALLOC, NULL, target->totvert);
+  }
+
+  float *source_mask;
+  if (CustomData_has_layer(&source->vdata, CD_PAINT_MASK)) {
+    source_mask = CustomData_get_layer(&source->vdata, CD_PAINT_MASK);
+  }
+  else {
+    source_mask = CustomData_add_layer(
+        &source->vdata, CD_PAINT_MASK, CD_CALLOC, NULL, source->totvert);
+  }
+
+  for (int i = 0; i < target->totvert; i++) {
+    float from_co[3];
+    BVHTreeNearest nearest;
+    nearest.index = -1;
+    nearest.dist_sq = FLT_MAX;
+    copy_v3_v3(from_co, target_verts[i].co);
+    BLI_bvhtree_find_nearest(bvhtree.tree, from_co, &nearest, bvhtree.nearest_callback, &bvhtree);
+    if (nearest.index != -1) {
+      target_mask[i] = source_mask[nearest.index];
+    }
+  }
+  free_bvhtree_from_mesh(&bvhtree);
+}
index 52d1269dc1bf74576dd9a7cafd679d2ddecd6d53..462d32d40bdd89602cef7207405a383cea77b926 100644 (file)
@@ -3752,5 +3752,10 @@ void blo_do_versions_280(FileData *fd, Library *UNUSED(lib), Main *bmain)
         }
       }
     }
+    for (Mesh *mesh = bmain->meshes.first; mesh; mesh = mesh->id.next) {
+      if (mesh->remesh_voxel_size == 0.0f) {
+        mesh->remesh_voxel_size = 0.1f;
+      }
+    }
   }
 }
index 86e108a26c65477569f39d703f073ec2f7b28a8c..034e002f86a4c0b15b5b5b57e13d3c4ca47b1970 100644 (file)
@@ -45,4 +45,7 @@ bool ED_sculpt_mask_box_select(struct bContext *C,
 /* sculpt_undo.c */
 void ED_sculpt_undosys_type(struct UndoType *ut);
 
+void ED_sculpt_undo_geometry_begin(struct Object *ob);
+void ED_sculpt_undo_geometry_end(struct Object *ob);
+
 #endif /* __ED_SCULPT_H__ */
index eaef93134315d9fa7807d64512bd27ee2f27802e..2490f88b5eb5bc5a335661a082de408d735a3350 100644 (file)
@@ -57,6 +57,7 @@ set(SRC
   object_ops.c
   object_random.c
   object_relations.c
+  object_remesh.c
   object_select.c
   object_shader_fx.c
   object_shapekey.c
index e697c25b37fcac7fcc192f4473558b30fd4bbdd5..4b369c10e4ddb74d5c89092a2db88dc4aa0846fc 100644 (file)
@@ -279,6 +279,9 @@ void OBJECT_OT_bake(wmOperatorType *ot);
 /* object_random.c */
 void TRANSFORM_OT_vertex_random(struct wmOperatorType *ot);
 
+/* object_remesh.c */
+void OBJECT_OT_voxel_remesh(struct wmOperatorType *ot);
+
 /* object_transfer_data.c */
 void OBJECT_OT_data_transfer(struct wmOperatorType *ot);
 void OBJECT_OT_datalayout_transfer(struct wmOperatorType *ot);
index b653c7fa70c22f839210de72ff8c00b5729ad2ca..38c063194505994170de7ebf3a4afbe41ba278ea 100644 (file)
@@ -257,6 +257,8 @@ void ED_operatortypes_object(void)
   WM_operatortype_append(OBJECT_OT_hide_view_clear);
   WM_operatortype_append(OBJECT_OT_hide_view_set);
   WM_operatortype_append(OBJECT_OT_hide_collection);
+
+  WM_operatortype_append(OBJECT_OT_voxel_remesh);
 }
 
 void ED_operatormacros_object(void)
diff --git a/source/blender/editors/object/object_remesh.c b/source/blender/editors/object/object_remesh.c
new file mode 100644 (file)
index 0000000..a7e7256
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * 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) 2019 by Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup edobj
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <float.h>
+#include <ctype.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_scene_types.h"
+#include "DNA_object_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_mesh_types.h"
+
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+#include "BKE_paint.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+#include "BKE_customdata.h"
+#include "BKE_remesh.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "ED_mesh.h"
+#include "ED_object.h"
+#include "ED_screen.h"
+#include "ED_sculpt.h"
+#include "ED_undo.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+#include "WM_message.h"
+#include "WM_toolsystem.h"
+
+#include "object_intern.h"  // own include
+
+static bool object_remesh_poll(bContext *C)
+{
+  Object *ob = CTX_data_active_object(C);
+
+  if (BKE_object_is_in_editmode(ob)) {
+    CTX_wm_operator_poll_msg_set(C, "The voxel remesher cannot run from edit mode.");
+    return false;
+  }
+
+  if (ob->mode == OB_MODE_SCULPT && ob->sculpt->bm) {
+    CTX_wm_operator_poll_msg_set(C, "The voxel remesher cannot run with dyntopo activated.");
+  }
+
+  return ED_operator_object_active_editable_mesh(C);
+}
+
+static int voxel_remesh_exec(bContext *C, wmOperator *op)
+{
+  Object *ob = CTX_data_active_object(C);
+  Main *bmain = CTX_data_main(C);
+
+  Mesh *mesh = ob->data;
+  Mesh *new_mesh;
+
+  if (mesh->remesh_voxel_size <= 0.0f) {
+    BKE_report(op->reports, RPT_ERROR, "Voxel remesher cannot run with a voxel size of 0.0.");
+    return OPERATOR_CANCELLED;
+  }
+
+  if (ob->mode == OB_MODE_SCULPT) {
+    ED_sculpt_undo_geometry_begin(ob);
+  }
+
+  new_mesh = BKE_remesh_voxel_to_mesh_nomain(mesh, mesh->remesh_voxel_size);
+
+  if (!new_mesh) {
+    return OPERATOR_CANCELLED;
+  }
+
+  Mesh *obj_mesh_copy;
+  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);
+    }
+  }
+
+  BKE_mesh_nomain_to_mesh(new_mesh, mesh, ob, &CD_MASK_MESH, true);
+  BKE_mesh_free(new_mesh);
+
+  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, true);
+  }
+
+  if (ob->mode == OB_MODE_SCULPT) {
+    ED_sculpt_undo_geometry_end(ob);
+  }
+
+  BKE_mesh_batch_cache_dirty_tag(ob->data, BKE_MESH_BATCH_DIRTY_ALL);
+  DEG_relations_tag_update(bmain);
+  DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
+  WM_event_add_notifier(C, NC_GEOM | ND_DATA, ob->data);
+
+  return OPERATOR_FINISHED;
+}
+
+void OBJECT_OT_voxel_remesh(wmOperatorType *ot)
+{
+  /* identifiers */
+  ot->name = "Voxel Remesh";
+  ot->description =
+      "Calculates a new manifold mesh based on the volume of the current mesh. All data layers "
+      "will be lost";
+  ot->idname = "OBJECT_OT_voxel_remesh";
+
+  /* api callbacks */
+  ot->poll = object_remesh_poll;
+  ot->exec = voxel_remesh_exec;
+
+  ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
index 285e6aff7d01724717a9b996de7acb7df5ac0d64..efaac6e97cf9117b668aab6696af787337a6553c 100644 (file)
@@ -5695,31 +5695,19 @@ static void sculpt_dynamic_topology_disable_ex(
     CustomData_free(&me->pdata, me->totpoly);
 
     /* Copy over stored custom data */
-    me->totvert = unode->bm_enter_totvert;
-    me->totloop = unode->bm_enter_totloop;
-    me->totpoly = unode->bm_enter_totpoly;
-    me->totedge = unode->bm_enter_totedge;
+    me->totvert = unode->geom_totvert;
+    me->totloop = unode->geom_totloop;
+    me->totpoly = unode->geom_totpoly;
+    me->totedge = unode->geom_totedge;
     me->totface = 0;
-    CustomData_copy(&unode->bm_enter_vdata,
-                    &me->vdata,
-                    CD_MASK_MESH.vmask,
-                    CD_DUPLICATE,
-                    unode->bm_enter_totvert);
-    CustomData_copy(&unode->bm_enter_edata,
-                    &me->edata,
-                    CD_MASK_MESH.emask,
-                    CD_DUPLICATE,
-                    unode->bm_enter_totedge);
-    CustomData_copy(&unode->bm_enter_ldata,
-                    &me->ldata,
-                    CD_MASK_MESH.lmask,
-                    CD_DUPLICATE,
-                    unode->bm_enter_totloop);
-    CustomData_copy(&unode->bm_enter_pdata,
-                    &me->pdata,
-                    CD_MASK_MESH.pmask,
-                    CD_DUPLICATE,
-                    unode->bm_enter_totpoly);
+    CustomData_copy(
+        &unode->geom_vdata, &me->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, unode->geom_totvert);
+    CustomData_copy(
+        &unode->geom_edata, &me->edata, CD_MASK_MESH.emask, CD_DUPLICATE, unode->geom_totedge);
+    CustomData_copy(
+        &unode->geom_ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, unode->geom_totloop);
+    CustomData_copy(
+        &unode->geom_pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, unode->geom_totpoly);
 
     BKE_mesh_update_customdata_pointers(me, false);
   }
index e66e1c49685b3299b98642a870479406e58c8b3e..e646accf1081fbecb471c356183aff45e66ca88b 100644 (file)
@@ -63,6 +63,7 @@ typedef enum {
   SCULPT_UNDO_DYNTOPO_BEGIN,
   SCULPT_UNDO_DYNTOPO_END,
   SCULPT_UNDO_DYNTOPO_SYMMETRIZE,
+  SCULPT_UNDO_GEOMETRY,
 } SculptUndoType;
 
 typedef struct SculptUndoNode {
@@ -94,18 +95,20 @@ typedef struct SculptUndoNode {
   /* bmesh */
   struct BMLogEntry *bm_entry;
   bool applied;
-  CustomData bm_enter_vdata;
-  CustomData bm_enter_edata;
-  CustomData bm_enter_ldata;
-  CustomData bm_enter_pdata;
-  int bm_enter_totvert;
-  int bm_enter_totedge;
-  int bm_enter_totloop;
-  int bm_enter_totpoly;
 
   /* shape keys */
   char shapeName[sizeof(((KeyBlock *)0))->name];
 
+  /* geometry modification operations and bmesh enter data */
+  CustomData geom_vdata;
+  CustomData geom_edata;
+  CustomData geom_ldata;
+  CustomData geom_pdata;
+  int geom_totvert;
+  int geom_totedge;
+  int geom_totloop;
+  int geom_totpoly;
+
   size_t undo_size;
 } SculptUndoNode;
 
index d4c97faa0a669c3ff87bfc7fb9c018b6af40bf60..3a3487227a3329ed254b4dcc47e04b8d191f3c60 100644 (file)
@@ -44,6 +44,7 @@
 
 #include "BKE_ccg.h"
 #include "BKE_context.h"
+#include "BKE_customdata.h"
 #include "BKE_multires.h"
 #include "BKE_paint.h"
 #include "BKE_key.h"
@@ -425,6 +426,32 @@ static void sculpt_undo_bmesh_restore_end(bContext *C,
   }
 }
 
+static void sculpt_undo_geometry_restore(SculptUndoNode *unode, Object *ob)
+{
+  Mesh *me;
+  sculpt_pbvh_clear(ob);
+  me = ob->data;
+  CustomData_free(&me->vdata, me->totvert);
+  CustomData_free(&me->edata, me->totedge);
+  CustomData_free(&me->fdata, me->totface);
+  CustomData_free(&me->ldata, me->totloop);
+  CustomData_free(&me->pdata, me->totpoly);
+  me->totvert = unode->geom_totvert;
+  me->totedge = unode->geom_totedge;
+  me->totloop = unode->geom_totloop;
+  me->totpoly = unode->geom_totpoly;
+  me->totface = 0;
+  CustomData_copy(
+      &unode->geom_vdata, &me->vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, unode->geom_totvert);
+  CustomData_copy(
+      &unode->geom_edata, &me->edata, CD_MASK_MESH.emask, CD_DUPLICATE, unode->geom_totedge);
+  CustomData_copy(
+      &unode->geom_ldata, &me->ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, unode->geom_totloop);
+  CustomData_copy(
+      &unode->geom_pdata, &me->pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, unode->geom_totpoly);
+  BKE_mesh_update_customdata_pointers(me, false);
+}
+
 /* Handle all dynamic-topology updates
  *
  * Returns true if this was a dynamic-topology undo step, otherwise
@@ -442,7 +469,6 @@ static int sculpt_undo_bmesh_restore(bContext *C,
     case SCULPT_UNDO_DYNTOPO_END:
       sculpt_undo_bmesh_restore_end(C, unode, ob, ss);
       return true;
-
     default:
       if (ss->bm_log) {
         sculpt_undo_bmesh_restore_generic(C, unode, ob, ss);
@@ -480,6 +506,24 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
 
   DEG_id_tag_update(&ob->id, ID_RECALC_SHADING);
 
+  if (lb->first) {
+    unode = lb->first;
+    if (unode->type == SCULPT_UNDO_GEOMETRY) {
+      if (unode->applied) {
+        sculpt_undo_geometry_restore(unode->next, ob);
+        unode->next->applied = true;
+        unode->applied = false;
+      }
+      else {
+        sculpt_undo_geometry_restore(unode, ob);
+        unode->next->applied = false;
+        unode->applied = true;
+      }
+      BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
+      return;
+    }
+  }
+
   BKE_sculpt_update_object_for_edit(depsgraph, ob, false, need_mask);
 
   if (lb->first && sculpt_undo_bmesh_restore(C, lb->first, ob, ss)) {
@@ -487,6 +531,7 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
   }
 
   for (unode = lb->first; unode; unode = unode->next) {
+
     if (!STREQ(unode->idname, ob->id.name)) {
       continue;
     }
@@ -530,6 +575,8 @@ static void sculpt_undo_restore_list(bContext *C, Depsgraph *depsgraph, ListBase
       case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
         BLI_assert(!"Dynamic topology should've already been handled");
         break;
+      case SCULPT_UNDO_GEOMETRY:
+        break;
     }
   }
 
@@ -617,17 +664,17 @@ static void sculpt_undo_free_list(ListBase *lb)
       BM_log_entry_drop(unode->bm_entry);
     }
 
-    if (unode->bm_enter_totvert) {
-      CustomData_free(&unode->bm_enter_vdata, unode->bm_enter_totvert);
+    if (unode->geom_totvert) {
+      CustomData_free(&unode->geom_vdata, unode->geom_totvert);
     }
-    if (unode->bm_enter_totedge) {
-      CustomData_free(&unode->bm_enter_edata, unode->bm_enter_totedge);
+    if (unode->geom_totedge) {
+      CustomData_free(&unode->geom_edata, unode->geom_totedge);
     }
-    if (unode->bm_enter_totloop) {
-      CustomData_free(&unode->bm_enter_ldata, unode->bm_enter_totloop);
+    if (unode->geom_totloop) {
+      CustomData_free(&unode->geom_ldata, unode->geom_totloop);
     }
-    if (unode->bm_enter_totpoly) {
-      CustomData_free(&unode->bm_enter_pdata, unode->bm_enter_totpoly);
+    if (unode->geom_totpoly) {
+      CustomData_free(&unode->geom_pdata, unode->geom_totpoly);
     }
 
     MEM_freeN(unode);
@@ -743,6 +790,7 @@ static SculptUndoNode *sculpt_undo_alloc_node(Object *ob, PBVHNode *node, Sculpt
     case SCULPT_UNDO_DYNTOPO_END:
     case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
       BLI_assert(!"Dynamic topology should've already been handled");
+    case SCULPT_UNDO_GEOMETRY:
       break;
   }
 
@@ -824,6 +872,36 @@ static void sculpt_undo_store_mask(Object *ob, SculptUndoNode *unode)
   BKE_pbvh_vertex_iter_end;
 }
 
+static SculptUndoNode *sculpt_undo_geometry_push(Object *ob, SculptUndoType type)
+{
+  UndoSculpt *usculpt = sculpt_undo_get_nodes();
+  Mesh *me = ob->data;
+  bool applied;
+
+  SculptUndoNode *unode = usculpt->nodes.first;
+  /* Store the original mesh in the first node, modifications in the second */
+  applied = unode != NULL;
+
+  unode = MEM_callocN(sizeof(*unode), __func__);
+
+  BLI_strncpy(unode->idname, ob->id.name, sizeof(unode->idname));
+  unode->type = type;
+  unode->applied = applied;
+
+  CustomData_copy(&me->vdata, &unode->geom_vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, me->totvert);
+  CustomData_copy(&me->edata, &unode->geom_edata, CD_MASK_MESH.emask, CD_DUPLICATE, me->totedge);
+  CustomData_copy(&me->ldata, &unode->geom_ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, me->totloop);
+  CustomData_copy(&me->pdata, &unode->geom_pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, me->totpoly);
+  unode->geom_totvert = me->totvert;
+  unode->geom_totedge = me->totedge;
+  unode->geom_totloop = me->totloop;
+  unode->geom_totpoly = me->totpoly;
+
+  BLI_addtail(&usculpt->nodes, unode);
+
+  return unode;
+}
+
 static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, SculptUndoType type)
 {
   UndoSculpt *usculpt = sculpt_undo_get_nodes();
@@ -852,17 +930,17 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
        * (converting polys to triangles) that the BMLog can't
        * fully restore from */
       CustomData_copy(
-          &me->vdata, &unode->bm_enter_vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, me->totvert);
+          &me->vdata, &unode->geom_vdata, CD_MASK_MESH.vmask, CD_DUPLICATE, me->totvert);
       CustomData_copy(
-          &me->edata, &unode->bm_enter_edata, CD_MASK_MESH.emask, CD_DUPLICATE, me->totedge);
+          &me->edata, &unode->geom_edata, CD_MASK_MESH.emask, CD_DUPLICATE, me->totedge);
       CustomData_copy(
-          &me->ldata, &unode->bm_enter_ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, me->totloop);
+          &me->ldata, &unode->geom_ldata, CD_MASK_MESH.lmask, CD_DUPLICATE, me->totloop);
       CustomData_copy(
-          &me->pdata, &unode->bm_enter_pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, me->totpoly);
-      unode->bm_enter_totvert = me->totvert;
-      unode->bm_enter_totedge = me->totedge;
-      unode->bm_enter_totloop = me->totloop;
-      unode->bm_enter_totpoly = me->totpoly;
+          &me->pdata, &unode->geom_pdata, CD_MASK_MESH.pmask, CD_DUPLICATE, me->totpoly);
+      unode->geom_totvert = me->totvert;
+      unode->geom_totedge = me->totedge;
+      unode->geom_totloop = me->totloop;
+      unode->geom_totpoly = me->totpoly;
 
       unode->bm_entry = BM_log_entry_add(ss->bm_log);
       BM_log_all_added(ss->bm, ss->bm_log);
@@ -906,6 +984,7 @@ static SculptUndoNode *sculpt_undo_bmesh_push(Object *ob, PBVHNode *node, Sculpt
       case SCULPT_UNDO_DYNTOPO_BEGIN:
       case SCULPT_UNDO_DYNTOPO_END:
       case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
+      case SCULPT_UNDO_GEOMETRY:
         break;
     }
   }
@@ -928,6 +1007,11 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
     BLI_thread_unlock(LOCK_CUSTOM1);
     return unode;
   }
+  else if (type == SCULPT_UNDO_GEOMETRY) {
+    unode = sculpt_undo_geometry_push(ob, type);
+    BLI_thread_unlock(LOCK_CUSTOM1);
+    return unode;
+  }
   else if ((unode = sculpt_undo_get_node(node))) {
     BLI_thread_unlock(LOCK_CUSTOM1);
     return unode;
@@ -967,6 +1051,7 @@ SculptUndoNode *sculpt_undo_push_node(Object *ob, PBVHNode *node, SculptUndoType
     case SCULPT_UNDO_DYNTOPO_END:
     case SCULPT_UNDO_DYNTOPO_SYMMETRIZE:
       BLI_assert(!"Dynamic topology should've already been handled");
+    case SCULPT_UNDO_GEOMETRY:
       break;
   }
 
@@ -1163,6 +1248,18 @@ static void sculpt_undosys_step_free(UndoStep *us_p)
   sculpt_undo_free_list(&us->data.nodes);
 }
 
+void ED_sculpt_undo_geometry_begin(struct Object *ob)
+{
+  sculpt_undo_push_begin("voxel remesh");
+  sculpt_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY);
+}
+
+void ED_sculpt_undo_geometry_end(struct Object *ob)
+{
+  sculpt_undo_push_node(ob, NULL, SCULPT_UNDO_GEOMETRY);
+  sculpt_undo_push_end();
+}
+
 /* Export for ED_undo_sys. */
 void ED_sculpt_undosys_type(UndoType *ut)
 {
index 8a9a69ac178d30984b4ebde8cb247c97aad0c6c9..caa7c98335a77ca5bf0ba8baa8613a2e93577b7a 100644 (file)
@@ -193,9 +193,10 @@ typedef struct Mesh {
 
   short totcol;
 
+  float remesh_voxel_size;
+  char _pad1[4];
   /** Deprecated multiresolution modeling data, only keep for loading old files. */
   struct Multires *mr DNA_DEPRECATED;
-  void *_pad1;
 
   Mesh_Runtime runtime;
 } Mesh;
@@ -250,6 +251,8 @@ enum {
   ME_FLAG_UNUSED_8 = 1 << 8, /* cleared */
   ME_DS_EXPAND = 1 << 9,
   ME_SCULPT_DYNAMIC_TOPOLOGY = 1 << 10,
+  ME_REMESH_SMOOTH_NORMALS = 1 << 11,
+  ME_REMESH_REPROJECT_PAINT_MASK = 1 << 12,
 };
 
 /* me->cd_flag */
index 03173bcb3dab50f1355f8a8985787d0e33b621fa..966dc071641f70da1ab53de9a6db462c4a9b6e2e 100644 (file)
@@ -2998,6 +2998,30 @@ static void rna_def_mesh(BlenderRNA *brna)
   rna_def_paint_mask(brna, prop);
   /* End paint mask */
 
+  /* Remesh */
+  prop = RNA_def_property(srna, "remesh_voxel_size", PROP_FLOAT, PROP_DISTANCE);
+  RNA_def_property_float_sdna(prop, NULL, "remesh_voxel_size");
+  RNA_def_property_float_default(prop, 0.1f);
+  RNA_def_property_range(prop, 0.00001f, FLT_MAX);
+  RNA_def_property_ui_range(prop, 0.0001f, FLT_MAX, 0.01, 4);
+  RNA_def_property_ui_text(prop,
+                           "Voxel size",
+                           "Size of the voxel in object space used for volume evaluation. Lower "
+                           "values preserve finer details");
+  RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
+
+  prop = RNA_def_property(srna, "remesh_smooth_normals", PROP_BOOLEAN, PROP_NONE);
+  RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_REMESH_SMOOTH_NORMALS);
+  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_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);
+  RNA_def_property_ui_text(prop, "Preserve Paint Mask", "Keep the current mask on the new mesh");
+  RNA_def_property_update(prop, 0, "rna_Mesh_update_draw");
+  /* End remesh */
+
   prop = RNA_def_property(srna, "use_auto_smooth", PROP_BOOLEAN, PROP_NONE);
   RNA_def_property_boolean_sdna(prop, NULL, "flag", ME_AUTOSMOOTH);
   RNA_def_property_ui_text(